Golang工程测试 | 青训营

84 阅读3分钟

1.测试内容

主要包括三点:单元测试,集成测试,回归测试

测试成本逐渐降低,代码覆盖率逐渐上升

1.1 单元测试

将参数输入测试单元(函数,接口,模块等),将输出与期望进行比对的过程

可以保证代码质量

3.1.1 单元测试规则

  • 所有测试文件均以_test.go结尾

Untitled.png

  • func TestXxx(t *testing.T),要注意Test后面的首字母不能小写,否则无法运行

Untitled2.png

  • 初始化逻辑放在TestMain中

Untitled3.png

3.1.2 单元测试示例

我们开发了一个HelloTom,本来是要输出Tom但是输出了Jerry,对该函数进行单元测试

func HelloTom() string {
	return "Jerry"
}
import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestHelloTom(t *testing.T) {
	output := HelloTom()
	expectoutput := "Tom"
	assert.Equal(t, expectoutput, output, "they should be equal")
}

使用go test [flag][package]对模块进行测试

这里我们可以使用github.com/stretchr/testify/assert包中的Equal方法来对期望输出与输出进行比较

引入依赖的方法:

点击Goland的Setting,选择GO模块的依赖项集成化,设置GOPROXY变量值:goproxy.io,direct,然后选择下载依赖项启用即可

在运行项目前使用go mod tidy命令将依赖项下载好

结果:

Untitled4.png

3.1.5 覆盖率

如何评估项目的测试水准?

代码覆盖率越完备,整体质量越好

	func TestJudgePassLineTrue(t *testing.T) {
	output := JudgePassLine(70)
	expectoutput := true
	assert.Equal(t, expectoutput, output, "they should be equal")
}
func TestJudgePassLineFalse(t *testing.T) {
	output := JudgePassLine(50)
	expectoutput := false
	assert.Equal(t, expectoutput, output, "they should be equal")
}

进入项目根目录,在终端使用命令:go test Xxxx_test.go Xxxx.go —cover(cover就是覆盖的意思)

将两种不同分支的输出分别进行测试会发现,代码测试的覆盖率已经到了100

Untitled5.png

对不同分支进行测试可以提高测试的完备率

一般覆盖率50——60即可,80就是较高了

测试分支相互独立,全面覆盖

测试的单元粒度足够小,函数单一职责

3.2 单元测试——依赖

Untitled6.png

单元测试的两个目标:幂等,稳定

幂等指的是:每次运行结果一样

稳定是指:单元测试相互隔离,任何时间都可以对单元进行测试

会用到mock机制

有些测试依赖一些特等的文件或者组件:如读文件的测试,数据库的测试等

这样的话就无法保证稳定性

3.4 Mock测试

使用mockey包进行mock测试

可以快速为一个函数打桩:用一个函数替换另一个函数

func Patch(target(原函数,目标被替换的函数),replacement(打桩函数) interface{}) *PatchGuard

还需要定义UnPatch,为了在测试完把目标桩卸载掉

func Unpatch(target interface{}) bool

下面的例子我们使用mockey包对一个读文件函数做mock测试使其不依赖本地文件

package mock

import (
	"bufio"
	"os"
	"strings"
)

func ReadFirstLine() string {
	open, err := os.Open("test.txt")
	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
}

使用monkey包进行打桩操作,将原先文本读出改为固定返回值

package mock

import (
	"github.com/bouk/monkey"
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestProcessFirstLine(t *testing.T) {
	monkey.Patch(ReadFirstLine, func() string {
		return "Line110"
	}) //使用monkey替换ReadFirstLine函数为一个固定的返回值
	defer monkey.Unpatch(ReadFirstLine)
	Line := ProcessFirstLine()
	assert.Equal(t, "Line000", Line, "they should be equal")
}

3.5基准测试

Golang内置了基准测试框架,对代码进行性能分析

基准测试与单元测试类似,也需要以_test为文件结尾

示例:负载均衡

package benchTest

import "math/rand"

var ServerId[10] int
func InitServerIndex(){
	for i:=0;i<10;i++{
		ServerId[i]=i+100
	}
}
func Select() int{
	return ServerId[rand.Intn(10)]
}
func FastSelect() int {
	return ServerId[fastrand.Uint32n(10)]
}

选择服务器id的函数

基准测试函数以BenchmarkXxx为name,与Testxxx类似,主要是对于性能的测试

package benchTest

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) { //并行测试,实质是多个goroutine同时执行Select()
		for pb.Next() {
			Select()
		}
	})
}
func BenchmarkFastSelect(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		FastSelect()
	}
}
func BenchmarkFastSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			FastSelect()
		}
	})
}

运行每个Benchmark,我们会发现并行时的性能比一般的要差一些,是因为有全局锁,性能劣化了

Untitled7.png

使用fastrand明显性能更好

Untitled8.png