Contents

Go 测试框架 stretchr/testify

Warning
本文最后更新于 April 29, 2021,文中内容可能已过时,请谨慎使用。

golang的测试框架stretchr/testify 简单的API去检验你的GoLang代码按照你的意愿运行。

项目地址: https://github.com/stretchr/testify

特性:

安装

使用 go get 安装

1
$ go get github.com/stretchr/testify

安装后获得以下包

1
2
3
4
5
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
github.com/stretchr/testify/mock
github.com/stretchr/testify/suite
github.com/stretchr/testify/http (deprecated

使用

  • assert package
  • require package
  • mock package
  • suite package

**这里面最常用的是前两个 package,他们的唯一差别就是require的函数会直接导致case结束,而assert虽然也标记为case失败,但case不会退出,而是继续往下执行 **

assert package

assert 包提供了一些有用的方法,允许您在Go中编写更好的测试代码。

  • 友好输出,更详细的问题描述
  • 代码可读性强
  • 可以选择用消息注释断言
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package test

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestCase1(t *testing.T) {
	name := "Bob"
	age := 10

	assert.Equal(t, "Bob", name)
	assert.Equal(t, 20, age,"年龄不相等")
}

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
=== RUN   TestCase1
    TestCase1: abc_test.go:18: 
        	Error Trace:	abc_test.go:18
        	Error:      	Not equal: 
        	            	expected: 20
        	            	actual  : 10
        	Test:       	TestCase1
        	Messages:   	年龄不相等
--- FAIL: TestCase1 (0.00s)


Expected :20
Actual   :10
<Click to see difference>


FAIL

Process finished with exit code 1

require package

require包提供与assert包相同的全局函数,但它们不会返回布尔结果,而是终止当前测试。

mock package

mock包提供了一种机制,可以方便地编写mock对象,在编写测试代码时可以用它代替真实对象。

如下一个示例测试函数,它测试依赖外部对象testObj的一段代码,可以设置期望(证明)并断言它们确实发生了。

例如,一个是消息服务或电子邮件服务,无论何时被调用,都会向客户端发送电子邮件。如果我们正在积极地开发我们的代码库,可能每天会运行数百次测试,但我们不希望每天向客户发送数百封电子邮件或消息,因为那样他们可能会不高兴。

那么,我们要如何使用 testify 包来模拟呢?

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 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 在被调用时应该做什么。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 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)
	// 这将记录方法被调用以及被调用时传进来的参数值
	args := m.Called(value)
	// 它将返回任何我需要返回的
	// 这种情况下模拟一个 SMS Service Notification 被发送出去
	args.Bool(0)
	return nil
}

// TestChargeCustomer 是个奇迹发生的地方
// 在这里我们将创建 SMSService mock
func TestChargeCustomer(t *testing.T) {
	var smsService smsServiceMock

	// 然后我们将定义当 100 传递给 SendChargeNotification 时,需要返回什么
	// 在这里,我们希望它在成功发送通知后返回 true
	smsService.On("SendChargeNotification", 100).Return(true)

	// 接下来,我们要定义要测试的服务
	myService := MyService{smsService}
	// 然后调用方法
	myService.ChargeCustomer(100)

	// 最后,我们验证 myService.ChargeCustomer 调用了我们模拟的 SendChargeNotification 方法
	smsService.AssertExpectations(t)
}

所以,当我们运行 go test ./... -v 时,我们应该看到以下输出:

1
2
3
4
5
6
7
8
9
go test ./... -v
=== RUN   TestChargeCustomer
Mocked charge notification function
Value passed in: 100
Charging Customer For the value of 100
--- PASS: TestChargeCustomer (0.00s)
    main_test.go:33: PASS:      SendChargeNotification(int)
PASS
ok      _/Users/elliot/Documents/Projects/tutorials/golang/go-testify-tutorial  0.012s

这证明我们的 myService.ChargeCustomer() 方法按照我们所期望的方式在运行!

我们现在已经能够使用模拟的方法来全面测试更复杂的项目。值得注意的是,此技术可用于各种不同的系统,例如模拟数据库查询或者是如何与其他 API 交互。总的来说,模拟是非常强大的手段

suite package

这个包不太常用