这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
1. 前言
承接上篇笔记,本篇主要介绍有关Go语言项目实战--测试方面的内容。主要涉及到单元测试,Mock测试与基准测试,通过对项目测试的了解,进一步加深对GO语言项目实战的理解与实践。
2. 测试
Go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程是类似的,并不需要学习新的语法、规则或工具。
-
go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。
-
在*_test.go文件中有三种类型的函数,单元测试函数、Mock测试函数和基准测试函数。
3. 单元测试
项目开发完毕一定要做单元测试,保证代码质量
规则
-
所有测试文件以
_test.go结尾- 通过展开目录就可以清晰地分辨项目的源代码和测试代码,方便定位
-
func TestXxx(*testing.T)-
测试函数命名规范
-
IDE中如果命名不规范,会出现提醒标志
-
-
初始化逻辑放到TestMain中
- 入参是
*testing.M,可以在测试之前实现一些数据装载、配置初始化等前置工作都可以在TestMain中执行,是在包维度生效的
- 入参是
测试之后,进行释放资源等收尾工作
assert
实现一些
equal或notEqual的操作
覆盖率
- 如何衡量代码是否经过了足够的测试
- 如何评价项目的测试水准
- 如何评估项目是否达到了高水准测试等级
可以通过代码覆盖率来评估
代码覆盖率在一定程度上表明我们测试用例的覆盖度,越完备表示对代码的正确性就越能表示
Tips
-
一般覆盖率:50% ~·60%;较高覆盖率80%+
-
一定程度上可以保证主流程没问题,但是一场分支可能没有覆盖到
-
对于资金类交易:支付等业务,覆盖率要求更高,要求到80%+
-
从60到80的提升是比较有成本的
-
-
测试分支相互独立、全面覆盖
-
为了更好的提高覆盖率,首先测试分支需要是完备的
-
基于分支能够保证测试的全面覆盖
-
-
测试单元粒度足够小,函数单一职责
- 还需要保证单元测试的粒度足够小,这就需要函数足够小,这与代码设计的单一职责相对应
4. Mock测试
背景
-
项目可能依赖数据库、cache(redis类似的组件)或本地的文件,这些都属于项目的强依赖
-
单测目标:
- 幂等:重复运行测试的case结果与之前一样
- 稳定性:单侧能够相互隔离,单元测试能在任何时间、任何函数独立运行
仅仅写单元测试调用DB、cache,那么它的测试测试可能会因为依赖网络变得不稳定。所以会用到Mock机制,以保证代码的稳定性
文件处理
如果文件被篡改,可能是个测试文件,被别人随便删除了。这样测试在特定场景下就难以运行了
MOCK测试
-
开源的Mock测试包——monkey:github.com/bouk/monkey
-
实现对method以及实例的打桩(打桩:用A函数替换函数 B,B原函数,B是打桩函数)
-
快速Mock函数
- 为一个函数打桩
- 为一个方法打桩
-
以函数打桩为例,有两个方法:
-
Patch
// Patch replaces a function with another func Patch(target, replacement interface{}) *PatchGuard { t := reflect.ValueOf(target) r := reflect.ValueOf(replacement) patchValue(t, r) return &PatchGuard{t, r} }- target:原函数,目标替换的函数
- replacement:需要打桩的函数
-
Unpath打桩结束需要卸载
为了保证测试结束之后,把桩卸载掉
// Unpatch removes any monkey patches on target // returns whether target was patched in the first place func Unpatch(target interface{}) bool { return unpatchValue(reflect.ValueOf(target)) }
-
关于Mock的实现,主要是在运行时通过Go的Unsafe包,将内存当中的地址替换为运行时函数的地址,最终测试时调用的就是打桩函数,一次实现Mock的功能
5. 基准测试
基准测试主要是通过测试 CPU 和内存的效率问题,来评估被测试代码的性能,进而找到更好的解决方案。比如链接池的数量不是越多越好,那么哪个值才是最优值呢,这就需要配合基准测试不断调优了。
有时候通过本地进行性能测试会比较方便
-
优化代码,需要对当前代码分析
- 实际开发中经常会遇到热点代码或代码性能瓶颈问题,为了定位问题,就需要对代码进行性能分析,使用方法类似于单元测试
-
内置的测试框架提供了基准测试的能力
- Go还提供了基准测试框架,以测试一段程序的性能和CPU的损耗。
运行
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
for i := 0; i < b.N; i++ {
Select()
}
}
// 串行
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}
// 并行
func BenchmarkFastSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
FastSelect()
}
})
}
并行的基准测试,其性能有劣化。问题在于,select用到了SDK的random函数,该函数为了保证全局的随机性和并发安全,持有一把全局锁,这样在一定程度长降低了并发性能。在并行情况下,性能减低。
优化
为了解决随机函数的性能问题,Github开源了fastrand函数,性能提升了百倍。但是在高并发场景下,fastrand如果不是也别注意底层实现的话,可能会出现一些性能问题。
后记
本篇笔记是参加「第三届青训营 -后端场」课程后创作,这里介绍了Go语言项目测试的相关内容。其中,单元测试是自己平时项目开放中做的不到位的地方,通过此次课程了学习不仅加什么对项目测试的理解,同时也为自己后续的项目开发增加了经验。