书接上文,测试功能详解第二篇,本文主要介绍 Go 的子测试和 main 测试。
子测试可以使多个测试函数共用部分代码,比如有两个测试函数 A 和 B,有相同的初始化程序,使用子测试函数可以将A、B函数合并到一个函数中,对于它们相同的初始化程序便可以提取出来合并到一起。我们举例说明:
目录结构如下所示:
[ceshi]
|--[gotest]
|--subunit.go
|--[gotest_test]
|--subunit_test.go
源代码文件 unit.go 代码:
package gotest
func Add(a, b int) int {
return a + b
}
测试文件 unit_test.go 代码:
package gotest_test
import (
"testing"
"ceshi/gotest"
)
// sub1 为子测试,只做加法测试
func sub1(t *testing.T) {
var a = 1
var b = 2
var expected = 3
actual := gotest.Add(a, b)
if actual != expected {
t.Errorf("Add(%d, %d) = %d; expected: %d", a, b, actual, expected)
}
}
// sub2 为子测试,只做加法测试
func sub2(t *testing.T) {
var a = 1
var b = 2
var expected = 3
actual := gotest.Add(a, b)
if actual != expected {
t.Errorf("Add(%d, %d) = %d; expected: %d", a, b, actual, expected)
}
}
// TestSub 内部调用sub1、sub2 子测试
func TestSub(t *testing.T) {
// setup code
t.Run("A=1", sub1)
t.Run("A=2", sub2)
// tear-down code
}
执行测试:
$ go test subunit_test.go -v
=== RUN TestSub
=== RUN TestSub/A=1
=== RUN TestSub/A=2
--- PASS: TestSub (0.00s)
--- PASS: TestSub/A=1 (0.00s)
--- PASS: TestSub/A=2 (0.00s)
PASS
ok command-line-arguments 1.142s
示例中 TestSub() 通过 t.Run() 依次执行了两个子测试。t.Run() 函数声明如下:
func (t *T) Run(name string, f func(t *T)) bool
name
为子测试的名字, f
为子测试函数;Run()
一直阻塞到 f
执行结束后才返回,返回值为 f
的执行结果。Run()
会启动新的协程来执行 f
,并阻塞等待 f
执行结束才返回,除非 f
中使用了 t.Parallel()
设置子测试为并发。TestSub()
把两个子测试合并起来,可以共享 setup
和 tear-down
部分的代码。我们可以根据测试的名字来过滤一部分测试,例如使用 -run Sub/A=1
参数达到只执行 “A=1” 的子测试
$ go test subunit_test.go -v -run Sub/A=1
=== RUN TestSub
=== RUN TestSub/A=1
--- PASS: TestSub (0.00s)
--- PASS: TestSub/A=1 (0.00s)
PASS
ok command-line-arguments 0.633s
子测试可以使用 t.Parallel()
来指定并发,但是这样就不能共享 setup 和 teardown 部分程序了,因为执行顺序很可能是setup->子测试1->teardown->子测试2。
如果子测试可能并发,则可以把子测试通过 Run() 再嵌套一层, Run() 可以保证其下的所有子测试执行结束后再返回。
package gotest_test
import (
"testing"
"time"
)
// 并发 sub1 子测试演示
func parallelSub1(t *testing.T) {
t.Parallel()
time.Sleep(2 * time.Second)
//...
}
// 并发 sub2 子测试演示
func parallelSub2(t *testing.T) {
t.Parallel()
time.Sleep(1 * time.Second)
//...
}
// TestSub 内部调用sub1、sub2子测试
func TestSubParallel(t *testing.T) {
// setup code
t.Run("group", func(t *testing.T){
t.Run("Test1", parallelSub1)
t.Run("Test2", parallelSub2)
})
// tear-down code
}
$ go test subparallel_test.go -v run SubParallel
=== RUN TestSubParallel
subparallel_test.go:25: setup code
=== RUN TestSubParallel/group
=== RUN TestSubParallel/group/Test1
=== PAUSE TestSubParallel/group/Test1
=== RUN TestSubParallel/group/Test2
=== PAUSE TestSubParallel/group/Test2
=== CONT TestSubParallel/group/Test1
=== CONT TestSubParallel/group/Test2
=== CONT TestSubParallel
subparallel_test.go:33: tear-down code
--- PASS: TestSubParallel (2.00s)
--- PASS: TestSubParallel/group (0.00s)
--- PASS: TestSubParallel/group/Test2 (1.01s)
--- PASS: TestSubParallel/group/Test1 (2.00s)
PASS
ok command-line-arguments 2.584s
Main测试,即声明一个 :func TestMain(m *testing.M)
,参数类型为 testing.M 指针,当前测试程序将不是直接执行各项测试,而是将测试交给 TestMain 调度。
源代码文件 unit.go 代码:
package gotest
func Add(a, b int) int {
return a + b
}
测试文件 unit_test.go 代码:
package gotest_test
import (
"ceshi/gotest"
"fmt"
"os"
"testing"
)
func TestAdd(t *testing.T){
var a = 1
var b = 2
var expected = 3
actual := gotest.Add(a, b)
if actual != expected {
t.Errorf("Add(%d, %d) = %d; expected: %d", a, b, actual, expected)
}
}
// TestMain 用于主动执行各种测试,可以测试前后做setup和tear-down操作
func TestMain(m *testing.M) {
fmt.Println("TestMain setup.")
retCode := m.Run() // 执行测试,包括单元测试、性能测试和示例测试
fmt.Println("TestMain tear-down.")
os.Exit(retCode)
}
执行测试
$ go test unit_test.go -v
TestMain setup.
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
TestMain tear-down.
ok command-line-arguments 0.593s
图片及部分相关技术知识点来源于网络搜索,侵权删!
参考资料:
https://my.oschina.net/renhc/blog/3009124
https://blog.csdn.net/weixin_33827965/article/details/91699652
https://blog.csdn.net/ma2595162349/article/details/112437975