前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go 单元测试从 0 到 1

Go 单元测试从 0 到 1

作者头像
恋喵大鲤鱼
发布2021-07-23 16:09:12
6450
发布2021-07-23 16:09:12
举报
文章被收录于专栏:C/C++基础C/C++基础

1.什么是单元测试

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。

对于单元测试中单元的含义,一般要根据实际情况去判定其具体含义,如 C 语言中单元指一个函数,Java 里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小被测功能模块。

在 Go 中,一般指对函数的单元测试。

2.单元测试的作用

单元测试可以检查我们的代码能否按照预期执行,来提升代码质量。

通过单元测试,我们可以设置多个测试用例,执行要测试的函数,判断是否符合预期。尽可能达保证函数功能没有问题,或者出现我们预知的错误。一次书写测试用例,随着代码一起永久保留,来验证函数功能,这就是单元测试的好处。

3.Go 如何写单元测试

Go 本身对自动化测试非常友好,并且有许多优秀的测试框架支持,非常好上手。

首先看一下 Go 官方的 testing 包。

要编写一个测试文件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx 函数。将该文件放在与被测试文件相同的包中,该文件将被排除在正常的程序包之外,但在运行 go test 命令时将被包含。

测试函数的签名必须接收一个指向 testing.T 类型的指针,并且不能返回任何值,函数名必须以 Test 开头,建议后跟要测试的函数名。

记得一定要先看一下 Package testing 的官方文档!

下面利用 Go 官方 testing 包给出一个示例。

代码目录结构:

代码语言:javascript
复制
gotest
	- go.mod
	- go.sum
	- main.go
	- hello
		- hello.go
		- hello_test.go

main.go:

代码语言:javascript
复制
package main

import (
	"main/hello"
)

func main() {
	fmt.Println(hello.Hello())
}

hello.go:

代码语言:javascript
复制
package hello

func Hello() string {
	return "Hello world"
}

我们新建一个单测文件 hello_test.go,为函数 Hello() 添加单元测试。

hello_test.go:

代码语言:javascript
复制
package hello

import "testing"

func TestHello(t *testing.T) {
	got := Hello()
	want := "Hello world"
	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}

进入 hello 目录执行命令go test,或指定目录 go test ./hello,所有以_test.go结尾的源码文件内以 Test 开头的函数会自动被执行。

输出:

代码语言:javascript
复制
PASS
ok      main/hello      0.173s

我们也可以加上-v选项,输出单测执行的详细过程:

代码语言:javascript
复制
go test -v
=== RUN   TestHello
--- PASS: TestHello (0.00s)
PASS
ok      main/hello      0.176s

该结果,表示单测通过了,返回的值与我们预期的值是相同的。

现在尝试把预期结果修改一下:

代码语言:javascript
复制
want := "Hello fuck"

测试结果:

代码语言:javascript
复制
D:\code\gotest\hello>go test -v
=== RUN   TestHello
    hello_test.go:9: got "Hello world" want "Hello fuck"
--- FAIL: TestHello (0.00s)
FAIL
exit status 1
FAIL    main/hello      0.127s

此时提示测试不通过,得到的值与预期的值不相同。

至此,利用 Go 官方包 testing 便完成了一个简单的单元测试,我们可以进行正确或错误的测试。

4.go test 命令参数

go test 是 Go 用来执行单元测试地命令,常用的选项有:

代码语言:javascript
复制
-bench regexp 执行相应的 benchmarks,例如 -bench=.;
-cover 开启测试覆盖率;
-run regexp 只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数;
-v 显示测试的详细命令。

详见官方文档Testing flags

现在再添加一个被测试函数。

代码语言:javascript
复制
func Add(a, b int) int {
	return a + b
}

测试代码:

代码语言:javascript
复制
func TestAdd(t *testing.T) {
	sum := Add(5, 5)
	if sum == 10 {
		t.Log("the result is ok")
	} else {
		t.Fatal("the result is wrong")
	}
}

使用 -run 来测试,发现只运行了 TestAdd 测试方法。

代码语言:javascript
复制
D:\code\gotest\hello>go test -v -run TestAdd
=== RUN   TestAdd
    hello_test.go:16: the result is ok
--- PASS: TestAdd (0.00s)
PASS
ok      main/hello      0.170s

5.快速生成单测代码

实际上,不同函数的单测代码虽然逻辑不同,但结构是一样的,长得非常相似,因此重复的代码可以使用工具来生成,不用手动繁琐地重复书写。

常用的 IDE,比如 GoLand 或 VSCode,都自带了生成单元测试代码的工具,以 GoLand 为例,可以快速为函数、文件或包生成测试代码。在源码文件中”右键函数名 > Generate… > Test for function“ 便可以快速生成对应函数的单测代码模板,然后我们在生成的模板代码中添加具体的测试用例即可。

使用该方法,为上面的 Hello() 函数生成的测试代码如下:

代码语言:javascript
复制
func TestHello(t *testing.T) {
	tests := []struct {
		name string
		want string
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Hello(); got != tt.want {
				t.Errorf("Hello() = %v, want %v", got, tt.want)
			}
		})
	}
}

我们在注释处添加测试用例即可,非常快捷方便。

6.看看单元测试覆盖率

写好测试后,可以利用 Go 自带的工具 test coverage 查看一下单元测试覆盖率。

测试覆盖率是一个术语,用于统计通过运行程序包的测试多少代码得到执行。 如果执行测试函数导致 80%的语句得到了运行,则测试覆盖率为 80%。

我们来试一下。

代码语言:javascript
复制
D:\code\gotest>go test -v -cover ./hello
=== RUN   TestHello
--- PASS: TestHello (0.00s)
=== RUN   TestAdd
    hello_test.go:16: the result is ok
--- PASS: TestAdd (0.00s)
PASS
coverage: 100.0% of statements
ok      main/hello      0.154s  coverage: 100.0% of statements

可以看到,目录 hello 下的所有单测都通过了,且报告覆盖率为 100%.

7.使用单测框架写单测

学会使用 Go 官方 testing 包写单元测试是远远不够的,因为实际项目开发中,面对复杂的逻辑判断,繁多的测试用例,网络IO调用等,都加大了单测编写与管理的难度,此时我们需要用到更好的测试框架来增强测试编写。

这里推荐使用 Testify + Gomonkey 开源库来完成 Go 的单元测试的书写。

Testify 主要提供测试套件和断言的能力,不过也提供了 mock 的功能,但我们不使用,因为有更好用的 mock 库。

Gomonkey 主要用于提供 mock 能力。

说到 mock,其本意是模拟,就是对一些不想执行的函数,比如有网络IO或对DB有写入的函数,因为测试环境网络不通或不想执行单测而向DB写入数据,都可以将其 mock 住,写一个替代函数。执行单测的时候会调用这个替代函数,相当于替代函数模拟了原函数。

下面使用 Testify + Gomonkey 给出使用示例。

先改造一下 Hello() 和 Add() 函数。

代码语言:javascript
复制
// Hello 返回 Hello world 和写入 DB 的值
func Hello(a, b int) string {
	return fmt.Sprintf("Hello world and %v", Add(a, b))
}

// Add 将 a b 和写入 DB 并返回
func Add(a, b int) int {
	sum := a + b
	// 省略的 DB 操作
	return sum
}

我们为 Hello() 函数添加单元测试并 mock 住 Add() 函数。

代码语言:javascript
复制
package hello

import (
	"testing"

	"github.com/agiledragon/gomonkey"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

// TestSuiteHello 测试套件
type TestSuiteHello struct {
	suite.Suite
	patches []*gomonkey.Patches
}

// SetupTest 测试套件初始化
func (suite *TestSuiteHello) SetupTest() {
	// mock Add 函数
	p := gomonkey.ApplyFunc(Add, func(a, b int) int {
		// 不执行DB写入,只返回加和结果
		return a + b
	})
	suite.patches = append(suite.patches, p)
}

// TearDownSuite 测试套件析构
func (suite *TestSuiteHello) TearDownSuite() {
	for _, p := range suite.patches {
		p.Reset()
	}
}

// TestHelloSuite 启动测试套件
func TestHelloSuite(t *testing.T) {
	suite.Run(t, new(TestSuiteHello))
}

// TestHello hello 函数单测
func (suite *TestSuiteHello) TestHello() {
	type args struct {
		a int
		b int
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{"testcase1", args{1, 2}, "Hello world and 3"},
		{"testcase2", args{3, 4}, "Hello world and 7"},
		{"testcase3", args{5, 6}, "Hello world and 11"},
	}
	for _, t := range tests {
		res := Hello(t.args.a, t.args.b)
		assert.Equal(suite.T(), t.want, res, t.name)
	}
}

自定义的测试套件 TestSuiteHello,可以添加多个以 Test 开头的单测,将被一一执行。

执行单元测试:

代码语言:javascript
复制
D:\code\gotest>go test -v ./hello
=== RUN   TestHelloSuite
=== RUN   TestHelloSuite/TestHello
--- PASS: TestHelloSuite (0.00s)
    --- PASS: TestHelloSuite/TestHello (0.00s)
PASS
ok      main/hello      0.149s

测试通过。

8.小结

关于单元测试,本文从 0 到 1 讲解了 Go 如何编写测试用例,熟练掌握 Golang 中单元测试的书写是一位合格 gopher 的必备技能。

推荐使用 testify + gomonkey 测试框架编写 Go 的单测,关于其他的单测框架,比如 goconvey + gomock,感兴趣的你可自行了解。


参考文献

[1] GoLang快速上手单元测试(思想、框架、实践) [2] golang-单元测试和mock框架的介绍和推荐 [3] gomonkey 1.0 正式发布! [4] Testify - Thou Shalt Write Tests

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-07-20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.什么是单元测试
  • 2.单元测试的作用
  • 3.Go 如何写单元测试
  • 4.go test 命令参数
  • 5.快速生成单测代码
  • 6.看看单元测试覆盖率
  • 7.使用单测框架写单测
  • 8.小结
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档