testify 是单元测试中较为常用的包
文档:https://pkg.go.dev/github.com/stretchr/testify

assert

  1. package main
  2. import (
  3. "testing"
  4. "github.com/stretchr/testify/assert"
  5. )
  6. func TestSomething(t *testing.T) {
  7. // assert equality
  8. assert.Equal(t, 123, 123, "they should be equal")
  9. // assert inequality
  10. assert.NotEqual(t, 123, 456, "they should not be equal")
  11. // assert for nil (good for errors)
  12. assert.Nil(t, object)
  13. // assert for not nil (good when you expect something)
  14. if assert.NotNil(t, object) {
  15. // now we know that object isn't nil, we are safe to make
  16. // further assertions without causing any errors
  17. assert.Equal(t, "Something", object.Value)
  18. }
  19. }

“t” 一直要做第一个参数,你可能觉得很烦,这里也提供了另一种写法

  1. package main
  2. import (
  3. "testing"
  4. "github.com/stretchr/testify/assert"
  5. )
  6. func TestSomething(t *testing.T) {
  7. assert := assert.New(t)
  8. // assert equality
  9. assert.Equal(123, 1213, "they should be equal")
  10. // assert inequality
  11. assert.NotEqual(123, 456, "they should not be equal")
  12. }

require

与assert 用法相同,只是如果失败,调用 t.FailNow()终止测试用例,而assert 调用的是t.Fail() 程序接着走

  1. package main
  2. import (
  3. "testing"
  4. "github.com/stretchr/testify/require"
  5. )
  6. func TestSomething(t *testing.T) {
  7. // assert equality
  8. require.Equal(t, 123, 1123, "they should be equal")
  9. // assert inequality
  10. require.NotEqual(t, 123, 456, "they should not be equal")
  11. }

mock

在编写测试代码时可以使用模拟对象代替实际对象

在这个例子中,我们有一个系统会尝试向客户收取产品或服务的费用。当 ChargeCustomer() 被调用时,它将随后调用 Message Service,向客户发送 SMS 文本消息来通知他们已经被收取的金额。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. // MessageService 通知客户被收取的费用
  6. type MessageService interface {
  7. SendChargeNotification(int) error
  8. }
  9. // SMSService 是 MessageService 的实现
  10. type SMSService struct{}
  11. // MyService 使用 MessageService 来通知客户
  12. type MyService struct {
  13. messageService MessageService
  14. }
  15. // SendChargeNotification 通过 SMS 来告知客户他们被收取费用
  16. // 这就是我们将要模拟的方法
  17. func (sms SMSService) SendChargeNotification(value int) error {
  18. fmt.Println("Sending Production Charge Notification")
  19. return nil
  20. }
  21. // ChargeCustomer 向客户收取费用
  22. // 在真实系统中,我们会模拟这个
  23. // 但是在这里,我想在每次运行测试时都赚点钱
  24. func (a MyService) ChargeCustomer(value int) error {
  25. a.messageService.SendChargeNotification(value)
  26. fmt.Printf("Charging Customer For the value of %d\n", value)
  27. return nil
  28. }
  29. // 一个 "Production" 例子
  30. func main() {
  31. fmt.Println("Hello World")
  32. smsService := SMSService{}
  33. myService := MyService{smsService}
  34. myService.ChargeCustomer(100)
  35. }

那么,我们如何进行测试以确保我们不会让客户疯掉?好吧,我们通过创建一个新的 struct 称之为 smsServiceMock ,用来模拟我们的 SMSService,并且将 mock.Mock 添加到它的字段列表中。

然后我们将改写 SendChargeNotification 方法,这样它就不会向我们的客户发送通知并返回 nil 错误。
最后,我们创建 TestChargeCustomer 测试函数,接着实例化一个新的类型实例 smsServiceMock 并指定 SendChargeNotification 在被调用时应该做什么。

  1. package main
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/stretchr/testify/mock"
  6. )
  7. // smsServiceMock
  8. type smsServiceMock struct {
  9. mock.Mock
  10. }
  11. // 我们模拟的 smsService 方法
  12. func (m *smsServiceMock) SendChargeNotification(value int) error {
  13. fmt.Println("Mocked charge notification function")
  14. fmt.Printf("Value passed in: %d\n", value)
  15. // Called告诉模拟对象某个方法已被调用,并返回return中设置的参数
  16. args := m.Called(value)
  17. // 它将返回任何我需要返回的
  18. // 这种情况下模拟一个 SMS Service Notification 被发送出去
  19. return args.Error(0) // args.Get(0).(error) 的简写
  20. }
  21. // 我们将实现 MessageService 接口
  22. // 这就意味着我们不得不改写在接口中定义的所有方法
  23. func (m *smsServiceMock) DummyFunc() {
  24. fmt.Println("Dummy")
  25. }
  26. // TestChargeCustomer 是个奇迹发生的地方
  27. // 在这里我们将创建 SMSService mock
  28. func TestChargeCustomer(t *testing.T) {
  29. smsService := new(smsServiceMock)
  30. // 然后我们将定义当 100 传递给 SendChargeNotification 时,需要返回什么
  31. // 在这里,我们希望它在成功发送通知后返回 nil(Return 设置预期返回值)
  32. // run 是 Called里调用的,只能拿到SendChargeNotification的入参
  33. smsService.On("SendChargeNotification", 100).Return(nil).Run(func(args mock.Arguments) {
  34. // run 是在SendChargeNotification 中 调用 Called 后执行的
  35. // 这里的args 是Called里的入参
  36. fmt.Println("+++++++++")
  37. })
  38. // 接下来,我们要定义要测试的服务
  39. myService := MyService{smsService}
  40. // 然后调用方法
  41. err := myService.ChargeCustomer(100)
  42. // 最后,我们验证 myService.ChargeCustomer 调用了我们模拟的 SendChargeNotification 方法
  43. smsService.AssertExpectations(t)
  44. }

执行该用例

  1. mac@weideMacBook-Pro afterShip % go test -v -run TestChargeCustomer -short
  2. === RUN TestChargeCustomer
  3. Mocked charge notification function
  4. Value passed in: 100
  5. Charging Customer For the value of 100
  6. demo1_test.go:49: PASS: SendChargeNotification(int)
  7. --- PASS: TestChargeCustomer (0.00s)
  8. PASS
  9. ok commons/afterShip 0.010s

或者:我们可以换一种灵活些的写法

  1. package main
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/mock"
  7. )
  8. // smsServiceMock
  9. type smsServiceMock struct {
  10. mock.Mock
  11. }
  12. // 我们模拟的 smsService 方法
  13. func (m *smsServiceMock) SendChargeNotification(value int) error {
  14. fmt.Println("Mocked charge notification function")
  15. fmt.Printf("Value passed in: %d\n", value)
  16. // 因为我们没设置return,所以len(args) == 0
  17. args := m.Called(value)
  18. return nil
  19. }
  20. // 我们将实现 MessageService 接口
  21. // 这就意味着我们不得不改写在接口中定义的所有方法
  22. func (m *smsServiceMock) DummyFunc() {
  23. fmt.Println("Dummy")
  24. }
  25. // TestChargeCustomer 是个奇迹发生的地方
  26. // 在这里我们将创建 SMSService mock
  27. func TestChargeCustomer(t *testing.T) {
  28. smsService := new(smsServiceMock)
  29. // 不确定 传入的参数,同时也不指定返回值
  30. // 注意:mock.Anything 定义返回值是不被允许的,因为它无法转换为真是的返回值类型
  31. smsService.On("SendChargeNotification", mock.Anything)
  32. // 接下来,我们要定义要测试的服务
  33. myService := MyService{smsService}
  34. // 然后调用方法
  35. err := myService.ChargeCustomer(100)
  36. assert.Nil(t, err)
  37. }

suite

suite 能很方便的提供测试执行流,比如:整个套件在执行前要干什么、单个测试用例执行前要干什么

比如:我们要两个方法需要测试

  1. package main
  2. import "fmt"
  3. func foo() {
  4. fmt.Printf("foo...\n")
  5. }
  6. func goo() {
  7. fmt.Printf("goo...\n")
  8. }

测试用例:

  1. package main
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/stretchr/testify/suite"
  6. )
  7. type _Suite struct {
  8. suite.Suite
  9. }
  10. // SetupSuite() 和 TearDownSuite() 仅执行一次
  11. // SetupTest() TearDownTest() BeforeTest() AfterTest() 对套件中的每个测试执行一次
  12. func (s *_Suite) AfterTest(suiteName, testName string) {
  13. fmt.Printf("5.10.AferTest: suiteName=%s,testName=%s\n", suiteName, testName)
  14. }
  15. func (s *_Suite) BeforeTest(suiteName, testName string) {
  16. fmt.Printf("3.8.BeforeTest: suiteName=%s,testName=%s\n", suiteName, testName)
  17. }
  18. // SetupSuite() 仅执行一次
  19. func (s *_Suite) SetupSuite() {
  20. fmt.Printf("1.SetupSuite() ...\n")
  21. }
  22. // TearDownSuite() 仅执行一次
  23. func (s *_Suite) TearDownSuite() {
  24. fmt.Printf("12.TearDowmnSuite()...\n")
  25. }
  26. func (s *_Suite) SetupTest() {
  27. fmt.Printf("2.7.SetupTest()... \n")
  28. }
  29. func (s *_Suite) TearDownTest() {
  30. fmt.Printf("6.11.TearDownTest()... \n")
  31. }
  32. func (s *_Suite) TestFoo() {
  33. foo() // 4.
  34. }
  35. func (s *_Suite) TestGoo() {
  36. goo() //9.
  37. }
  38. // 让 go test 执行测试
  39. func TestGooFoo(t *testing.T) {
  40. suite.Run(t, new(_Suite))
  41. }

