Go单元测试利器testify使用示例详解

作者:小马别过河 时间:2024-05-22 10:12:10 

testify

在团队里推行单元测试的时候,有一个反对的意见是:写单元测试耗时太多。且不论这个意见对错,单元测试确实不应该太费时间。这时候,一个好的单测辅助工具,显得格外重要。本文推荐的 testify(github.com/stretchr/te…) 包,具有断言、mock 等功能,能配合标准库,使你的单元测试更加简洁易读。

testify 有三个主要功能:

  • 断言,在 assert 包和 require 包。

  • Mocking,在 mock 包下。

  • 测试组件,在 suite 包下。

assert 包

assert 包提供了一系列很方便的断言方法,简化你的测试代码。如

package yours
import (
 "testing"
 "github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
 // 断言相等
 assert.Equal(t, 123, 123, "they should be equal")
 // 断言不等
 assert.NotEqual(t, 123, 456, "they should not be equal")
 // 断言为 nil
 assert.Nil(t, object)
}

assert 包的函数的第一个参数为 testing.T,用于执行 go test 时输出信息。

如果你有很多个断言,可以调用New方法实例化 Assertions 结构体,然后就可以省略testing.T参数了。上面的代码,可以简化成

func TestSomething(t *testing.T) {
 // 实例化 assertion 结构体,下面的断言都不用传入 t 作为第一个参数了。
 assert := assert.New(t)
 // 断言相等
 assert.Equal(123, 123, "they should be equal")
 // 断言不等
 assert.NotEqual(123, 456, "they should not be equal")
 // 断言为 nil
 assert.Nil(object)
}

assert 失败的话,底层调用 t.Errorf 来输出错误信息。也就是说,断言失败并不会中停止测试。

assert 包的断言函数,返回值是 bool 类型,表示断言的成功或失败。 我们可以根据返回值,进一步做断言。如

// 当 object 不为 nil 的时候,进一步断言 object.Value 的值
if assert.NotNil(t, object) {
 assert.Equal(t, "Something", object.Value)
}

assert 包提供的断言类型非常多,包括对比变量、json、目录、Http 响应等。完整列表见:pkg.go.dev/github.com/…

require 包

require 包提供的函数和 assert 包是一样的,区别是:

  • require 包如果断言失败,底层调用 t.FailNow, 会立刻中断当前的测试,所以也不会有返回值。

  • assert 包如果断言失败,底层调用 t.Errorf,返回 false,不会中断测试。

mock 包

单元测试一般仅限于测试本服务,对于别的服务的调用(比如数据库),我们可以创建 Mock 对象来模拟对其他服务的调用。

和别的语言一样,Mock 对象我们可以通过工具自动生成。mockery(github.com/vektra/mock…) 工具可以根据 golang 的 interface,生成类似下面的 Mock 对象。

package yours
import (
 "testing"
 "github.com/stretchr/testify/mock"
)
// Mock 对象,会依赖 mock.Mock
type MyMockedObject struct{
 mock.Mock
}
// 要 mock 的接口的方法
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
 args := m.Called(number)
 return args.Bool(0), args.Error(1)
}

我们可以调用 Mock 对象的 On 方法,设置对应方法的参数和返回值。如

// 测试
func TestSomething(t *testing.T) {
 // 实例化 Mock 对象
 testObj := new(MyMockedObject)
 // 设置预期,当调用 testObj.DoSomething(123)时,返回 true, nil
 testObj.On("DoSomething", 123).Return(true, nil)
 // 我们要测试的函数
 targetFuncThatDoesSomethingWithObj(testObj)
 // 断言符合预期,即 testObj.DoSomething(123) 会被调用
 testObj.AssertExpectations(t)
}

On方法设置预期(expectations)时,传入的参数可以使用 mock.Anything,表示任何值都行。如

// 设置预期,当调用 DoSomething 时,返回 ture,nil
 testObj.On("DoSomething", mock.Anything).Return(true, nil)

