Go语言的测试 | 青训营笔记

56 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 .

运行结果如图:

image-20230216205413162.png