testify 是单元测试中较为常用的包
文档:https://pkg.go.dev/github.com/stretchr/testify
assert
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
// assert equality
assert.Equal(t, 123, 123, "they should be equal")
// assert inequality
assert.NotEqual(t, 123, 456, "they should not be equal")
// assert for nil (good for errors)
assert.Nil(t, object)
// assert for not nil (good when you expect something)
if assert.NotNil(t, object) {
// now we know that object isn't nil, we are safe to make
// further assertions without causing any errors
assert.Equal(t, "Something", object.Value)
}
}
“t” 一直要做第一个参数,你可能觉得很烦,这里也提供了另一种写法
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert := assert.New(t)
// assert equality
assert.Equal(123, 1213, "they should be equal")
// assert inequality
assert.NotEqual(123, 456, "they should not be equal")
}
require
与assert 用法相同,只是如果失败,调用 t.FailNow()终止测试用例,而assert 调用的是t.Fail() 程序接着走
package main
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestSomething(t *testing.T) {
// assert equality
require.Equal(t, 123, 1123, "they should be equal")
// assert inequality
require.NotEqual(t, 123, 456, "they should not be equal")
}
mock
在编写测试代码时可以使用模拟对象代替实际对象
在这个例子中,我们有一个系统会尝试向客户收取产品或服务的费用。当 ChargeCustomer() 被调用时,它将随后调用 Message Service,向客户发送 SMS 文本消息来通知他们已经被收取的金额。
package main
import (
"fmt"
)
// MessageService 通知客户被收取的费用
type MessageService interface {
SendChargeNotification(int) error
}
// SMSService 是 MessageService 的实现
type SMSService struct{}
// MyService 使用 MessageService 来通知客户
type MyService struct {
messageService MessageService
}
// SendChargeNotification 通过 SMS 来告知客户他们被收取费用
// 这就是我们将要模拟的方法
func (sms SMSService) SendChargeNotification(value int) error {
fmt.Println("Sending Production Charge Notification")
return nil
}
// ChargeCustomer 向客户收取费用
// 在真实系统中,我们会模拟这个
// 但是在这里,我想在每次运行测试时都赚点钱
func (a MyService) ChargeCustomer(value int) error {
a.messageService.SendChargeNotification(value)
fmt.Printf("Charging Customer For the value of %d\n", value)
return nil
}
// 一个 "Production" 例子
func main() {
fmt.Println("Hello World")
smsService := SMSService{}
myService := MyService{smsService}
myService.ChargeCustomer(100)
}
那么,我们如何进行测试以确保我们不会让客户疯掉?好吧,我们通过创建一个新的 struct 称之为 smsServiceMock ,用来模拟我们的 SMSService,并且将 mock.Mock 添加到它的字段列表中。
然后我们将改写 SendChargeNotification 方法,这样它就不会向我们的客户发送通知并返回 nil 错误。
最后,我们创建 TestChargeCustomer 测试函数,接着实例化一个新的类型实例 smsServiceMock 并指定 SendChargeNotification 在被调用时应该做什么。
package main
import (
"fmt"
"testing"
"github.com/stretchr/testify/mock"
)
// smsServiceMock
type smsServiceMock struct {
mock.Mock
}
// 我们模拟的 smsService 方法
func (m *smsServiceMock) SendChargeNotification(value int) error {
fmt.Println("Mocked charge notification function")
fmt.Printf("Value passed in: %d\n", value)
// Called告诉模拟对象某个方法已被调用,并返回return中设置的参数
args := m.Called(value)
// 它将返回任何我需要返回的
// 这种情况下模拟一个 SMS Service Notification 被发送出去
return args.Error(0) // args.Get(0).(error) 的简写
}
// 我们将实现 MessageService 接口
// 这就意味着我们不得不改写在接口中定义的所有方法
func (m *smsServiceMock) DummyFunc() {
fmt.Println("Dummy")
}
// TestChargeCustomer 是个奇迹发生的地方
// 在这里我们将创建 SMSService mock
func TestChargeCustomer(t *testing.T) {
smsService := new(smsServiceMock)
// 然后我们将定义当 100 传递给 SendChargeNotification 时,需要返回什么
// 在这里,我们希望它在成功发送通知后返回 nil(Return 设置预期返回值)
// run 是 Called里调用的,只能拿到SendChargeNotification的入参
smsService.On("SendChargeNotification", 100).Return(nil).Run(func(args mock.Arguments) {
// run 是在SendChargeNotification 中 调用 Called 后执行的
// 这里的args 是Called里的入参
fmt.Println("+++++++++")
})
// 接下来,我们要定义要测试的服务
myService := MyService{smsService}
// 然后调用方法
err := myService.ChargeCustomer(100)
// 最后,我们验证 myService.ChargeCustomer 调用了我们模拟的 SendChargeNotification 方法
smsService.AssertExpectations(t)
}
执行该用例
mac@weideMacBook-Pro afterShip % go test -v -run TestChargeCustomer -short
=== RUN TestChargeCustomer
Mocked charge notification function
Value passed in: 100
Charging Customer For the value of 100
demo1_test.go:49: PASS: SendChargeNotification(int)
--- PASS: TestChargeCustomer (0.00s)
PASS
ok commons/afterShip 0.010s
或者:我们可以换一种灵活些的写法
package main
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// smsServiceMock
type smsServiceMock struct {
mock.Mock
}
// 我们模拟的 smsService 方法
func (m *smsServiceMock) SendChargeNotification(value int) error {
fmt.Println("Mocked charge notification function")
fmt.Printf("Value passed in: %d\n", value)
// 因为我们没设置return,所以len(args) == 0
args := m.Called(value)
return nil
}
// 我们将实现 MessageService 接口
// 这就意味着我们不得不改写在接口中定义的所有方法
func (m *smsServiceMock) DummyFunc() {
fmt.Println("Dummy")
}
// TestChargeCustomer 是个奇迹发生的地方
// 在这里我们将创建 SMSService mock
func TestChargeCustomer(t *testing.T) {
smsService := new(smsServiceMock)
// 不确定 传入的参数,同时也不指定返回值
// 注意:mock.Anything 定义返回值是不被允许的,因为它无法转换为真是的返回值类型
smsService.On("SendChargeNotification", mock.Anything)
// 接下来,我们要定义要测试的服务
myService := MyService{smsService}
// 然后调用方法
err := myService.ChargeCustomer(100)
assert.Nil(t, err)
}
suite
suite 能很方便的提供测试执行流,比如:整个套件在执行前要干什么、单个测试用例执行前要干什么
比如:我们要两个方法需要测试
package main
import "fmt"
func foo() {
fmt.Printf("foo...\n")
}
func goo() {
fmt.Printf("goo...\n")
}
测试用例:
package main
import (
"fmt"
"testing"
"github.com/stretchr/testify/suite"
)
type _Suite struct {
suite.Suite
}
// SetupSuite() 和 TearDownSuite() 仅执行一次
// SetupTest() TearDownTest() BeforeTest() AfterTest() 对套件中的每个测试执行一次
func (s *_Suite) AfterTest(suiteName, testName string) {
fmt.Printf("5.10.AferTest: suiteName=%s,testName=%s\n", suiteName, testName)
}
func (s *_Suite) BeforeTest(suiteName, testName string) {
fmt.Printf("3.8.BeforeTest: suiteName=%s,testName=%s\n", suiteName, testName)
}
// SetupSuite() 仅执行一次
func (s *_Suite) SetupSuite() {
fmt.Printf("1.SetupSuite() ...\n")
}
// TearDownSuite() 仅执行一次
func (s *_Suite) TearDownSuite() {
fmt.Printf("12.TearDowmnSuite()...\n")
}
func (s *_Suite) SetupTest() {
fmt.Printf("2.7.SetupTest()... \n")
}
func (s *_Suite) TearDownTest() {
fmt.Printf("6.11.TearDownTest()... \n")
}
func (s *_Suite) TestFoo() {
foo() // 4.
}
func (s *_Suite) TestGoo() {
goo() //9.
}
// 让 go test 执行测试
func TestGooFoo(t *testing.T) {
suite.Run(t, new(_Suite))
}
执行该用例:
mac@weideMacBook-Pro afterShip % go test -v -run TestGooFoo
=== RUN TestGooFoo
1.SetupSuite() ...
=== RUN TestGooFoo/TestFoo
2.7.SetupTest()...
3.8.BeforeTest: suiteName=_Suite,testName=TestFoo
foo...
5.10.AferTest: suiteName=_Suite,testName=TestFoo
6.11.TearDownTest()...
=== RUN TestGooFoo/TestGoo
2.7.SetupTest()...
3.8.BeforeTest: suiteName=_Suite,testName=TestGoo
goo...
5.10.AferTest: suiteName=_Suite,testName=TestGoo
6.11.TearDownTest()...
12.TearDowmnSuite()...
--- PASS: TestGooFoo (0.00s)
--- PASS: TestGooFoo/TestFoo (0.00s)
--- PASS: TestGooFoo/TestGoo (0.00s)
PASS
ok commons/afterShip 0.014s
assert 常见使用
包含关系
// 断言指定的字符串、列表(数组、slice…)或map包含指定的子字符串或元素
func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
assert.Contains(t, "Hello World", "World")
assert.Contains(t, ["Hello", "World"], "World")
assert.Contains(t, {"Hello": "World"}, "Hello")
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool
assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
元素匹配
// ElementsMatch断言指定的listA(array, slice…)等于指定的listB(array, slice…),忽略元素的顺序。
// 如果存在重复的元素,则它们在两个列表中出现的次数应该匹配。
func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool)
assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2])
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool
assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
元素包含
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool)
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool
assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
空值校验
// Empty断言指定的对象为空。即nil, "", false, 0或一个切片或一个带有len == 0的通道。
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
assert.Empty(t, obj)
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool
assert.Emptyf(t, obj, "error message %s", "formatted")
func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool
相等关系
// 如果参数是指针,则取决于引用值是否相等
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool
// 比较error
// actualObj, err := SomeFunction()
// assert.EqualError(t, err, expectedErrorString)
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool
// EqualValues断言两个对象相等或可转换为相同类型和相等
// assert.EqualValues(t, uint32(123), int32(123))
func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool
// Exactly 判断value 与 type 相等
// assert.Exactly(t, int32(123), int64(123)) 为 false
func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool
error断言
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool
// ErrorAs断言err链中至少有一个错误与target匹配,是error.As的封装
func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool
// ErrorAs断言err链中至少有一个错误与target匹配,是error.Is的封装
func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool
bool 断言
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool
数值判断
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool
is断言
// 集合是否递减
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
assert.IsDecreasing(t, []int{2, 1, 0})
assert.IsDecreasing(t, []float{2, 1})
assert.IsDecreasing(t, []string{"b", "a"})
// 集合是否递增
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
assert.IsIncreasing(t, []int{1, 2, 3})
assert.IsIncreasing(t, []float{1, 2})
assert.IsIncreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
assert.IsNonDecreasing(t, []int{1, 1, 2})
assert.IsNonDecreasing(t, []float{1, 2})
assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
// 类型是否一致
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool
长度判断
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool
nil判断
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
assert.Nil(t, err)
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool
正则
func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool
assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
assert.Regexp(t, "start...$", "it's not starting")
数据格式相关
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool
a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool
func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool
文件相关
func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool
func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
not断言
func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) bool
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool
func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) bool
func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool)
func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool
Delta
// 判断两个数彼此在 delta 之内
func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool
