You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
$ go test
setup stuff for tests...
Test1 use stuff setup in TestMain: 2021-07-30 14:59:23.978121 +0800 CST m=+0.000281767
Test2 use stuff setup in TestMain: 2021-07-30 14:59:23.978121 +0800 CST m=+0.000281767
PASS
clear stuff after tests...
ok example.com/hello/adder 0.096s
[golang] 测试
测试文件以
*_test.go
结尾,和被测文件放在一起。其中 adder.go 的内容为:
adder_test.go 的内容为:
可以将测试脚本添加到 makefile 中以方便执行:
运行上面的测试:
通过结果发现不符合预期,修正
addNum
再次测试:运行结果:
$ make test ok example.com/hello/adder
测试函数的名称
关于测试名,如上,一般以
Test*
开头,然后取个能够表示测试功能的名字即可,比如TestDbConnection
;如果是函数的单测,直接Test
加函数名,对于未导出的函数,用下划线连接函数名。测试命令
go test
会运行当前目录下的测试,go test ./...
则运行所有测试,其中./...
表示所有 target,如果用过 bazel 肯定不会陌生。打印错误
除上前面使用过的
t.Error
,*testing.T
上还有其他很多方法可用来打印信息。类似于
fmt.Printf
的t.Errorf
可对字符串进行参数格式化:Error
和Errorf
只是打印错误,测试仍然正常执行。如果想要失败时立即停止执行,可换用与之对应的Fatal
和Fatalf
。但只是停止当前测试中后续逻辑的执行,其他测试用例仍然正常执行不受影响。前置及收尾操作
通常情况下,需要为测试准备一些数据,设置一些变量,同时在测试结束收进行一些清理工作,这样的操作可放在
TestMain
函数中。当
TestMain
函数存在时,运行测试会调用该函数而非直接运行各个测试函数。其中m.Run()
负责调用实际的用例。进入包目录运行
go test
,以下是运行结果:注意对于单个包只能有一个
TestMain
函数。*testing
上还有个Cleanup
方法可用于收尾清理工作,会在每个刚测试用例完成时执行,作用与defer
类似。运行:
测试数据
如果测试过程涉及临时数据,比如文件读写,可以包内创建
testdata
的目录用以存放对应数据。以下是一个示例:
测试结果的缓存
类似编译结果会被缓存,测试通过的用例其结果也会缓存,除非代码有变动才会重跑。可在运行
go test
时添加-count=1
参数来忽略缓存。Table Test
考察如下代码:
如果上测试上面的函数,需要涵盖其中的每个分支,每个用例中都会包含变量初化,返回值检查等冗余逻辑。
此时可声明一个结构体包含每个用例的名称,测试时需要的数据和其他信息,在一个循环中进行测试以减少冗余代码。
循环体中通过调用
t.Run()
执行的测试。通过go test -v
可看到测试详情,包含上面指定的用例名称:$ go test -v === RUN TestDoMath === RUN TestDoMath/addition === RUN TestDoMath/subtraction === RUN TestDoMath/multiplication === RUN TestDoMath/division === RUN TestDoMath/bad_division --- PASS: TestDoMath (0.00s) --- PASS: TestDoMath/addition (0.00s) --- PASS: TestDoMath/subtraction (0.00s) --- PASS: TestDoMath/multiplication (0.00s) --- PASS: TestDoMath/division (0.00s) --- PASS: TestDoMath/bad_division (0.00s) PASS ok example.com/hello/do_math 0.090s
代码覆盖率
覆盖率反映用例对代码的覆盖情况,可作为测试编写是否全面的参考,但 100% 的覆盖率不代码代码就没 bug,会有其他输入输出未体现在用例中但被覆盖的情况。
通过
-cover
开启覆盖率的计算,-coverprofile
将结果输出到文件。$ go test -cover -coverprofile c.out
go tool 还提供了将结果输出成 html 形式的功能:
html 文件中可直观看出哪些代码是未覆盖的:
通过上面的输出可看到我们未处理
default
分支,修正我们的代码添加一种求知的操作类型:重新运行测试后查看覆盖率,此时已经完全覆盖到了。
$ go test -cover -coverprofile=c.out PASS coverage: 100.0% of statements ok example.com/hello/do_math 0.308s
Benchmark
基准测试用于衡量程序的性能。请看以下计算计算文件中字符数的函数:
进行基准测试前需要确定功能符合预期,所以先编写一个用例测试功能:
基准测试也是放在测试文件中的,区别与功能测试,它以
Benchmark
开头:任何基准测试都包含一个从 0 到
b.N
次的循环,循环体中进行执行被测试的对象。N 动态调整直到得到一可准确结果为止。go test -bench=<regexp>
指定正则以匹配需要执行的基准测试,或go test -bench=.
执行所有。-benchmem
则会在结果中包含内存分配信息。下面运行以上准备好的基准测试:带内存信息的结果中包含 5 列,它们分别是:
BenchmarkFilelen-12
本次基准测试的名称27146
运行数次47007 ns/op
完成单次测试需要的时间,单位为纳秒(1/1,000,000,000s)167 B/op
单次测试中分配的字节数42 allocs/op
字节分配次数使用 table test,这里控制下入参,进行批量测试观测结果:
运行结果:
可以看出,当 buffer 增大时,内存分配次数减少,性能有所提升,直到 buffer 大于文件大小,内存分配的损耗开始增加,当前文件大小下,buffer 设置为 100 是最优的。
Stubs
以上测试未涉及外部依赖,但真实场景下,会依赖很多接口。所以在测试时,需要 Mock 这些依赖,此时即可为这些依赖编写 Stub。
考察如下的结构体,其依赖一个
Entities
接口。Entities
这个接口上可能有很多方法,但此结构体中只用到了GetPets
这一个方法。为了测试Logic
的GetPetNames
,我们需要先实现Entities
接口,但不必完整实现,只实现用到的那个方法即可。方法是将接口放到结构体中:{% raw %}
{% endraw %}
上面的方法只适用于单个或小范围测试中,如果存在大量测试用例都需要使用该 Stub,不同用例中输入输出都不一样,这样的话,要么在 Stub 中将所有情形包含,要么各个用例自己实现 Stub,不管哪种都不太好维护。
这种情况下,可以构造这么一个 Stub 结构体,它包含的方法字段与接口所需方法一一对应,在进行方法调时,用代理到结构体的方法字段上,这样每个用例在使用时,提供不同的方法实现即可。
展开来讲。
还是上面的例子,假设接口包含这么三个方法,我们构造如下结构体:
然后为结构体定义方法,与接口方法一一对应:
这样,不同用例在使用时,只需要提供不同实现即可,然后我们可以很方便地进行 Table Test:
网络测试
真实场景涉及网络请求会比较常见。通过 Go 提供的
net/http/httptest
这些包可完成网络的测试。下面看个示例,
下面通过
httptest
来测试上面的方法,而不用真实请求到服务器。首先定义一个结构体保存请求的入参和结果:
接下来设置 mock server 接收请求:
以上。
The text was updated successfully, but these errors were encountered: