Go工程实践之测试
不是开玩笑的,本文只要你愿意跟着抄,基本就能把课上的东西弄明白抄一遍🐶,实操写的非常非常详细。如果你看不明白,到下方github仓库提交issue,我来帮你解决问题🕊
如果这篇文章对你有帮助,希望你能留下点赞,如果你能到github点个star就更好了(@^0^@)/。
本文所有案例代码都可以在这里获取: MoFishXiaodui/ExecutableManual: 青训营后端-可执行手册-非常详细的手把手教学教程 (github.com)
相关资料
- PPT链接 Go 语言入门 - 工程实践 - 赵征.pptx - 飞书云文档 (feishu.cn)
- 官方预习文档 【后端专场 学习资料一】字节跳动青训营 - 掘金 (juejin.cn)
Manual
此次可操作手册以PPT顺序为主,建议大家对照着PPT一起阅读,并且跟着操作
03 测试
基础测试的章节在手把手教学-Go语言进阶与依赖管理课 | 青训营 - 掘金 (juejin.cn)后半部分,未学习的读者需要自行往回翻阅
案例10 - 依赖文件的单元测试
-
新文件夹
2-2-10file
,在内新建file.go
、file_test.go
和log.txt
文件 -
log.txt
line11 line22 line33
-
file.go
package main import ( "bufio" "fmt" "os" "strings" ) func ReadFirstLine() string { file, err := os.Open("log.txt") defer file.Close() if err != nil { return "" } scanner := bufio.NewScanner(file) for scanner.Scan() { return scanner.Text() } return "" } func ProcessFirstLine() string { line := ReadFirstLine() destLine := strings.ReplaceAll(line, "11", "00") return destLine } func main() { line := ProcessFirstLine() fmt.Println(line) }
-
file_test.go
package main import ( "github.com/stretchr/testify/assert" "testing" ) func TestProcessFirstLine(t *testing.T) { firstLine := ProcessFirstLine() assert.Equal(t, "line00", firstLine) }
-
先运行
go run file.go
查看效果src\2-2-10file> go run .\file.go line00
-
再运行
go test file.go file_test.go
查看效果(base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-10file> go test .\file.go .\file_test.go ok command-line-arguments 0.213s (base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-10file> go test .\file.go .\file_test.go --cover ok command-line-arguments 0.227s coverage: 69.2% of statements
-
总结
ReadFirstLine
函数可以读取log.txt
文件并返回第一行ProcessFirstLine
函数可以调用ReadFirstLine
函数获取第一行文本副本,并对该副本做加工,把"11"替换成"00"TestProcessFirstLine
函数用来测试ProcessFirstLine
函数是否正确ReadFirstLine
函数的内容返回依赖于文件log.txt
,如果log.txt
被删除,就不能测试ProcessFirstLine
的正确性。接下来的案例我们将设法替换ReadFirstLine
函数,使测试不依赖于log.txt
文件
案例11 - mock
mock
- 为一个函数打桩
- 为一个方法打桩
此次mock需要用到"bou.ke/monkey"包
-
复制案例10的整个文件夹,命名为:
2-2-11mock
,删掉log.txt
文件。(把ReadFirstLine
的依赖删除) -
添加monkey包,在终端运行
go get "bou.ke/monkey"
(base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-11mock> go get "bou.ke/monkey" go: added bou.ke/monkey v1.0.2
-
修改
file_test.go
文件的测试函数代码。(就是在中间多了一个函数替换操作,也就是打桩)// 原来 func TestProcessFirstLine(t *testing.T) { firstLine := ProcessFirstLine() assert.Equal(t, "line00", firstLine) } // 现在 func TestProcessFirstLine(t *testing.T) { monkey.Patch(ReadFirstLine, func() string { return "line11" }) defer monkey.Unpatch(ReadFirstLine) firstLine := ProcessFirstLine() assert.Equal(t, "line00", firstLine) }
-
最终代码见src/2-2-11mock/
-
同案例10一样测试
(base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-11mock> go test .\file.go .\file_test.go --cover ok command-line-arguments 0.218s coverage: 23.1% of statements
-
总结:mock可以通过替换函数的方式,从而可以删去部分依赖。
案例12 - 基准测试
-
新建文件夹
2-2-12bench
,在内新建文件server.go
和server_test.go
-
初始化,在
2-2-12bench
目录下执行go mod init server
-
在
server.go
中写入代码package bench import "math/rand" var ServerIndex [10]int // 初始化服务函数,(其实就是往数组里面各个项写入特定数字,i+100本身应该没什么含义) func InitServerIndex() { for i := 0; i < 10; i++ { ServerIndex[i] = i + 100 } } func Select() int { // 随意选择一个服务器(随机选择一个数组元素,模拟随机一个服务器返回数字) return ServerIndex[rand.Intn(10)] }
-
在
server_test.go
中写入代码package bench import "testing" func BenchmarkSelect(b *testing.B) { InitServerIndex() b.ResetTimer() for i := 0; i < b.N; i++ { Select() } } // parallel 平行的,同时发生的 func BenchmarkSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { Select() } }) }
-
最终代码见src/2-2-12bench/
-
运行命令
go test -bench=Ben
,查看效果。指令中Ben
是指以Ben
开头的测试函数都执行(base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-12bench> go test -bench=B goos: windows goarch: amd64 pkg: server cpu: 12th Gen Intel(R) Core(TM) i5-12490F BenchmarkSelect-12 71638796 15.41 ns/op BenchmarkSelectParallel-12 28514331 42.61 ns/op PASS ok server 2.562s # 只测并行 (base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-12bench> go test -bench=BenchmarkSelectP goos: windows goarch: amd64 pkg: server cpu: 12th Gen Intel(R) Core(TM) i5-12490F BenchmarkSelectParallel-12 27168068 43.41 ns/op PASS ok server 1.399s
案例13 - 基准测试优化
-
复制案例12 的
2-2-12bench
文件夹,重命名为2-2-13benchOptimize
-
在
2-2-13bencOptimize
文件夹中执行命令,go get "github.com/bytedance/gopkg@main"
添加字节的gopkg(base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-13benchOptimize> go get "github.com/bytedance/gopkg@main" go: downloading github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b go: added github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b
-
在文件
server.go
中导入字节的fastrand包import "github.com/bytedance/gopkg/lang/fastrand"
这里说一下是怎么知道在路径
gopkg/lang/fastrand
方法一:
- 当你执行go get 命令后,你可以去
$GOPATH/pkg/mod
找到你下载的包,然后在里面查找就能找到fastrand
包的位置 - 如果你不知道你的
$GOPATH
在哪,你可以在终端输入go env
确定
方法二(其实我也是用方法二的时候才确定用方法一再找一遍的):
- 当你执行go get 命令后,你可以去
-
修改下方的随机选择服务器代码中的随机函数为快速随机函数
// 原来 return ServerIndex[rand.Intn(10)] // 现在 return ServerIndex[fastrand.Intn(10)]
-
与案例12一样进行测试
(base) PS D:\code\MoFishXiaodui\ExecutableManual\src\2-2-13benchOptimize> go test -bench=B goos: windows goarch: amd64 pkg: server cpu: 12th Gen Intel(R) Core(TM) i5-12490F BenchmarkSelect-12 523615494 2.292 ns/op BenchmarkSelectParallel-12 1000000000 0.3863 ns/op PASS ok server 2.039s
-
可以发现这比案例12要快很多