执行该用例:

  1. mac@weideMacBook-Pro afterShip % go test -v -run TestGooFoo
  2. === RUN TestGooFoo
  3. 1.SetupSuite() ...
  4. === RUN TestGooFoo/TestFoo
  5. 2.7.SetupTest()...
  6. 3.8.BeforeTest: suiteName=_Suite,testName=TestFoo
  7. foo...
  8. 5.10.AferTest: suiteName=_Suite,testName=TestFoo
  9. 6.11.TearDownTest()...
  10. === RUN TestGooFoo/TestGoo
  11. 2.7.SetupTest()...
  12. 3.8.BeforeTest: suiteName=_Suite,testName=TestGoo
  13. goo...
  14. 5.10.AferTest: suiteName=_Suite,testName=TestGoo
  15. 6.11.TearDownTest()...
  16. 12.TearDowmnSuite()...
  17. --- PASS: TestGooFoo (0.00s)
  18. --- PASS: TestGooFoo/TestFoo (0.00s)
  19. --- PASS: TestGooFoo/TestGoo (0.00s)
  20. PASS
  21. ok commons/afterShip 0.014s

assert 常见使用

包含关系

  1. // 断言指定的字符串、列表(数组、slice…)或map包含指定的子字符串或元素
  2. func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
  3. assert.Contains(t, "Hello World", "World")
  4. assert.Contains(t, ["Hello", "World"], "World")
  5. assert.Contains(t, {"Hello": "World"}, "Hello")
  6. func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool
  7. assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
  8. assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
  9. assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")

元素匹配

  1. // ElementsMatch断言指定的listA(array, slice…)等于指定的listB(array, slice…),忽略元素的顺序。
  2. // 如果存在重复的元素,则它们在两个列表中出现的次数应该匹配。
  3. func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool)
  4. assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2])
  5. func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool
  6. assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")

元素包含

  1. func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool)
  2. func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool
  3. assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")

空值校验

  1. // Empty断言指定的对象为空。即nil, "", false, 0或一个切片或一个带有len == 0的通道。
  2. func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  3. assert.Empty(t, obj)
  4. func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool
  5. assert.Emptyf(t, obj, "error message %s", "formatted")
  6. func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool
  7. func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool

相等关系

  1. // 如果参数是指针,则取决于引用值是否相等
  2. func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
  3. func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool
  4. // 比较error
  5. // actualObj, err := SomeFunction()
  6. // assert.EqualError(t, err, expectedErrorString)
  7. func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool
  8. func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool
  9. // EqualValues断言两个对象相等或可转换为相同类型和相等
  10. // assert.EqualValues(t, uint32(123), int32(123))
  11. func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
  12. func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool
  13. // Exactly 判断value 与 type 相等
  14. // assert.Exactly(t, int32(123), int64(123)) 为 false
  15. func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
  16. func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool

error断言

  1. func Error(t TestingT, err error, msgAndArgs ...interface{}) bool
  2. func Errorf(t TestingT, err error, msg string, args ...interface{}) bool
  3. // ErrorAs断言err链中至少有一个错误与target匹配,是error.As的封装
  4. func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool
  5. func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool
  6. // ErrorAs断言err链中至少有一个错误与target匹配,是error.Is的封装
  7. func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool
  8. func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool

bool 断言

  1. func False(t TestingT, value bool, msgAndArgs ...interface{}) bool
  2. func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool
  3. func True(t TestingT, value bool, msgAndArgs ...interface{}) bool
  4. func Truef(t TestingT, value bool, msg string, args ...interface{}) bool

数值判断

  1. func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
  2. func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
  3. func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
  4. func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
  5. func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
  6. func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
  7. func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool
  8. func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool
  9. func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool
  10. func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool
  11. func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool
  12. func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool

is断言

  1. // 集合是否递减
  2. func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  3. func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
  4. assert.IsDecreasing(t, []int{2, 1, 0})
  5. assert.IsDecreasing(t, []float{2, 1})
  6. assert.IsDecreasing(t, []string{"b", "a"})
  7. // 集合是否递增
  8. func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  9. func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
  10. assert.IsIncreasing(t, []int{1, 2, 3})
  11. assert.IsIncreasing(t, []float{1, 2})
  12. assert.IsIncreasing(t, []string{"a", "b"})
  13. func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  14. func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
  15. assert.IsNonDecreasing(t, []int{1, 1, 2})
  16. assert.IsNonDecreasing(t, []float{1, 2})
  17. assert.IsNonDecreasing(t, []string{"a", "b"})
  18. func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  19. func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool
  20. // 类型是否一致
  21. func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool

长度判断

  1. func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool
  2. func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool

nil判断

  1. func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  2. assert.Nil(t, err)
  3. func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool

正则

  1. func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool
  2. func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool
  3. assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
  4. assert.Regexp(t, "start...$", "it's not starting")

数据格式相关

  1. func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool
  2. func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool
  3. a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
  4. func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool
  5. func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool

文件相关

  1. func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool
  2. func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool
  3. func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool
  4. func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool

not断言

  1. func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) bool
  2. func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool
  3. func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) bool
  4. func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
  5. func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  6. func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
  7. func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
  8. func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool
  9. func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool
  10. func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool
  11. func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool)
  12. func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool

Delta

  1. // 判断两个数彼此在 delta 之内
  2. func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool