这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天。
首先,软件测试是在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。而完备的测试可以有效避免各类事故发生。测试可以分为回归测试,集成测试和单元测试。从回归测试到单元测试,测试成本在逐渐降低,而测试覆盖率在逐步上升,所以单元测试的覆盖率一定程度上决定了代码的质量。
单元测试
单元测试主要包括输入,测试单元,输出以及和最终期望输出的校对。单元的概念包括接口,函数模块等。单元测试可以保证质量,同时提升效率。
Go单元测试的编写规则:
- 所有测试文件以_test.go为结尾。
- 测试函数的签名为:
func TestXxx(t *testing.T){} - 初始化逻辑存放在TestMain函数中中。
单元测试案例:设计一个函数测试预期其返回字符串Tom,但误返回字符串Jerry,编写代码如下:
//tom.go文件
package practice
import (
"testing"
)
func Tom() string {
return "Jerry"
}
//tom_test.go文件
func TestTom(t *testing.T) {
output := Tom()
expectOutput := "Tom"
if output != expectOutput {
t.Errorf("Expected %s does not match actual %s", expectOutput, output)
}
}
在命令行运行go test,或者直接在IDE中快捷运行,可以获得结果:
--- FAIL: TestTom (0.00s)
tom_test.go:15: Expected Tom does not match actual Jerry
FAIL
exit status 1
FAIL practice 1.839s
对上述代码采用开源包提供的assert功能进一步简化,下述代码和上面的功能一致:
func TestHelloTom(t *testing.T){
output :=HelloTom()
expectOutput:="Tom"
assert.Equal(t,expectOutput,output)
}
对单元测试做代码覆盖率检测,覆盖率是评估代码测试的指标,证明测试用例的覆盖度,越完备越说明代码有保障,而通过命令cover可以计算覆盖率。
//judgement.go文件
func JundgePassLine(score int16) bool{
if score >= 60{
return true
}
return false
}
//judgement_test.go文件
func TestJudgePassLine(t *testing.T){
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
在终端运行命令:
go test judgment_test.go judgment.go --cover
可以获得结果为:
ok command-line-arguments 0.224s coverage: 66.7% of statements
覆盖率数值其实显示的是测试到的代码行数占函数总行数的百分比。主流程序覆盖率在50%-60%说明基本问题不大,而支付类追求安全的环境需要较高覆盖率,约80%。
Mock测试
首先一些软件往往会依赖数据库,Cache和本地文件等,而单元测试目标是幂等和稳定,幂等是指重复运行测试case结果和之前一样,稳定是指单元测试可以相互隔离,能在任何时间对任何函数进行测试。如果存在强依赖,会不符合稳定的原则,因此产生了Mock机制。
常用的开源Mock测试包是monkey。常用功能是Patch(target(原函数), replacement(打桩函数)),打桩即为用函数A替换函数B,内存函数地址替换成运行时函数地址,实现mock功能。以及unpatch()卸载桩。
以下代码中,ReadFirstLine()函数会依赖读取的本地log文件,而通过Mock来替换就不会依赖本地文件了。
func ReadFirstLine() string{
open, err := os.Open("log")
defer open.Close()
if err != nil {
return ""
}
scanner :=bufio.NewScanner(open)
for scanner.Scan(){
return scanner.Text()
}
return ""
}
func ProcessFirstLine() string{
line:=ReadFirstLine()
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
func TestProcessFirstLine(t *testing.T) {
Line := ProcessFirstLine()
assert.Equal(t, "line00", Line)
}
func TestProcessFirstLineWithMock(t *testing.T) {
monkey.Patch(ReadFirstLine, func() string{return "line110"}
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
基准测试
测试程序运行时性能和CPU的损耗,在实际项目开发中会遇到热点代码和代码性能定位问题,可以通过基准测试来检验和定位。其使用方法类似单元测试,benchmark 和普通的单元测试用例一样,都位于 _test.go 文件中,函数名称为BenchmarkXxx(d *testing.B)。实现案例如下:
package practice
import (
"math/rand"
)
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
func Select() int {
return ServerIndex[rand.Intn(10)]
}
package practice
import (
"testing"
)
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()
}
})
}
终端输入指令为:
go test selectserver.go selectserver_test.go -bench .
运行结果如图: