测试
测试方法:
- 回归测试:手动对主流场景进行测试
- 集成测试:对系统的一个功能模块进行验证,检查是否能系统中的其它模块一起正常工作
- 单元测试:对单独的函数模块进行验证,检查是否能对指定输入做出正确的输出
从上至下,覆盖率逐层增加,成本逐层降低。
单元测试
- 所有测试文件以
_test.go结尾 - 对函数
Xxx的单元测试声明为func TestXxx(t *testing.T) - 一个测试文件的初始化/收尾工作写在
func TestMain(m *testing.M)函数中
-
- 调用
m.Run()执行所有测试
- 调用
*testing.T对象方法:
Errorf(fstring, ...)在标准错误流中打印一个格式化字符串
也可以使用github.com/stretchr/testify/assert包中的assert.Equal(t, expectedOutput, output)简化错误打印代码。
设计单元测试
- 测试分支相互独立、全面覆盖
- 测试单元力度足够小
- 被测试的函数职责单一
运行单元测试
运行项目中所有单元测试的命令:go test [flags] [packages]
测试覆盖率
测试覆盖率用于衡量代码是否经过了完备的测试,更高的测试覆盖率通常可以保证更好的代码正确性。
- 使用
go test XX_test.go XX.go --cover获取XX.go中代码的测试覆盖率 - 实际项目中代码覆盖率一般为50%-60%
单元测试中的依赖
被测试的代码单元可能需要外部依赖,例如:文件、数据库、缓存
单元测试的设计目标:
- 稳定:任何单元测试能够在任何时间独立运行
- 幂等:重复运行一个测试的结果总是相同
举例:文件依赖
假设我们需要对一个读取文件的函数进行测试,这个函数通过一个固定的路径访问本地文件,读取文本并返回。
这个文件可能被删除或修改,从而导致测试结果不稳定。
Mock
为保证单元测试的稳定性与幂等性,可以使用Mock,即使用另一个函数替换被测试代码中的一个函数,模拟对特定依赖的访问。
- 这里使用
github.com/bouk/monkey进行Mock。
在单元测试开始时,使用monkey.Patch(<func>, <replacement_func>)将所有对函数<func>的调用替换为对<replacement_func>的调用,这个操作被称为打桩。
在单元测试结束时,使用monkey.Unpatch(<func>)解除对函数<func>调用的替换。
- 可以使用
defer monkey.Unpatch(<func>)
基准测试
基准测试方法与单元测试类似,但主要目的是分析代码单元的性能。
- 基准测试函数命名为
BenchmarkXXX - 入参:
b *testing.B
计时:
- 计时默认从进入函数开始
b.ResetTimer()重置计时器,用于跳过对准备数据的代码的计时- 计时在函数结束时结束
并行测试:
- 测试多线程并行时函数的性能
b.RunParallel(func(pb *testing.PB){
for pb.Next(){
// Call the function being tested
}
})
例如:rand()为了保证全局随机性,设计为线程安全的操作,这使得多个线程调用rand()时需要获取线程锁,造成性能下降。