前言
TDD(Test Driven Development),那么我们如何做到可反复、无遗漏、高效地测试我们自己写的的代码?实现敏捷开发呢?
这就需要我们自己给自己写的代码写测试用例!
本文主要介绍下在Go语言中如何做单元测试、基准测试、非功能测试。
go test介绍
想要测试Go代码需要依赖go test
命令,go test命令更像1个框架:
在包目录内所有测试文件必须以_test.go结尾,go build不会把这些测试文件
编译到最终的可执行文件中。
ps:
我们执行go test
命令时,它会遍历该go包中所有以_test.go结尾的测试
文件, 然后调用并执行测试文件中符合go test 规则的函数帮助我们实现自动化测试。
其过程为生成1个临时的main包用于调用相应的测试函数,然后构建并运行测试文件中的函数、报告测试结果,最后清理测试中生成的临时文件。
根据测试维度可以把包内以_test.go结尾的测试
文件中的函数划分为以下3种:
类型 | 格式 | 作用 |
---|---|---|
测试函数 | 函数名前缀为Test | 测试程序的一些逻辑行为是否正确 |
基准函数 | 函数名前缀为Benchmark | 测试函数的性能(执行时间、内存申请情况) |
示例函数 | 函数名前缀为Example | 为文档提供示例文档 |
单元测试函数(unit testing):是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。
基准测试函数:测试程序执行时间复杂度、空间复杂度
示例函数:为调用该功能代码的人提供演示
以上了解到为了可以使用golang内置的 go test命令实现自动化测试 需要做以下步骤:
1.在需要测试代码的同一目录下,准备一些以x_test.go结尾的测试文件。
2.执行go test自动发现x_test.go结尾的测试文件并执行其内部符合go test 格式的函数 。
有go test这个测试工具之后,对于1个golang程序员来说,主要学习如何准备测试文件、测试文件中的测试函数需要遵循哪些格式规范,go test才会帮我去自动调用、执行我写的测试用例!
单元测试函数
单元测试函数就是1个针对源码中1个单元(func/class)进行功能测试的函数。
单元测试函数格式
1.每个函数必须导入testing包
import ( "reflect" "testing" ) |
2.测试函数格式
单元测试函数的名字必须以Test
开头,可选的后缀名必须以大写字母开头
每个单元测试函数的参数必须为*testing.T,参数t
用于报告测试是否失败以及日志信息。
func TestAdd(t *testing.T){ ... } func TestSum(t *testing.T){ ... } func TestLog(t *testing.T){ ... } |
testing.T参数
的拥有的方法如下:
func (c *T) Error(args ... interface {}) func (c *T) Errorf(format string, args ... interface {}) func (c *T) Fail() func (c *T) FailNow() func (c *T) Failed() bool func (c *T) Fatal(args ... interface {}) func (c *T) Fatalf(format string, args ... interface {}) func (c *T) Log(args ... interface {}) func (c *T) Logf(format string, args ... interface {}) func (c *T) Name() string func (t *T) Parallel() func (t *T) Run(name string, f func (t *T)) bool func (c *T) Skip(args ... interface {}) func (c *T) SkipNow() func (c *T) Skipf(format string, args ... interface {}) func (c *T) Skipped() bool |
3.验证测试驱动开发理念
假设现在开发了1个单元(函数), 该单元的功能是对string类型的变量进行split。
split.go (源代码)
package splitString import "strings" //Newsplit 切割字符串 //example: //abc,b=>[ac] func Newsplit(str, sep string) (des []string) { index := strings.Index(str, sep) for index > -1 { sectionBefor := str[:index] des = append(des, sectionBefor) str = str[index+1:] index = strings.Index(str, sep) } //最后1 des = append(des, str) return } |
split_test.go(单元测试代码)
package splitString import ( "reflect" "testing" ) //测试用例1:以字符分割 func TestSplit(t *testing.T) { got := Newsplit( "123N456" , "N" ) want := []string{ "123" , "456" } //DeepEqual比较底层数组 if !reflect.DeepEqual(got, want) { //如果got和want不一致说明你写得代码有问题 t.Errorf( "The values of %v is not %v\n" , got, want) } } //测试用例2:以标点符号分割 func TestPunctuationSplit(t *testing.T) { got := Newsplit( "a:b:c" , ":" ) want := []string{ "a" , "b" , "c" } if !reflect.DeepEqual(got, want) { t.FailNow() //出错就stop别往下测了! } } |
It's the truth that the test driven the developmen.
我在原来测试用例的基础上增加了1个测试用例3
package splitString import ( "reflect" "testing" ) //测试用例1:以字符分割 func TestSplit(t *testing.T) { got := Newsplit( "123N456" , "N" ) want := []string{ "123" , "456" } //DeepEqual比较底层数组 if !reflect.DeepEqual(got, want) { //如果got和want不一致说明你写得代码有问题 t.Errorf( "The values of %v is not %v\n" , got, want) } } //测试用例2:以标点符号分割 func TestPunctuationSplit(t *testing.T) { got := Newsplit( "a:b:c" , ":" ) want := []string{ "a" , "b" , "c" } if !reflect.DeepEqual(got, want) { t.FailNow() //出错就stop别往下测了! } } //测试用例3:增加分隔符的长度 func TestMultipleChartSplit(t *testing.T) { got := Newsplit( "hellowbsdjshdworld" , "bsdjshd" ) want := []string{ "hellow" , "world" } if !reflect.DeepEqual(got, want) { t.Fatalf( "无法通过多字符分隔符的测试!got: %v want:%v\n" , got, want) //出错就stop别往下测了! } } |
执行go test测试出bug无法 使用多个字符分割字符串
1 2 3 4 5 6 7 8 9 10 11 | D:\goproject\src\LearningTest\splitString>go test - v === RUN TestSplit --- PASS: TestSplit (0.00s) === RUN TestPunctuationSplit --- PASS: TestPunctuationSplit (0.00s) === RUN TestMultipleChartSplit --- FAIL: TestMultipleChartSplit (0.00s) split_test.go:35: 无法通过多字符分隔符的测试!got: [hellow sdjshdworld] want:[hellow world] FAIL exit status 1 FAIL LearningTest /splitString 0.037s |
驱动我继续开发源码
package splitString import "strings" //Newsplit 切割字符串 //example: //abc,b=>[ac] func Newsplit(str, sep string) (des []string) { index := strings.Index(str, sep) for index > -1 { sectionBefor := str[:index] des = append(des, sectionBefor) str = str[index+len(sep):] index = strings.Index(str, sep) } //最后1 des = append(des, str) return } |
测试组
以上的测试模式中我们每写个测试用例就需要再写1个函数,可以继续优化测试代码!
利用结构体组织测试数据把多个测试用例合到一起,在1个函数内对1组测试用例进行统一测试。
测试代码
package splitString import ( "reflect" "testing" ) //测试组:在1个函数中写多个测试用例,切支持灵活扩展! type testCase struct { str string separate string want []string } var testGroup = []testCase{ //测试用例1:单个英文字母 testCase{ str: "123N456" , separate: "N" , want: []string{ "123" , "456" }, }, //测试用例2:符号 testCase{ str: "a:b:c" , separate: ":" , want: []string{ "a" , "b" , "c" }, }, //测试用例3:多个英文字母 testCase{ str: "hellowbsdjshdworld" , separate: "bsdjshd" , want: []string{ "hellow" , "world" }, }, //测试用例4:单个汉字 testCase{ str: "山西运煤车煤运西山" , separate: "山" , want: []string{ "西运煤车煤运西" }, }, //测试用例4:多个汉字 testCase{ str: "京北北京之北" , separate: "北京" , want: []string{ "京北" , "之北" }, }, } func TestSplit(t *testing.T) { for _, test := range testGroup { got := Newsplit(test.str, test.separate) if !reflect.DeepEqual(got, test.want) { t.Fatalf( "失败!got:%#v want:%#v\n" , got, test.want) } } } |
源码
测试驱动开发!源码又发现了新的bug!
package splitString import "strings" //Newsplit 切割字符串 //example: //abc,b=>[ac] func Newsplit(str, sep string) (des []string) { index := strings.Index(str, sep) for index > -1 { sectionBefor := str[:index] if len(sectionBefor) >= 1 { des = append(des, sectionBefor) } str = str[index+len(sep):] index = strings.Index(str, sep) } //最后1 if len(str) >= 1 { des = append(des, str) } return } |
测试结果
D:\goproject\src\LearningTest\splitString> go test -v === RUN TestSplit --- PASS: TestSplit (0.00s) PASS ok LearningTest/splitString 0.022s D:\goproject\src\LearningTest\splitString> |
子测试
基于测试组对测试代码再次进行优化,利用使用t *testing.T参数的run方法去执行测试用例。
这种方法可以针对测试组里的1个测试用例进行单独测试,所以也叫子测试。
测试代码
package splitString import ( "reflect" "testing" ) //子测试 type testCase struct { str string separate string want []string } var testGroup = map [string]testCase{ "punctuation" : testCase{ str: "a:b:c" , separate: ":" , want: []string{ "a" , "b" , "c" }, }, "sigalLetter" : testCase{ str: "123N456" , separate: "N" , want: []string{ "123" , "456" }, }, "MultipleLetter" : testCase{ str: "hellowbsdjshdworld" , separate: "bsdjshd" , want: []string{ "hellow" , "world" }, }, "singalRune" : testCase{ str: "山西运煤车煤运西山" , separate: "山" , want: []string{ "西运煤车煤运西" }, }, "multiplRune" : testCase{ str: "京北北京之北" , separate: "北京" , want: []string{ "京北" , "之北" }, }, } //测试用例函数 func TestSplit(t *testing.T) { for name, test := range testGroup { //使用t参数的run方法 t.Run(name, func (t *testing.T) { got := Newsplit(test.str, test.separate) if !reflect.DeepEqual(got, test.want) { t.Fatalf( "失败!got:%#v want:%#v\n" , got, test.want) } }) } } |
测试结果
D:\goproject\src\LearningTest\splitString>go test - v === RUN TestSplit === RUN TestSplit /punctuation === RUN TestSplit /sigalLetter === RUN TestSplit /MultipleLetter === RUN TestSplit /singalRune === RUN TestSplit /multiplRune --- PASS: TestSplit (0.00s) --- PASS: TestSplit /punctuation (0.00s) --- PASS: TestSplit /sigalLetter (0.00s) --- PASS: TestSplit /MultipleLetter (0.00s) --- PASS: TestSplit /singalRune (0.00s) --- PASS: TestSplit /multiplRune (0.00s) PASS ok LearningTest /splitString 0.037s 针对某1个测试用例进行单独测试 D:\goproject\src\LearningTest\splitString>go test -run=TestSplit /punctuation PASS ok LearningTest /splitString 0.042s D:\goproject\src\LearningTest\splitString> |
测试覆盖率
测试覆盖率是你的代码被测试套件覆盖的百分比。
通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。
Go提供内置功能来检查你的代码覆盖率。我们可以使用
go test -cover
查看测试覆盖率。
1 2 3 4 5 6 | D:\goproject\src\LearningTest\splitString> go test -cover PASS coverage: 100.0% of statements ok LearningTest /splitString 0.042s D:\goproject\src\LearningTest\splitString> |
go test -cover -coverprofile=测试报告文件
把测试覆盖率的详细信息输出到文件
D:\goproject\src\LearningTest\splitString>go test -cover -coverprofile=test_report.out PASS coverage: 100.0% of statements ok LearningTest /splitString 0.040s |
go tool cover -html=测试报告文件
把测试报告输出到文件,就是为了分析测试结果,go内置的工具支持以HTML的方式打开测试报告文件!
1 | D:\goproject\src\LearningTest\splitString>go tool cover -html=test_report.out |
上图中每个用绿色标记的语句块表示被覆盖了,而红色的表示没有被覆盖。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南