Go单元测试利器testify使用示例详解
小马别过河 人气:0testify
在团队里推行单元测试的时候,有一个反对的意见是:写单元测试耗时太多。且不论这个意见对错,单元测试确实不应该太费时间。这时候,一个好的单测辅助工具,显得格外重要。本文推荐的 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/…
引用
加载全部内容