这种编写test的操作还是我实习的小哥哥教我的,个人觉得特别的实用,我们编写项目的时候难免不会出现某个调用不确定!这个输出结果是否是我们期望的!很多是否都要碰都这种情况,特别是在写一些项目的时候,文件很多代码也多,以前在不知道这样方法的时候就很恼火,以前在遇到这种情况的时候处理方法就是用IDE新建一个环境,然后把要测试的函数直接复制过去,但是这样不仅麻烦很多时候还没有办法做。
打个比方:我现在不确定我用gorm(Go语言中一个比较火的orm第三方包)写了一个model的查询,但是我不确定我写的这个是否有问题,那么就只有另外写一个东西来运行测试这个函数是否正确。但是model的查询等等,都是还是很多数据库初始化连接,读取配置文件中的数据库配置信息等,一大堆的东西,所以就很麻烦。自从小哥哥教了我写test这种“高大上”的操作,“妈妈再也不用担心我的学习了”。
我现在拿一个我实习正在更新的一个项目举一个列子:
首先我先讲一下这个项目的其中一个需求:
- 这个后端的系统需要接受用户上传的图片,那么我们肯定需要对用户上传的图片做一些检查,检查用户是不是上传的一个图片呀,一般也就是对文件的后缀进行检查,把其他的可能存在一些攻击的文件进行过滤。
文件结构如图所示:
其中的post.go文件内部有两个函数:(这个项目用了gin框架)
func Post(c *gin.Context) //用作接受提交的POST图片上传请求
func CheckFile(f *multipart.FileHeader) (newFileName string, flag bool) //用于文件合法检查
情景是这样的:带我实习的那个小哥哥一开始没有对上传的文件进行合法性检查,我看源码的时候觉得这样不检查不是很合适,所以我就给他加上了的,但是我最开始写好了CheckFile
这个函数不是很确定这个函数是否正确,毕竟接触GO语言还没有很长时间,很多的东西还是很不熟悉。慢慢后面就会好了吧,毕竟编程也是需要熟能生巧的过程吧。
那么这里我就需要测试post.go
中我新增的函数是否正确看看有没有什么bug,在这里我就教大家怎么做。
- 首先在post.go同级目录下新建一个文件,文件名的规则是*_test.go
*
是指你要测试的文件的文件名。 - 在
*_test.go
文件里面的测试函数应该这样写:
package image
import (
"fmt"
"mime/multipart"
"testing"
)
func TestCheckFile(t *testing.T) {
f := multipart.FileHeader{}
f.Filename = "1.jpg.go"
filename, err := CheckFile(&f)
if !err {
fmt.Println("文件类型错误")
} else {
fmt.Println("new file name is ", filename)
}
}
其中我们import了testing
这个包,这个包的功能很强大,其中我们写test就是用的这个包的功能,其中我写了一个测试TestCheckFile
的函数,这个函数的写法使用规则的,规则就是:
- 命名必须是TestXXX,必须是以Test开通而且第一个字母必须大写。这样就写好了一个测试用例,在命令行中只要进入到post.go的那个目录下面,执行
go test -v
就可以了。 - 其中我们必须给我们写的测试函数一个
*testing.T
的指针参数。比如我上面给出的列子中func TestCheckFile(t *testing.T)
。
高级点的test用法
比如我门需要测试一个文件里面的多个函数,比如我写了一个测试的例子:
在项目test路径为($GOPATH/src/test) 现在需要我们做在main_test.go中测试main.go的里面的三个函数,那么我们在main_test.go
中写了三个测试函数,那么我们在运行命令go test -v
后这三个函数执行了顺序是怎样的呢?
一般情况下都是按照文件函数编译的顺序来执行的,但是go官方并没有说这一定是按照这个顺序来执行的。我自己运行了下执行的顺序确实是按照顺序执行的。
test$ go test -v
=== RUN TestA
goo
--- PASS: TestA (0.00s)
=== RUN TestB
gogogo
--- PASS: TestB (0.00s)
=== RUN TestC
gogogo
--- PASS: TestC (0.00s)
PASS
ok test 0.005s
如果万一又有这样一个需求呢:我只要在main_test.go
中测试goo()
这一个函数呢?岂不是我们要把其他的两个测试函数注释掉或者是删除掉,其实不必这样也可以做到。比如像我这样,调用下一个函数:
然后再执行下go test -v
:
[alphababy@AlphaBaby] test$ go test -v
=== RUN TestA
goo
--- PASS: TestA (0.00s)
=== RUN TestB
--- SKIP: TestB (0.00s)
=== RUN TestC
--- SKIP: TestC (0.00s)
PASS
ok test 0.005s
像这样就可以直接跳过下面两个测试函数了。
那么再增加一个需求, 比如最开始那个后端项目的列子中,在model的模块里面是需要连接数据库的我们在连接数据库的时候必须要先加载配置文件,但是go官方并没有保证说测试函数执行的顺序是一定从上到下的,那么我们怎么保证在测试main.go
中的三个函数的时候一定先执行TestA
然后TestB
在接着TestC
,那么我们怎么保证这个顺序呢?
看我是怎么处理的:
执行结果就是如下所示。
test$ go test -v
=== RUN TestPrint
=== RUN TestPrint/A
goo
=== RUN TestPrint/2
=== RUN TestPrint/3
gogogo
--- PASS: TestPrint (0.00s)
--- PASS: TestPrint/A (0.00s)
--- SKIP: TestPrint/2 (0.00s)
--- PASS: TestPrint/3 (0.00s)
PASS
ok test 0.005s
这样就能做到有顺序的执行我们的测试函数了。
这里再介绍一个TestMain
的函数调用,比如我写的:
执行go test -v
后的结果是:
test$ go test -v
TestMain is first exec!
=== RUN TestPrint
=== RUN TestPrint/A
goo
=== RUN TestPrint/2
=== RUN TestPrint/3
gogogo
--- PASS: TestPrint (0.00s)
--- PASS: TestPrint/A (0.00s)
--- SKIP: TestPrint/2 (0.00s)
--- PASS: TestPrint/3 (0.00s)
PASS
ok test 0.005s
其中有两点应该注意:
- TestMain会比所有的TestXXX函数最先执行。
- 只有当在
TestMain
函数中调用m.Run()
才会执行其他的TestXXX。(你可以去掉m.Run()
自己试一下。)
更高级点的test用法
我们的testing包中的工具不仅能测试我们函数功能上是否有BUG还能测试函数的性能。
那这个怎么写呢?:我再给大家一个demo:
其余的函数我没有截图下来,但是我还是没有修改只是在以上的文件中增加了这两个函数。
我们使用命令:$ go test -v -bench=.
继续测试下
[alphababy@AlphaBaby] test$ go test -v -bench=.
TestMain is first exec!
=== RUN TestPrint
=== RUN TestPrint/A
goo
=== RUN TestPrint/2
=== RUN TestPrint/3
gogogo
--- PASS: TestPrint (0.00s)
--- PASS: TestPrint/A (0.00s)
--- SKIP: TestPrint/2 (0.00s)
--- PASS: TestPrint/3 (0.00s)
goos: darwin
goarch: amd64
pkg: test
BenchmarkA-4 2000000000 0.29 ns/op
PASS
ok test 0.623s
这里可以发现,在BenchmarkA
函数中的循环循环了2000000000次,每次执行的时间为:0.29 ns。举个列子,比如我们要测量一张纸的厚度我们会把纸重在一起叠厚以后在测量后面计算一个平均值然后就可以测量出一张纸的厚度了。这里的道理也是一行,其中b.N
这个值会是testing
包给我动态的计算好,计算的规则就是当我们函数test
调用到达稳态。
但是这里还是有几点注意:
- 测试性能的函数必须是以
BenchmarkXXX
为命名格式 - 其中
BenchmarkXXX
函数和普通的TestXXX
函数是一样的,执行顺序也是一样,而且也只有当m.Run()
函数调用后BenchmarkXXX
函数才会执行。
如果后面有有趣的,还会继续分享
有疑问加站长微信联系(非本文作者)