版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第Go语言自带测试库testing使用教程目录简介单元测试表格驱动测试分组和并行主测试函数其他性能测试其他选项示例测试总结
简介
testing是Go语言标准库自带的测试库。在Go语言中编写测试很简单,只需要遵循Go测试的几个约定,与编写正常的Go代码没有什么区别。Go语言中有3种类型的测试:单元测试,性能测试,示例测试。下面依次来介绍。
单元测试
单元测试又称为功能性测试,是为了测试函数、模块等代码的逻辑是否正确。接下来我们编写一个库,用于将表示罗马数字的字符串和整数互转。罗马数字是由M/D/C/L/X/V/I这几个字符根据一定的规则组合起来表示一个正整数:
M=1000,D=500,C=100,L=50,X=10,V=5,I=1;只能表示1-3999范围内的整数,不能表示0和负数,不能表示4000及以上的整数,不能表示分数和小数(当然有其他复杂的规则来表示这些数字,这里暂不考虑);每个整数只有一种表示方式,一般情况下,连写的字符表示对应整数相加,例如I=1,II=2,III=3。但是,十位字符(I/X/C/M)最多出现3次,所以不能用IIII表示4,需要在V左边添加一个I(即IV)来表示,不能用VIIII表示9,需要使用IX代替。另外五位字符(V/L/D)不能连续出现2次,所以不能出现VV,需要用X代替。
//roman.go
packageroman
import(
"bytes"
"errors"
"regexp"
typeromanNumPairstruct{
Romanstring
Numint
var(
romanNumParis[]romanNumPair
romanRegex*regexp.Regexp
var(
ErrOutOfRange=errors.New("outofrange")
ErrInvalidRoman=errors.New("invalidroman")
funcinit(){
romanNumParis=[]romanNumPair{
{"M",1000},
{"CM",900},
{"D",500},
{"CD",400},
{"C",100},
{"XC",90},
{"L",50},
{"XL",40},
{"X",10},
{"IX",9},
{"V",5},
{"IV",4},
{"I",1},
romanRegex=regexp.MustCompile(`^M{0,3}(CM|CD|DC{0,3})(XC|XL|LX{0,3})(IX|IV|VI{0,3})$`)
funcToRoman(nint)(string,error){
ifn=0||n=4000{
return"",ErrOutOfRange
varbufbytes.Buffer
for_,pair:=rangeromanNumParis{
fornpair.Num{
buf.WriteString(pair.Roman)
n-=pair.Num
returnbuf.String(),nil
funcFromRoman(romanstring)(int,error){
if!romanRegex.MatchString(roman){
return0,ErrInvalidRoman
varresultint
varindexint
for_,pair:=rangeromanNumParis{
forroman[index:index+len(pair.Roman)]==pair.Roman{
result+=pair.Num
index+=len(pair.Roman)
returnresult,nil
}
在Go中编写测试很简单,只需要在待测试功能所在文件的同级目录中创建一个以_test.go结尾的文件。在该文件中,我们可以编写一个个测试函数。测试函数名必须是TestXxxx这个形式,而且Xxxx必须以大写字母开头,另外函数带有一个*testing.T类型的参数:
//roman_test.go
packageroman
import(
"testing"
funcTestToRoman(t*testing.T){
_,err1:=ToRoman(0)
iferr1!=ErrOutOfRange{
t.Errorf("ToRoman(0)expecterror:%vgot:%v",ErrOutOfRange,err1)
roman2,err2:=ToRoman(1)
iferr2!=nil{
t.Errorf("ToRoman(1)expectnilerror,got:%v",err2)
ifroman2!="I"{
t.Errorf("ToRoman(1)expect:%sgot:%s","I",roman2)
}
在测试函数中编写的代码与正常的代码没有什么不同,调用相应的函数,返回结果,判断结果与预期是否一致,如果不一致则调用testing.T的Errorf()输出错误信息。运行测试时,这些错误信息会被收集起来,运行结束后统一输出。
测试编写完成之后,使用gotest命令运行测试,输出结果:
$gotest
---FAIL:TestToRoman(0.00s)
roman_test.go:18:ToRoman(1)expect:Igot:
FAIL
exitstatus1
FAIL/darjun/go-daily-lib/testing0.172s
我故意将ToRoman()函数中写错了一行代码,npair.Num中应该为=,单元测试成功找出了错误。修改之后重新运行测试:
$gotest
ok/darjun/go-daily-lib/testing0.178s
这次测试都通过了!
我们还可以给gotest命令传入-v选项,输出详细的测试信息:
$gotest-v
===RUNTestToRoman
---PASS:TestToRoman(0.00s)
PASS
ok/darjun/go-daily-lib/testing0.174s
在运行每个测试函数前,都输出一行===RUN,运行结束之后输出---PASS或---FAIL信息。
表格驱动测试
在上面的例子中,我们实际上只测试了两种情况,0和1。按照这种方式将每种情况都写出来就太繁琐了,Go中流行使用表格的方式将各个测试数据和结果列举出来:
funcTestToRoman(t*testing.T){
testCases:=[]struct{
numint
expectstring
errerror
{0,"",ErrOutOfRange},
{1,"I",nil},
{2,"II",nil},
{3,"III",nil},
{4,"IV",nil},
{5,"V",nil},
{6,"VI",nil},
{7,"VII",nil},
{8,"VIII",nil},
{9,"IX",nil},
{10,"X",nil},
{50,"L",nil},
{100,"C",nil},
{500,"D",nil},
{1000,"M",nil},
{31,"XXXI",nil},
{148,"CXLVIII",nil},
{294,"CCXCIV",nil},
{312,"CCCXII",nil},
{421,"CDXXI",nil},
{528,"DXXVIII",nil},
{621,"DCXXI",nil},
{782,"DCCLXXXII",nil},
{870,"DCCCLXX",nil},
{941,"CMXLI",nil},
{1043,"MXLIII",nil},
{1110,"MCX",nil},
{1226,"MCCXXVI",nil},
{1301,"MCCCI",nil},
{1485,"MCDLXXXV",nil},
{1509,"MDIX",nil},
{1607,"MDCVII",nil},
{1754,"MDCCLIV",nil},
{1832,"MDCCCXXXII",nil},
{1993,"MCMXCIII",nil},
{2074,"MMLXXIV",nil},
{2152,"MMCLII",nil},
{2212,"MMCCXII",nil},
{2343,"MMCCCXLIII",nil},
{2499,"MMCDXCIX",nil},
{2574,"MMDLXXIV",nil},
{2646,"MMDCXLVI",nil},
{2723,"MMDCCXXIII",nil},
{2892,"MMDCCCXCII",nil},
{2975,"MMCMLXXV",nil},
{3051,"MMMLI",nil},
{3185,"MMMCLXXXV",nil},
{3250,"MMMCCL",nil},
{3313,"MMMCCCXIII",nil},
{3408,"MMMCDVIII",nil},
{3501,"MMMDI",nil},
{3610,"MMMDCX",nil},
{3743,"MMMDCCXLIII",nil},
{3844,"MMMDCCCXLIV",nil},
{3888,"MMMDCCCLXXXVIII",nil},
{3940,"MMMCMXL",nil},
{3999,"MMMCMXCIX",nil},
{4000,"",ErrOutOfRange},
for_,testCase:=rangetestCases{
got,err:=ToRoman(testCase.num)
ifgot!=testCase.expect{
t.Errorf("ToRoman(%d)expect:%sgot:%s",testCase.num,testCase.expect,got)
iferr!=testCase.err{
t.Errorf("ToRoman(%d)expecterror:%vgot:%v",testCase.num,testCase.err,err)
}
上面将要测试的每种情况列举出来,然后针对每个整数调用ToRoman()函数,比较返回的罗马数字字符串和错误值是否与预期的相符。后续要添加新的测试用例也很方便。
分组和并行
有时候对同一个函数有不同维度的测试,将这些组合在一起有利于维护。例如上面对ToRoman()函数的测试可以分为非法值,单个罗马字符和普通3种情况。
为了分组,我对代码做了一定程度的重构,首先抽象一个toRomanCase结构:
typetoRomanCasestruct{
numint
expectstring
errerror
}
将所有的测试数据划分到3个组中:
var(
toRomanInvalidCases[]toRomanCase
toRomanSingleCases[]toRomanCase
toRomanNormalCases[]toRomanCase
funcinit(){
toRomanInvalidCases=[]toRomanCase{
{0,"",roman.ErrOutOfRange},
{4000,"",roman.ErrOutOfRange},
toRomanSingleCases=[]toRomanCase{
{1,"I",nil},
{5,"V",nil},
//...
toRomanNormalCases=[]toRomanCase{
{2,"II",nil},
{3,"III",nil},
//...
}
然后为了避免代码重复,抽象一个运行多个toRomanCase的函数:
functestToRomanCases(cases[]toRomanCase,t*testing.T){
for_,testCase:=rangecases{
got,err:=roman.ToRoman(testCase.num)
ifgot!=testCase.expect{
t.Errorf("ToRoman(%d)expect:%sgot:%s",testCase.num,testCase.expect,got)
iferr!=testCase.err{
t.Errorf("ToRoman(%d)expecterror:%vgot:%v",testCase.num,testCase.err,err)
}
为每个分组定义一个测试函数:
functestToRomanInvalid(t*testing.T){
testToRomanCases(toRomanInvalidCases,t)
functestToRomanSingle(t*testing.T){
testToRomanCases(toRomanSingleCases,t)
functestToRomanNormal(t*testing.T){
testToRomanCases(toRomanNormalCases,t)
}
在原来的测试函数中,调用t.Run()运行不同分组的测试函数,t.Run()第一个参数为子测试名,第二个参数为子测试函数:
funcTestToRoman(t*testing.T){
t.Run("Invalid",testToRomanInvalid)
t.Run("Single",testToRomanSingle)
t.Run("Normal",testToRomanNormal)
}
运行:
$gotest-v
===RUNTestToRoman
===RUNTestToRoman/Invalid
===RUNTestToRoman/Single
===RUNTestToRoman/Normal
---PASS:TestToRoman(0.00s)
---PASS:TestToRoman/Invalid(0.00s)
---PASS:TestToRoman/Single(0.00s)
---PASS:TestToRoman/Normal(0.00s)
PASS
ok/darjun/go-daily-lib/testing0.188s
可以看到,依次运行3个子测试,子测试名是父测试名和t.Run()指定的名字组合而成的,如TestToRoman/Invalid。
默认情况下,这些测试都是依次顺序执行的。如果各个测试之间没有联系,我们可以让他们并行以加快测试速度。方法也很简单,在testToRomanInvalid/testToRomanSingle/testToRomanNormal这3个函数开始处调用t.Parallel(),由于这3个函数直接调用了testToRomanCases,也可以只在testToRomanCases函数开头出添加:
functestToRomanCases(cases[]toRomanCase,t*testing.T){
t.Parallel()
//...
}
运行:
$gotest-v
---PASS:TestToRoman(0.00s)
---PASS:TestToRoman/Invalid(0.00s)
---PASS:TestToRoman/Normal(0.00s)
---PASS:TestToRoman/Single(0.00s)
ok/darjun/go-daily-lib/testing0.182s
我们发现测试完成的顺序并不是我们指定的顺序。
另外,这个示例中我将roman_test.go文件移到了roman_test包中,所以需要import/darjun/go-daily-lib/testing/roman。这种方式在测试包有循环依赖的情况下非常有用,例如标准库中net/http依赖net/url,url的测试函数依赖net/http,如果把测试放在net/url包中,那么就会导致循环依赖url_test(net/url)-net/http-net/url。这时可以将url_test放在一个独立的包中。
主测试函数
有一种特殊的测试函数,函数名为TestMain(),接受一个*testing.M类型的参数。这个函数一般用于在运行所有测试前执行一些初始化逻辑(如创建数据库链接),或所有测试都运行结束之后执行一些清理逻辑(释放数据库链接)。如果测试文件中定义了这个函数,则gotest命令会直接运行这个函数,否者gotest会创建一个默认的TestMain()函数。这个函数的默认行为就是运行文件中定义的测试。我们自定义TestMain()函数时,也需要手动调用m.Run()方法运行测试函数,否则测试函数不会运行。默认的TestMain()类似下面代码:
funcTestMain(m*testing.M){
os.Exit(m.Run())
}
下面自定义一个TestMain()函数,打印gotest支持的选项:
funcTestMain(m*testing.M){
flag.Parse()
flag.VisitAll(func(f*flag.Flag){
fmt.Printf("name:%susage:%svalue:%v\n",f.Name,f.Usage,f.Value)
os.Exit(m.Run())
}
运行:
$gotest-v
name:test.benchusage:runonlybenchmarksmatching`regexp`value:
name:test.benchmemusage:printmemoryallocationsforbenchmarksvalue:false
name:test.benchtimeusage:runeachbenchmarkforduration`d`value:1s
name:test.blockprofileusage:writeagoroutineblockingprofileto`file`value:
name:test.blockprofilerateusage:setblockingprofile`rate`(seeruntime.SetBlockProfileRate)value:1
name:test.countusage:runtestsandbenchmarks`n`timesvalue:1
name:test.coverprofileusage:writeacoverageprofileto`file`value:
name:test.cpuusage:comma-separated`list`ofcpucountstoruneachtestwithvalue:
name:test.cpuprofileusage:writeacpuprofileto`file`value:
name:test.failfastusage:donotstartnewtestsafterthefirsttestfailurevalue:false
name:test.listusage:listtests,examples,andbenchmarksmatching`regexp`thenexitvalue:
name:test.memprofileusage:writeanallocationprofileto`file`value:
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026重庆发展能源有限公司招聘5人笔试备考试题及答案解析
- 2026四川宜宾市叙州区金才人力资源有限责任公司第二次招聘外派项目制工作人员14人笔试参考题库及答案解析
- 2026宁夏宝丰储能负极厂招聘470人考试备考题库及答案解析
- 2026台州玉环市事业单位招聘59人-统考考试备考题库及答案解析
- 2026上半年北京事业单位统考北京市农业农村局招聘19人考试备考题库及答案解析
- 2026四川乐山师范学院考核招聘专职博士辅导员10人笔试模拟试题及答案解析
- 2026福建漳州平和县人民检察院招聘2名笔试参考题库及答案解析
- 广东省东莞市虎门捷胜校2026届初三2月模拟(四)英语试题含解析
- 2026年福建省厦门市同安区五校初三下学期二轮模考英语试题含解析
- 江苏省苏州市常熟达标名校2025-2026学年初三质量检测试题语文试题含解析
- NCCN临床实践指南:非小细胞肺癌(2025.V8)
- 科技公司下游合同范本
- GB/T 46247-2025风能发电系统基于地面遥测技术的风能资源测量
- 韩语topik所有历届考试真题及答案
- 工业节能降耗知识培训课件
- 2025年9月福建厦门市人力资源和社会保障局附所属事业单位招聘非在编人员5人笔试参考题库附答案解析
- TCHES65-2022生态护坡预制混凝土装配式护岸技术规程
- 幼儿园大班数学获奖公开课《8的分成》课件
- 2025中国农业科学院棉花研究所第二批招聘7人备考考试题库附答案解析
- 基层信访工作课件
- 国家电网ESG报告:2023年度供应链环境、社会与公司治理行动报告
评论
0/150
提交评论