go test - goconvey
ddatsh
GoConvey 简介
GoConvey 网站 : https://smartystreets.github.io/goconvey
兼容 Go 内置 testing库,丰富的断言函数,彩色的控制台输出,提供命令行工具,更加直观的 Web 界面,测试会自动运行
go install github.com/smartystreets/goconvey@latest
运行 goconvey
,可以在本地运行一个强大 Web 图形界面,配合内置的 testing 使用,以提高开发效率
基本使用
产品代码
实现判断两个字符串切片是否相等的函数 StringSliceEqual,主要逻辑包括:
- 两个字符串切片长度不相等时,返回 false
- 两个字符串切片一个是 nil,另一个不是 nil 时,返回 false
- 遍历两个切片,比较对应索引的两个切片元素值,如果不相等,返回 false
- 否则,返回 true
func StringSliceEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
if (a == nil) != (b == nil) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
if (a == nil) != (b == nil) {
return false
}
实例化a和b,[]string{} 和 []string(nil),这时两个字符串切片的长度都是 0,但不相等
测试代码
正常情况的测试用例:
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStringSliceEqual(t *testing.T) {
Convey("TestStringSliceEqual should return true when a != nil && b != nil", t, func() {
a := []string{"hello", "goconvey"}
b := []string{"hello", "goconvey"}
So(StringSliceEqual(a, b), ShouldBeTrue)
})
}
go test -v
=== RUN TestStringSliceEqual
TestStringSliceEqual should return true when a != nil && b != nil ✔
1 total assertion
--- PASS: TestStringSliceEqual (0.00s)
PASS
ok example 0.002s
So(StringSliceEqual(a, b), ShouldBeFalse) 故意不过后
go test -v -trimpath
=== RUN TestStringSliceEqual
TestStringSliceEqual should return true when a != nil && b != nil ✘
Failures:
* example/slice_test.go
Line 13:
Expected: false
Actual: true
1 total assertion
--- FAIL: TestStringSliceEqual (0.00s)
FAIL
exit status 1
FAIL example 0.003s
-trimpath 可去本地目录信息
再补充 3 个测试用例:
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStringSliceEqual(t *testing.T) {
Convey("TestStringSliceEqual should return true when a != nil && b != nil", t, func() {
a := []string{"hello", "goconvey"}
b := []string{"hello", "goconvey"}
So(StringSliceEqual(a, b), ShouldBeTrue)
})
Convey("TestStringSliceEqual should return true when a == nil && b == nil", t, func() {
So(StringSliceEqual(nil, nil), ShouldBeTrue)
})
Convey("TestStringSliceEqual should return false when a == nil && b != nil", t, func() {
a := []string(nil)
b := []string{}
So(StringSliceEqual(a, b), ShouldBeFalse)
})
Convey("TestStringSliceEqual should return false when a != nil && b != nil", t, func() {
a := []string{"hello", "world"}
b := []string{"hello", "goconvey"}
So(StringSliceEqual(a, b), ShouldBeFalse)
})
}
每一个 Convey 语句对应一个测试用例,一个函数的多个测试用例可以通过一个测试函数的多个 Convey 语句来呈现
=== RUN TestStringSliceEqual
TestStringSliceEqual should return true when a != nil && b != nil ✔
1 total assertion
TestStringSliceEqual should return true when a == nil && b == nil ✔
2 total assertions
TestStringSliceEqual should return false when a == nil && b != nil ✔
3 total assertions
TestStringSliceEqual should return false when a != nil && b != nil ✔
4 total assertions
--- PASS: TestStringSliceEqual (0.00s)
PASS
ok example 0.003s
Convey 语句的嵌套
Convey 语句可以无限嵌套,以体现测试用例之间的关系。只有最外层的 Convey 需要传入 *testing.T 类型的变量 t
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStringSliceEqual(t *testing.T) {
Convey("TestStringSliceEqual", t, func() {
Convey("should return true when a != nil && b != nil", func() {
a := []string{"hello", "goconvey"}
b := []string{"hello", "goconvey"}
So(StringSliceEqual(a, b), ShouldBeTrue)
})
Convey("should return true when a == nil && b == nil", func() {
So(StringSliceEqual(nil, nil), ShouldBeTrue)
})
Convey("should return false when a == nil && b != nil", func() {
a := []string(nil)
b := []string{}
So(StringSliceEqual(a, b), ShouldBeFalse)
})
Convey("should return false when a != nil && b != nil", func() {
a := []string{"hello", "world"}
b := []string{"hello", "goconvey"}
So(StringSliceEqual(a, b), ShouldBeFalse)
})
})
}
=== RUN TestStringSliceEqual
TestStringSliceEqual
should return true when a != nil && b != nil ✔
should return true when a == nil && b == nil ✔
should return false when a == nil && b != nil ✔
should return false when a != nil && b != nil ✔
4 total assertions
--- PASS: TestStringSliceEqual (0.00s)
PASS
ok example 0.003s
BDD 风格
一种三层嵌套的惯用法,核心点是通过 GWT(Given…When…Then)格式来描述测试用例,示例:
func TestStringSliceEqualIfBothNotNil(t *testing.T) {
Convey("Given two string slice which are both not nil", t, func() {
a := []string{"hello", "goconvey"}
b := []string{"hello", "goconvey"}
Convey("When the comparision is done", func() {
result := StringSliceEqual(a, b)
Convey("Then the result should be true", func() {
So(result, ShouldBeTrue)
})
})
})
}
=== RUN TestStringSliceEqualIfBothNotNil
Given two string slice which are both not nil
When the comparision is done
Then the result should be true ✔
1 total assertion
--- PASS: TestStringSliceEqualIfBothNotNil (0.00s)
PASS
ok example 0.003s
按 GWT 格式写测试用例时,每一组 GWT 对应一条测试用例,即最内层的 Convey 语句不像两层嵌套时可以有多个,而是只能有一个 Convey 语句
使用测试套的形式,一个测试函数包含多条用例,每条用例使用 Convey 语句四层嵌套的惯用法:
func TestStringSliceEqual(t *testing.T) {
Convey("TestStringSliceEqualIfBothNotNil", t, func() {
Convey("Given two string slice which are both not nil", func() {
a := []string{"hello", "goconvey"}
b := []string{"hello", "goconvey"}
Convey("When the comparision is done", func() {
result := StringSliceEqual(a, b)
Convey("Then the result should be true", func() {
So(result, ShouldBeTrue)
})
})
})
})
Convey("TestStringSliceEqualIfBothNil", t, func() {
Convey("Given two string slice which are both nil", func() {
var a []string = nil
var b []string = nil
Convey("When the comparision is done", func() {
result := StringSliceEqual(a, b)
Convey("Then the result should be true", func() {
So(result, ShouldBeTrue)
})
})
})
})
Convey("TestStringSliceNotEqualIfNotBothNil", t, func() {
Convey("Given two string slice which are both nil", func() {
a := []string(nil)
b := []string{}
Convey("When the comparision is done", func() {
result := StringSliceEqual(a, b)
Convey("Then the result should be false", func() {
So(result, ShouldBeFalse)
})
})
})
})
Convey("TestStringSliceNotEqualIfBothNotNil", t, func() {
Convey("Given two string slice which are both not nil", func() {
a := []string{"hello", "world"}
b := []string{"hello", "goconvey"}
Convey("When the comparision is done", func() {
result := StringSliceEqual(a, b)
Convey("Then the result should be false", func() {
So(result, ShouldBeFalse)
})
})
})
})
}
断言方法
一般相等类
So(thing1, ShouldEqual, thing2)
So(thing1, ShouldNotEqual, thing2)
So(thing1, ShouldResemble, thing2) // 用于数组、切片、map和结构体相等
So(thing1, ShouldNotResemble, thing2)
So(thing1, ShouldPointTo, thing2)
So(thing1, ShouldNotPointTo, thing2)
So(thing1, ShouldBeNil)
So(thing1, ShouldNotBeNil)
So(thing1, ShouldBeTrue)
So(thing1, ShouldBeFalse)
So(thing1, ShouldBeZeroValue)
数字数量比较类
So(1, ShouldBeGreaterThan, 0)
So(1, ShouldBeGreaterThanOrEqualTo, 0)
So(1, ShouldBeLessThan, 2)
So(1, ShouldBeLessThanOrEqualTo, 2)
So(1.1, ShouldBeBetween, .8, 1.2)
So(1.1, ShouldNotBeBetween, 2, 3)
So(1.1, ShouldBeBetweenOrEqual, .9, 1.1)
So(1.1, ShouldNotBeBetweenOrEqual, 1000, 2000)
So(1.0, ShouldAlmostEqual, 0.99999999, .0001) // tolerance is optional; default 0.0000000001
So(1.0, ShouldNotAlmostEqual, 0.9, .0001)
包含类
So([]int{2, 4, 6}, ShouldContain, 4)
So([]int{2, 4, 6}, ShouldNotContain, 5)
So(4, ShouldBeIn, ...[]int{2, 4, 6})
So(4, ShouldNotBeIn, ...[]int{1, 3, 5})
So([]int{}, ShouldBeEmpty)
So([]int{1}, ShouldNotBeEmpty)
So(map[string]string{"a": "b"}, ShouldContainKey, "a")
So(map[string]string{"a": "b"}, ShouldNotContainKey, "b")
So(map[string]string{"a": "b"}, ShouldNotBeEmpty)
So(map[string]string{}, ShouldBeEmpty)
So(map[string]string{"a": "b"}, ShouldHaveLength, 1) // supports map, slice, chan, and string
字符串类
So("asdf", ShouldStartWith, "as")
So("asdf", ShouldNotStartWith, "df")
So("asdf", ShouldEndWith, "df")
So("asdf", ShouldNotEndWith, "df")
So("asdf", ShouldContainSubstring, "稍等一下") // optional 'expected occurences' arguments?
So("asdf", ShouldNotContainSubstring, "er")
So("adsf", ShouldBeBlank)
So("asdf", ShouldNotBeBlank)
panic 类
So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "") // or errors.New("something")
So(func(), ShouldNotPanicWith, "") // or errors.New("something")
类型检查类
So(1, ShouldHaveSameTypeAs, 0)
So(1, ShouldNotHaveSameTypeAs, "asdf")
时间和时间间隔类
So(time.Now(), ShouldHappenBefore, time.Now())
So(time.Now(), ShouldHappenOnOrBefore, time.Now())
So(time.Now(), ShouldHappenAfter, time.Now())
So(time.Now(), ShouldHappenOnOrAfter, time.Now())
So(time.Now(), ShouldHappenBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldNotHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenWithin, duration, time.Now())
So(time.Now(), ShouldNotHappenWithin, duration, time.Now())
要点归纳
- import goconvey 包时,前面加点号 “.",以减少冗余的代码
- 每个测试用例使用 Convey 语句包裹起来,推荐使用 Convey 语句的嵌套,即一个函数有一个或多个测试函数,一个测试函数嵌套两层、三层或四层 Convey 语句
- 使用 GoConvey 框架的 Web 界面特性,作为命令行的补充
- 在适当的场景下使用 SkipConvey 函数或 SkipSo 函数
- 当测试中有需要时,可以定制断言函数