概述
常言道,不会测试的程序猿不是好的产品经理!!!现在越来越多测试和运维的工作也需要研发来做了,本篇文章就来讲讲Go的单元测试。
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。简单说,就是将测试用例的运行结果与预期结果进行比较。
Go的单元测试
基础知识
Go有testing测试包,配合go test命令能够进行单元测试。
- 测试文件以_test.go结尾
- 在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。
- _test.go文件包含TestXxx函数
- 形参类型必须为*test.T
- PASS表示测试用例运行成功,FAIL表示失败
个人常用Fatalf,这里就来具体说一下,其他函数见参考的链接。
func (c *T) Logf(format string, args ...interface{})
Log 使用与 Printf 相同的格式化语法对它的参数进行格式化,然后将格式化后的文本记录到错误日志里面。 如果输入的格式化文本最末尾没有出现新行,那么将一个新行添加到格式化后的文本末尾。
1)对于测试来说,Logf 产生的格式化文本只会在测试失败或者设置了 -test.v 标志的情况下被打印出来;
2)对于基准测试来说,为了避免 -test.v 标志的值对测试的性能产生影响,Logf 产生的格式化文本总会被打印出来
func (c *T) FailNow()
将当前测试标识为失败并停止执行该测试,在此之后,测试过程将在下一个测试或者下一个基准测试中继续。
FailNow 必须在运行测试函数或者基准测试函数的 goroutine 中调用,而不能在测试期间创建的 goroutine 中调用。调用 FailNow 不会导致其他 goroutine 停止。
func (c *T) Fatalf(format string, args ...interface{})
调用 Fatalf 相当于在调用 Logf 之后调用 FailNow 。
快速入门
项目结构
learnGo
└── main
├── compute.go
└── compute_test.go
compute.go
package main import "errors" func div(a,b int) (int,error) { if b != 0{ return a/b,nil } else{ return 0,errors.New("b is 0") } }
compute_test.go
package main import "testing" func TestDiv(t *testing.T) { res,_ := div(4,2) want := 2 if res != want{ t.Fatalf("期待:%d ,实际结果:%d",want,res) } }
main目录下运行
go test -v
结果如下:
=== RUN TestDiv --- PASS: TestDiv (0.00s) PASS ok learnGo/main 0.457s
以上我们就针对main包的compute.go文件进行了单元测试。
进阶
查看更多的选项,可使用
go help test go help testflag
单个文件的测试
我们在main包中再添加str.go及str_test.go
str.go
package main func getSub(str string,start,end int) string { // 左闭右闭 if 0<=start&&start<end&&end<=len(str){ return str[start:end] }else{ return "" } }
str_test.go
package main import "testing" func TestGetSub(t *testing.T) { astr := "lady_killer9" // 包含开头 res1 := getSub(astr,0,4) want1 := "lady" // 包含结尾 res2 := getSub(astr,4,len(astr)) want2 := "_killer9" // 范围错误 res3 := getSub(astr,1,len(astr)+1) want3 := "" if res1 != want1{ t.Errorf("期望:%s,实际结果:%s",want1,res1) } if res2 != want2{ t.Errorf("期望:%s,实际结果:%s",want2,res2) } if res3 != want3{ t.Errorf("期望:%s,实际结果:%s",want3,res3) } }
运行 go test -v,得到结果如下
=== RUN TestDiv --- PASS: TestDiv (0.00s) === RUN TestGetSub --- PASS: TestGetSub (0.00s) PASS ok learnGo/main 0.446s
go test会运行所有的单元测试,有时候我们只想测试某个文件
如果只是运行一个测试文件,可添加参数
go test -v 测试文件 源文件
运行go test -v str_test.go str.go, 结果如下:
=== RUN TestGetSub --- PASS: TestGetSub (0.00s) PASS ok command-line-arguments 0.619s
单个函数的测试
我们在compute.go中添加
1. func add(a,b int) int { 2. return a+b 3. }
在compute_test.go中添加
func TestAdd(t *testing.T) { a,b:=3,4 res := add(a,b) want := 7 if res != want{ t.Fatalf("期待:%d,实际结果:%d",want,res) } }
由于对div函数未做改动,只想测试add函数,可以使用参数-test.run指定测试函数
go test -v -test.run 测试函数
运行 go test -v -test.run TestAdd 结果如下:
=== RUN TestAdd --- PASS: TestAdd (0.00s) PASS ok learnGo/main 0.836s
单元测试覆盖率
测试应该全面,达到100%。
可以使用-cover参数
go test -cover
结果如下:
PASS coverage: 85.7% of statements ok learnGo/main 0.505s
可以看到,测试的并不全面,指定测试文件来查看。
go test -cover compute_test.go compute.go
ok command-line-arguments 0.314s coverage: 75.0% of statements
观察发现,我们缺少了b为0的分支,修改TestDiv函数为
func TestDiv(t *testing.T) { res,_ := div(4,2) want := 2 if res != want{ t.Fatalf("期待:%d,实际结果:%d",want,res) } res,_ = div(3,0) want = 0 if res != want{ t.Fatalf("期待:%d,实际结果:%d",want,res) } }
测试后再看覆盖率,结果如下
PASS coverage: 100.0% of statements ok learnGo/main 0.306s
作为开发,基本的单元测试就可以了,还可以去了解基准测试、性能测试、压力测试、黑盒测试等。
参考
更多Go相关内容:Go-Golang学习总结笔记
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。