Review 《github.com/stretchr/testify》
testify 是一组工具包,它里面的很多工具可以让你更舒适地写 Go 测试代码。
它包含以下特征:
知识索引:
- 使用一行代码安装 testify,使用另一行代码来更新它
- 在 Go 中写测试代码的相关介绍, 请参考 http://golang.org/doc/code.html#Testing
- 在 http://godoc.org/github.com/stretchr/testify 查看我们的 API 文档
- 为了让你的测试生活更简单一些,建议使用我们开发的测试工具 gorc
- 关于 Test-Driven Development (TDD) 的一点点介绍
assert 包
assert 包提供了一些有用的方法,可以让你写出更友好的测试代码。它具有以下特性:
- 打印友好,很容易去阅读错误信息
- 可以开发出易阅读的代码
- 可以在每个
assert
后添加一个可选的注解信息
让我们通过一个实际例子来了解它:
package testify_exam
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")
var obj chan struct{}
// 测试 nil,常用于错误值的判断
assert.Nil(t, obj)
obj2 := struct {
Value string
}{
Value: "Something",
}
// 测试不为 nil,常用于你希望返回的结果值拥有某些东西的时候
if assert.NotNil(t, obj2) {
// 现在我们知道 obj2 的值不是 nil了,我们可以在这个基础上**安全地**访问它的字段。
assert.Equal(t, "Something", obj2.Value)
}
}
- 每个
assert
函数都使用testing.T
来作为它的第一个参数,这就是它通过正常的 go 测试功能将错误输出写出来的方式。 - 每个
assert
函数返回了一个布尔值,表示这个断言成功与否,如果你想在某个断言结果的基础上做出更多的断言,可以用到它的返回值。
如果你使用断言很多次,可以使用如下的代码:
package testify_exam
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEasyAssert(t *testing.T) {
assert := assert.New(t)
assert.Equal(123, 123, "they should be equal")
assert.NotEqual(123, 456, "they should not be equal")
var obj chan struct{}
assert.Nil(obj)
obj2 := struct {
Value string
}{
Value: "Something",
}
if assert.NotNil(obj2) {
assert.Equal("Something", obj2.Value)
}
}
require 包
require
包中拥有和assert
包相同的全局函数,它和assert
的不同在于,它不会再返回一个布尔值表示测试运行成功与否,而是直接结束当前测试
可以参考 t.FailNow 函数了解更多的信息
下面是一个require
包和assert
包的比较示例:
package testify_exam
import (
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// `require.True` 失败后会将测试 TestEx1 停掉,不会再执行后面的 require.Equal 断言
// `assert.True` 失败后并不会将 TestEx2 停掉,在后面的断言中,Add 依然会被执行,
// 所以 Add 会在 TestEx2 中执行一次, count 的最终结果是1
var count int32 = 0
func Add(a, b int) int {
atomic.AddInt32(&count, 1)
return a + b
}
func TestEx1(t *testing.T) {
require.True(t, false)
require.Equal(t, Add(1, 1), 2)
}
func TestEx2(t *testing.T) {
assert.True(t, false)
assert.Equal(t, Add(1, 1), 2)
assert.Equal(t, count, int32(1))
}
// 下面是程序的运行结果
//>>> go test -v testify_exam/require_test.go 23:30:19 (06-02)
//=== RUN TestEx1
//--- FAIL: TestEx1 (0.00s)
//Error Trace: require_test.go:19
//Error: Should be true
//=== RUN TestEx2
//--- FAIL: TestEx2 (0.00s)
//Error Trace: require_test.go:24
//Error: Should be true
//FAIL
//FAIL command-line-arguments 0.018s
mock 包
mock
提供了一种机制,可以在编写测试代码的时候轻松编写可用于替代实际对象的模拟对象。
下面是一段示例代码,它测试了一段依赖于外部对象testObj
的代码,并且能够设置期望(testify
)且断言他们确实发生了。
package yours
import (
"testing"
"github.com/stretchr/testify/mock"
)
/*
Test objects
*/
// MyMockedObject is a mocked object that implements an interface
// that describes an object that the code I am testing relies on.
type MyMockedObject struct{
mock.Mock
}
// DoSomething is a method on MyMockedObject that implements some interface
// and just records the activity, and returns what the Mock object tells it to.
//
// In the real object, this method would do something useful, but since this
// is a mocked object - we're just going to stub it out.
//
// NOTE: This method is not being tested here, code that uses this object is.
func (m *MyMockedObject) DoSomething(number int) (bool, error) {
args := m.Called(number)
return args.Bool(0), args.Error(1)
}
/*
Actual test functions
*/
// TestSomething is an example of how to use our test object to
// make assertions about some target code we are testing.
func TestSomething(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations
testObj.On("DoSomething", 123).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
// TestSomethingWithPlaceholder is a second example of how to use our test object to
// make assertions about some target code we are testing.
// This time using a placeholder. Placeholders might be used when the
// data being passed in is normally dynamically generated and cannot be
// predicted beforehand (eg. containing hashes that are time sensitive)
func TestSomethingWithPlaceholder(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations with a placeholder in the argument list
testObj.On("DoSomething", mock.Anything).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
}
// TestSomethingElse2 is a third example that shows how you can use
// the Unset method to cleanup handlers and then add new ones.
func TestSomethingElse2(t *testing.T) {
// create an instance of our test object
testObj := new(MyMockedObject)
// set up expectations with a placeholder in the argument list
mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
// call the code we are testing
targetFuncThatDoesSomethingWithObj(testObj)
// assert that the expectations were met
testObj.AssertExpectations(t)
// remove the handler now so we can add another one that takes precedence
mockCall.Unset()
// return false now instead of true
testObj.On("DoSomething", mock.Anything).Return(false, nil)
testObj.AssertExpectations(t)
}
2019年06月02日 / 22:41