Go语言自带测试库testing使用教程_第1页
Go语言自带测试库testing使用教程_第2页
Go语言自带测试库testing使用教程_第3页
Go语言自带测试库testing使用教程_第4页
Go语言自带测试库testing使用教程_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第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. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论