GO语言测试入门
- 单元测试
- Mock 测试
- 基准测试
测试
-
回归测试
比如抖音刷刷视频,看看评论
-
集成测试
对功能测试,服务暴露的某个接口进行测试
-
单元测试
面对测试开发阶段,对开发模块进行测试
单元测试
单元测试-规则
单元测试-覆盖率
单元测试的覆盖率是指在进行单元测试时,代码被执行的程度。它通常用百分比来表示,表示代码中有多少比例的语句、分支或路径被测试用例执行过。覆盖率越高,说明代码被测试的越全面,潜在的错误和漏洞就越少。
func JudgePassLine(score int16) bool {
if score >= 60 {
return true
}
return false
}
这是一个测试成绩的函数,下面写一个单测
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
使用go test judgement_test.go judgement.go --cover 测试覆盖率得到 66.7% 的代码覆盖率;这是因为只有 true 的两个代码被执行了,后面的没被执行。(3 / 2)我们要提升覆盖率
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
func TestJudgePassLineFalse(t *testing.T) {
isPass := JudgePassLine(50)
assert.Equal(t, false, isPass)
}
单元测试-Tips
- 一般覆盖率:50 % - 60 %,较高覆盖率80% + (一般支付业务)
- 测试分支相互独立,全面覆盖
- 测试单元粒度足够小,函数单一指责
单元测试-依赖
依赖管理很复杂,依赖的组件也很复杂,依赖一些数据库,Cache... 这些都是强依赖
单元测试目标:
- 幂等 重复运行一个测试的case,结果都是一样的
- 稳定 单元测试都是相互隔离的
这样就会用到 Mock 机制
单元测试-文件处理
在如图的例子中因为测试文件依赖于 log 文件,要是 log 被篡改了,测试文件就没法正常工作了。这时引入 mock 是一种很好的解决方法。通过使用 mock 对象,可以在不依赖实际文件的情况下测试函数逻辑,从而避免因文件被篡改或测试文件被删除等外部因素导致的测试失败。
单元测试- Mock
Mock 组件有很多,这里我们用一个常见的开源的 monkey : github.com/bouk/monkey
快速 Mock 函数
- 为一个函数打桩
- 为一个方法打桩
打桩可以理解为用函数 A(打桩函数) 替换 函数 B(原函数) 参考源码如下
// 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}
}
// 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))
}
基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
基准测试是一种测量和评估软件性能的方法,通常用于比较不同算法、数据结构或系统配置的性能。在Go语言中,基准测试是通过testing包中的Benchmark函数来实现的。
package main
import (
// "github.com/NebulousLabs/fastrand"
"math/rand"
"github.com/bytedance/gopkg/lang/fastrand"
)
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
func Select() int {
return ServerIndex[rand.Intn(10)]
}
// SelectWithRand 使用传入的独立随机数生成器
func SelectWithRand(r *rand.Rand) int {
return ServerIndex[r.Intn(10)]
}
// fastrand 是一个全局的随机数生成器
func FastSelect() int {
return ServerIndex[fastrand.Intn(10)]
}
rand 函数为了保证全局的随机性,持有一把全局锁,会导致并发速度变慢
package main
import (
"math/rand"
"testing"
"time"
)
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()
}
})
}
// 基准测试:并行调用独立随机数生成器的 SelectWithRand
func BenchmarkSelectWithRandParallel(b *testing.B) {
InitServerIndex()
// 每个 goroutine 使用独立的随机数生成器
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
// 为每个 goroutine 初始化独立的随机数生成器
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for pb.Next() {
SelectWithRand(r)
}
})
}
func BenchmarkFastSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
FastSelect()
}
})
}
然后我们可以用go get "github.com/bytedance/gopkg@main" 添加字节的 gopkg,使用这个轮子就能加快速度,怎么知道在路径 gopkg/lang/fastrand 呢?
- 当你执行go get 命令后,你可以去
$GOPATH/pkg/mod找到你下载的包,然后在里面查找就能找到fastrand包的位置 - 如果你不知道你的
$GOPATH在哪,你可以在终端输入go env确定
然后我们就看到速度快了不少,因为是 amd 的 cpu 对多线程支持很好吧,可以看到并行还比串行还快了点,但每快太多。第三个基准测试是解决了锁的问题,可以看到没有 fastrank 快。
goos: linux
goarch: amd64
pkg: mytest
cpu: AMD EPYC 9Y24 96-Core Processor
BenchmarkSelect-32 123825390 9.955 ns/op
BenchmarkSelectParallel-32 156996909 8.271 ns/op
BenchmarkSelectWithRandParallel-32 299097139 4.678 ns/op
BenchmarkFastSelect-32 402931642 3.450 ns/op
PASS
ok mytest 8.784s