本文是青训营课程- 《Go语言工程实践之测试》的笔记
回归测试:负责QA(质量保证)的同学手动用APP测试
集成测试:自动化的测试功能
单元测试:测试开发阶段,在一定程度上决定着代码的质量
1 单测(单元测试)
1.1 规则
- 所有测试文件都以
_test.go结尾
- 测试函数命名规范:
func TestXxx(*testing.T)
- 初始化逻辑放到
TestMain中
例子:
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectedOutput := "Tom"
if output != expectedOutput {
t.Errorf("Test failed, expected: '%s', got: '%s'", expectedOutput, output)
}
}
除了用!=判断输出与预期是否相符以外,还可以使用testify的assert包
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectedOutput := "Tom"
assert.Equal(t, expectedOutput, output)
}
这个用起来好像更爽一点。
1.2 单测的覆盖率
如何衡量代码是否经过了足够的测试? 如何评价项目的测试水准? 如何评估项目是否达到了高水准测试等级? 一个指标就能回答三个问题:代码覆盖率。 来个例子, 实现源代码文件如下:
func JudgePassLine(score int16) bool {
if score >= 60 {
return true
} else {
return false
}
}
测试函数如下:
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestJudgePassLine(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
使用go test执行测试代码时,加上--cover选项可以看到测试覆盖率的输出。
go test .\main_test.go .\main.go --cover
ok command-line-arguments 0.249s coverage: 66.7% of statements
为什么是66.7%呢?因为源代码中score<60的分支没有被测试。
如果把这部分也考虑到,那就会实现100%的单测覆盖率。
但是在实际项目中,100%的覆盖率是可望不可即的,一般的覆盖率在,超过
的就属于比较高的(设计支付、资金的部分一般要求更高)
单测设计要求:测试分支相互独立、全面覆盖。应保证单元粒度足够小,函数单一职责。
2 Mock测试
2.3 依赖
项目的一些强依赖:文件、数据库、cache等。 单测的两个目标:
- 幂等:重复运行一个测试程序,结果都是一样的。
- 稳定:单测能够相互隔离,也就是说,能在任何时间独立运行。
如果直接写单测,那是不稳定的,因为访问这些依赖可能会有网络等其他原因的问题,我们在单测中一般用mock机制。
我们原函数实现读取文件中的第一行,然后将其中的"11"换成"00"输出,那现在我们的外部文件为上图中右下角所示,在这种情况下,我们当然知道输出应该为"line00",但是当所依赖的文件发生变化之后,这个测试函数就失效了.
2.4 Mock(打桩)
常用的mock的包:bou.ke/monkey 可以实现对函数和实例的方法进行mock, 快速Mock函数:
- 为一个
func打桩
我刚开始以为打桩是mock的一个释义呢,结果查了没查到,老师解释:打桩可以理解为用一个函数去替代
,
被称为原函数,
就叫做打桩函数.
func TestProcessFirstLine(t *testing.T) {
monkey.Patch(ReadFirstLine, func() string {
return "line110"
})
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
多说一嘴,我这里尝试导入"github.com/bouk/monkey"没有成功,所以这里我没有实现,但是可以先理解思想,这利用monkey.Patch()有两个参数,是两个函数,一个是被替换的ReadFirstLine,第二个是匿名函数,通过Patch实现临时替换(还是不理解为啥叫打桩……这个打桩有啥关系呢),defer测试完之后再替换回来.
这样,我们就解开了ProcessFirstLine对外部文件的依赖,从而实现测试.
- 为一个
mothod打桩
这里没有给例子,但是道理是一样的.
3 基准测试
go还提供了基准测试框架 基准测试:测试运行一段程序的性能和CPU的使用情况 我们在实际项目开发中,经常会遇到一些热点代码,或代码性能瓶颈问题,为了定位问题经常会对代码作性能分析(跑分),这就用到了基准测试.使用方法和单测类似. 举例,主程序如下
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
func Select() int {
return ServerIndex[rand.Intn(10)]
}
func main() {
Select()
}
针对Select()函数的测试程序设计如下
package main
import "testing"
func BenchmarkSelect(b *testing.B) {
// Select要调用的ServerIndex()函数需要ServerIndex这歌全局变量资源
// 初始化这个资源的过程应该计入跑分计时器
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()
}
})
}
可以看到,并行的测试结果比串行要差(这被称为劣化),因为rand为了保证全局的随机性和并发安全,持有一个全局锁,这就降低了并发性能,在多协程并行压力测试的时候,性能就会劣化.
字节跳动提供了一个FastRand来解决这个问题,导入
"github.com/bytedance/gopkg/lang/fastrand"后再实现一遍
对比效率,只能说牛逼.并发的压力测试效率提升了两百倍不止.但是FastRand的实现牺牲了一定的随机数列的一致性.
但是大多数场景下,
fastrand都比rand好用.