testify 是单元测试中较为常用的包
文档:https://pkg.go.dev/github.com/stretchr/testify
assert
package mainimport ("testing""github.com/stretchr/testify/assert")func TestSomething(t *testing.T) {// assert equalityassert.Equal(t, 123, 123, "they should be equal")// assert inequalityassert.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 errorsassert.Equal(t, "Something", object.Value)}}
“t” 一直要做第一个参数,你可能觉得很烦,这里也提供了另一种写法
package mainimport ("testing""github.com/stretchr/testify/assert")func TestSomething(t *testing.T) {assert := assert.New(t)// assert equalityassert.Equal(123, 1213, "they should be equal")// assert inequalityassert.NotEqual(123, 456, "they should not be equal")}
require
与assert 用法相同,只是如果失败,调用 t.FailNow()终止测试用例,而assert 调用的是t.Fail() 程序接着走
package mainimport ("testing""github.com/stretchr/testify/require")func TestSomething(t *testing.T) {// assert equalityrequire.Equal(t, 123, 1123, "they should be equal")// assert inequalityrequire.NotEqual(t, 123, 456, "they should not be equal")}
mock
在编写测试代码时可以使用模拟对象代替实际对象
在这个例子中,我们有一个系统会尝试向客户收取产品或服务的费用。当 ChargeCustomer() 被调用时,它将随后调用 Message Service,向客户发送 SMS 文本消息来通知他们已经被收取的金额。
package mainimport ("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 mainimport ("fmt""testing""github.com/stretchr/testify/mock")// smsServiceMocktype 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 mockfunc 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 TestChargeCustomerMocked charge notification functionValue passed in: 100Charging Customer For the value of 100demo1_test.go:49: PASS: SendChargeNotification(int)--- PASS: TestChargeCustomer (0.00s)PASSok commons/afterShip 0.010s
或者:我们可以换一种灵活些的写法
package mainimport ("fmt""testing""github.com/stretchr/testify/assert""github.com/stretchr/testify/mock")// smsServiceMocktype 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) == 0args := m.Called(value)return nil}// 我们将实现 MessageService 接口// 这就意味着我们不得不改写在接口中定义的所有方法func (m *smsServiceMock) DummyFunc() {fmt.Println("Dummy")}// TestChargeCustomer 是个奇迹发生的地方// 在这里我们将创建 SMSService mockfunc 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 mainimport "fmt"func foo() {fmt.Printf("foo...\n")}func goo() {fmt.Printf("goo...\n")}
测试用例:
package mainimport ("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 TestGooFoo1.SetupSuite() ...=== RUN TestGooFoo/TestFoo2.7.SetupTest()...3.8.BeforeTest: suiteName=_Suite,testName=TestFoofoo...5.10.AferTest: suiteName=_Suite,testName=TestFoo6.11.TearDownTest()...=== RUN TestGooFoo/TestGoo2.7.SetupTest()...3.8.BeforeTest: suiteName=_Suite,testName=TestGoogoo...5.10.AferTest: suiteName=_Suite,testName=TestGoo6.11.TearDownTest()...12.TearDowmnSuite()...--- PASS: TestGooFoo (0.00s)--- PASS: TestGooFoo/TestFoo (0.00s)--- PASS: TestGooFoo/TestGoo (0.00s)PASSok commons/afterShip 0.014s
assert 常见使用
包含关系
// 断言指定的字符串、列表(数组、slice…)或map包含指定的子字符串或元素func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) boolassert.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{}) boolassert.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{}) boolassert.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{}) boolassert.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{}) boolassert.Empty(t, obj)func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) boolassert.Emptyf(t, obj, "error message %s", "formatted")func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) boolfunc Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool
相等关系
// 如果参数是指针,则取决于引用值是否相等func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) boolfunc 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{}) boolfunc 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{}) boolfunc EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool// Exactly 判断value 与 type 相等// assert.Exactly(t, int32(123), int64(123)) 为 falsefunc Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) boolfunc Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool
error断言
func Error(t TestingT, err error, msgAndArgs ...interface{}) boolfunc 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{}) boolfunc 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{}) boolfunc ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool
bool 断言
func False(t TestingT, value bool, msgAndArgs ...interface{}) boolfunc Falsef(t TestingT, value bool, msg string, args ...interface{}) boolfunc True(t TestingT, value bool, msgAndArgs ...interface{}) boolfunc Truef(t TestingT, value bool, msg string, args ...interface{}) bool
数值判断
func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) boolfunc Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) boolfunc GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) boolfunc GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) boolfunc Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) boolfunc Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) boolfunc LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) boolfunc LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) boolfunc Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) boolfunc Negativef(t TestingT, e interface{}, msg string, args ...interface{}) boolfunc Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) boolfunc Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool
is断言
// 集合是否递减func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) boolfunc IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) boolassert.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{}) boolfunc IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) boolassert.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{}) boolfunc IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) boolassert.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{}) boolfunc 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{}) boolfunc Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool
nil判断
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) boolassert.Nil(t, err)func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool
正则
func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) boolfunc Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) boolassert.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{}) boolfunc (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) boola.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) boolfunc YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool
文件相关
func DirExists(t TestingT, path string, msgAndArgs ...interface{}) boolfunc DirExistsf(t TestingT, path string, msg string, args ...interface{}) boolfunc FileExists(t TestingT, path string, msgAndArgs ...interface{}) boolfunc FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
not断言
func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) boolfunc NoError(t TestingT, err error, msgAndArgs ...interface{}) boolfunc NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) boolfunc NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) boolfunc NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) boolfunc NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) boolfunc NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) boolfunc NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) boolfunc NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) boolfunc NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) boolfunc 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
�
