Go 语言虽然自带单元测试功能,在 GoConvey 诞生之前也出现了许多第三方辅助库。但没有一个辅助库能够像 GoConvey 这样优雅地书写代码的单元测试,简洁的语法和舒适的界面能够让一个不爱书写单元测试的开发人员从此爱上单元测试。
下载安装 GoConvey:
go get github.com/smartystreets/goconvey
- 在$GOPATH/src目录下新增了github.com子目录,该子目录里包含了GoConvey框架的库代码
- 在$GOPATH/bin目录下新增了GoConvey框架的可执行程序goconvey
API 文档
请移步 Go Walker。
基本使用方法
示例代码
回到顶部
示例:
下面是一个能够实现整数基本四则运算(加、减、乘、除)的代码:
package main
import "errors"
func Add(a, b int) int {
return a + b
}
func Subtract(a, b int) int {
return a - b
}
func Multiply(a, b int) int {
return a * b
}
func Division(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("被除数不能为 0")
}
return a / b, nil
}
func main() {
}
在上面的代码中,我们实现了 4 个函数,因此需要为这 4 个函数分别书写单元测试:
package main
import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestAdd(t *testing.T) {
Convey("将两数相加", t, func() {
So(Add(1, 2), ShouldEqual, 3)
})
}
func TestSubtract(t *testing.T) {
Convey("将两数相减", t, func() {
So(Subtract(1, 2), ShouldEqual, -1)
})
}
func TestMultiply(t *testing.T) {
Convey("将两数相乘", t, func() {
So(Multiply(3, 2), ShouldEqual, 6)
})
}
func TestDivision(t *testing.T) {
Convey("将两数相除", t, func() {
Convey("除以非 0 数", func() {
num, err := Division(10, 2)
So(err, ShouldBeNil)
So(num, ShouldEqual, 5)
})
Convey("除以 0", func() {
_, err := Division(10, 0)
So(err, ShouldNotBeNil)
})
})
}
首先,您需要使用官方推荐的方式导入 GoConvey 的辅助包以减少冗余的代码: . “github.com/smartystreets/goconvey/convey” 。
每个单元测试的名称需要以 Test 开头,例如: TestAdd ,并需要接受一个类型为 *testing.T 的参数。
使用 GoConvey 书写单元测试,每个测试用例需要使用 Convey 函数包裹起来。它接受的第一个参数为 string 类型的描述;第二个参数一般为 *testing.T ,即本例中的变量 t;第三个参数为不接收任何参数也不返回任何值的函数(习惯以闭包的形式书写)。
Convey 语句同样可以无限嵌套,以体现各个测试用例之间的关系,例如 TestDivision 函数就采用了嵌套的方式体现它们之间的关系。需要注意的是,只有最外层的 Convey 需要传入变量 t,内层的嵌套均不需要传入。
最后,需要使用 So 语句来对条件进行判断。在本例中,我们只使用了 3 个不同类型的条件判断: ShouldBeNil 、 ShouldEqual 和 ShouldNotBeNil ,分别表示值应该为 nil、值应该相等和值不应该为 nil。有关详细的条件列表,可以参见 官方文档。
测试:
go test -v
输出结果(ubuntu):
=== RUN TestAdd
将两数相加 ✔
1 total assertion
--- PASS: TestAdd (0.00s)
=== RUN TestSubtract
将两数相减 ✔
2 total assertions
--- PASS: TestSubtract (0.00s)
=== RUN TestMultiply
将两数相乘 ✔
3 total assertions
--- PASS: TestMultiply (0.00s)
=== RUN TestDivision
将两数相除
除以非 0 数 ✔✔
除以 0 ✔
6 total assertions
--- PASS: TestDivision (0.00s)
PASS
ok tttt 0.004s
我们可以看到,输出结果调理非常清晰,单元测试的代码写起来也非常优雅。那么,这就是全部吗?当然不是。GoConvey 不仅支持在命令行进行人工调用调试命令,还有非常舒适的 Web 界面提供给开发者来进行自动化的编译测试工作。
回到顶部
Web 界面
想要使用 GoConvey 的 Web 界面特性,需要在相应目录下执行 goconvey (需使用 go get 安装到 $GOPATH/bin 目录下),然后打开浏览器,访问 ,就可以看到下以下界面:
//一般都是在项目根目录下(具体根据自己的情况)
goconvey // 确保 $GOPATH/bin 目录下存在 goconvey文件
在 Web 界面中,您可以设置界面主题,查看完整的测试结果,使用浏览器提醒等实用功能。
其它功能:
- 自动检测代码变动并编译测试
- 半自动化书写测试用例:
- 查看测试覆盖率:
- 临时屏蔽某个包的编译测试
回到顶部
Skip
针对想忽略但又不想删掉或注释掉某些断言操作,GoConvey提供了Convey/So的Skip方法:
- SkipConvey函数表明相应的闭包函数将不被执行
- SkipSo函数表明相应的断言将不被执行
当存在SkipConvey或SkipSo时,测试日志中会显式打上”skipped”形式的标记:
- 当测试代码中存在SkipConvey时,相应闭包函数中不管是否为SkipSo,都将被忽略,测试日志中对应的符号仅为一个”⚠”
- 当测试代码Convey语句中存在SkipSo时,测试日志中每个So对应一个”✔”或”✘”,每个SkipSo对应一个”⚠”,按实际顺序排列
- 不管存在SkipConvey还是SkipSo时,测试日志中都有字符串”{n} total assertions (one or more sections skipped)”,其中{n}表示测试中实际已运行的断言语句数
回到顶部
定制断言函数
我们先看一下So的函数原型:
func So(actual interface{}, assert assertion, expected ...interface{})
第二个参数为assertion,它的原型为:
type assertion func(actual interface{}, expected ...interface{}) string
当assertion的返回值为””时表示断言成功,否则表示失败,GoConvey框架中的相关代码为:
const (
success = ""
needExactValues = "This assertion requires exactly %d comparison values (you provided %d)."
needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0)."
)
我们简单实现一个assertion函数:
func ShouldGt(actual interface{}, expected ...interface{}) string {
if len(expected) == 0{
return "缺少参数 expected..."
}
val1 := actual.(int)
val2 := expected[0].(int)
if val1 <= val2{
return fmt.Sprintf("%d 不大于 %d", actual, expected[0])
}
return ""
}
写一个简单的测试:
func TestGt(t *testing.T) {
time.Sleep(time.Second*3) //仅仅是测试耗时
Convey("两数相乘是否大于100", t, func() {
So(88, ShouldGt, 100)
So(110, ShouldGt, 100)
})
}
根据ShouldGt的实现,Convey语句中第一个So将断言成功,第二个So将断言失败。
我们运行测试,查看执行结果,符合期望:
=== RUN TestGt
两数相乘是否大于100 ✘
Failures:
* /home/zhou/go/src/tttt/zhou_test.go
Line 59:
88 不大于 100
1 total assertion
--- FAIL: TestGt (3.00s)
FAIL
exit status 1
FAIL tttt 3.002s
要点归纳总结:
- import goconvey包时,前面加点号”.”,以减少冗余的代码
- 测试函数的名字必须以Test开头,而且参数类型必须为*testing.T
- 每个测试用例必须使用Convey函数包裹起来,推荐使用Convey语句的嵌套,即一个函数有一个测试函数,测试函数中嵌套两级Convey语句,第一级Convey语句对应测试函数,第二级Convey语句对应测试用例
- Convey语句的第三个参数习惯以闭包的形式实现,在闭包中通过So语句完成断言
- 使用GoConvey框架的 Web 界面特性,作为命令行的补充
- 在适当的场景下使用SkipConvey函数或SkipSo函数
- 当测试中有需要时,可以定制断言函数