写点什么

Go 语言,测试功能详解 - 下

作者:微客鸟窝
  • 2021 年 11 月 16 日
  • 本文字数:2610 字

    阅读完需:约 9 分钟

Go 语言,测试功能详解 - 下

子测试

子测试可以使多个测试函数公用部分代码,比如有两个测试函数 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)PASSok      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() 把两个子测试合并起来,可以共享 setuptear-down 部分的代码。

子测试的命名

  1. Run() 方法第一个参数为子测试的名字;

  2. 实际上子测试的内部命名规则为:”<父测试名字>/<传递给 Run 的名字>“。

  3. 例如,传递给 Run() 的名字是“A=1”,那么子测试名字为“TestSub/A=1”。由上面的命令行输出中也可以看出。

过滤筛选

我们可以根据测试的名字来过滤一部分测试,例如使用 -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)PASSok      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}
复制代码


  • 子测试是并发执行的(Test1 最先被执行却最后结束)

  • tear-down 在所有子测试结束后才执行


$ 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)PASSok      command-line-arguments  2.584s
复制代码

总结

  • 子测试适用于单元测试和性能测试;

  • 子测试可以控制并发;

  • 子测试可以共享 setup 和 tear-down;

Main 测试

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 -vTestMain setup.=== RUN   TestAdd--- PASS: TestAdd (0.00s)PASSTestMain tear-down.ok      command-line-arguments  0.593s
复制代码


  • Main 测试课用于在整个测试程序做一些全局的 setup 和 Tear-down

  • 日志打印的两行分别对应 Setup 和 Tear-down 代码;

  • m.Run()即为执行所有的测试,m.Run() 的返回结果通过 os.Exit() 返回。

  • 如果所有测试均通过测试,m.Run() 返回 0,否同 m.Run() 返回 1,代表测试失败。

  • TestMain 执行时,命令行参数还未解析,如果测试程序需要依赖参数,可以使用 flag.Parse() 解析参数,m.Run()方法内部还会再次解析参数,此处解析不会影响原测试过程。

用户头像

微客鸟窝

关注

还未添加个人签名 2019.11.01 加入

公众号《微客鸟窝》笔者,目前从事web后端开发,涉及语言PHP、golang。获得美国《时代周刊》2006年度风云人物!

评论

发布
暂无评论
Go 语言,测试功能详解 - 下