如果有多个 mock 对象需要 AssertExpectations,如

testObj1.AssertExpectations(t)
testObj2.AssertExpectations(t)
testObj3.AssertExpectations(t)

可以使用 mock.AssertExpectations 优化

mock.AssertExpectations(t, testObj1, testObj2, testObj3)

mock.Mock 还有一些很实用的常用断言,如:

  • AssertCalled 断言被调用

  • AssertNotCalled 断言没被调用

  • AssertExpectations 断言 On 和 Return 设置的参数和返回值的方法,有被调用

  • AssertNumberOfCalls 断言调用次数

完整的列表可以查看: pkg.go.dev/github.com/…

suite 包

如果你有别的面向对象语言的经验,用 suite 包写单元测试可能更符合你的习惯。我们可以自定义一个结构体,它依赖 suite.Suite,它所有的以 Test 开头的函数,都是一个测试。

import (
   "testing"
   "github.com/stretchr/testify/assert"
   "github.com/stretchr/testify/suite"
)
// 依赖 suite.Suite
type ExampleTestSuite struct {
   suite.Suite
   VariableThatShouldStartAtFive int
}
// 每个测试运行前,会执行
func (suite *ExampleTestSuite) SetupTest() {
   suite.VariableThatShouldStartAtFive = 5
}
// 所有以“Test”开头的方法,都是一个测试
func (suite *ExampleTestSuite) TestExample() {
   assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
}
// 用于 'go test' 的入口
func TestExampleTestSuite(t *testing.T) {
   suite.Run(t, new(ExampleTestSuite))
}

除了 SetupTest,suite.Suite 还有一些钩子:

TearDownTest 每个测试之后执行

SetupSuite Suite 开始之前执行一次,在所有测试之前执行

TearDownSuite Suite 结束之后执行一次,在所有测试之后执行

更多的介绍可以查看官网:pkg.go.dev/github.com/…

引用

pkg.go.dev/github.com/…

来源:https://juejin.cn/post/7171836190415388703

标签:Go,testify,单元测试
0
投稿

猜你喜欢

  • PHP入门教程之自定义函数用法详解(创建,调用,变量,参数,返回值等)

    2023-10-21 05:36:58
  • Python标准库os库的常用功能解析

    2021-03-06 12:32:51
  • go语言import报错处理图文详解

    2024-02-06 17:01:51
  • SqlServer数据库备份与还原的实现步骤

    2024-01-28 13:08:40
  • python实现微信定时每天和女友发送消息

    2022-08-04 09:43:54
  • python pandas.DataFrame.loc函数使用详解

    2023-10-04 07:01:58
  • python-pymysql如何实现更新mysql表中任意字段数据

    2024-01-19 17:35:01
  • 使用Python的Twisted框架编写非阻塞程序的代码示例

    2021-01-22 16:20:51
  • python 实现 pymysql 数据库操作方法

    2024-01-22 09:56:29
  • python入门学习之自带help功能初步使用示例

    2021-05-27 17:07:28
  • 如何用python写个模板引擎

    2022-07-29 06:09:45
  • ant-design-vue中的select选择器,对输入值的进行筛选操作

    2024-04-26 17:41:26
  • 使用python实现对元素的长截图功能

    2023-11-20 10:27:44
  • 解决pytorch 交叉熵损失输出为负数的问题

    2022-04-29 10:17:33
  • 在Python中操作文件之seek()方法的使用教程

    2023-08-01 14:58:01
  • python字符串连接方法分析

    2021-12-24 16:27:10
  • 如何利用JSHint减少JavaScript的错误

    2024-05-28 15:37:40
  • 用ASP实现txt,doc,jpg等文件下载的函数

    2007-08-17 13:17:00
  • 关于对Java正则表达式"\\\\"的理解

    2023-06-24 07:23:02
  • MySQL下载安装详情图文教程

    2024-01-24 15:48:48
  • asp之家 网络编程 m.aspxhome.com