前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go高阶指南19,测试功能详解-下

Go高阶指南19,测试功能详解-下

作者头像
微客鸟窝
发布2021-10-08 15:40:40
3760
发布2021-10-08 15:40:40
举报
文章被收录于专栏:Go语言指北

书接上文,测试功能详解第二篇,本文主要介绍 Go 的子测试和 main 测试。

子测试

子测试可以使多个测试函数共用部分代码,比如有两个测试函数 A 和 B,有相同的初始化程序,使用子测试函数可以将A、B函数合并到一个函数中,对于它们相同的初始化程序便可以提取出来合并到一起。我们举例说明:

目录结构如下所示:

代码语言:javascript
复制
[ceshi]
  |--[gotest]
          |--subunit.go
  |--[gotest_test]
          |--subunit_test.go

源代码文件 unit.go 代码:

代码语言:javascript
复制
package gotest

func Add(a, b int) int {
 return a + b
}

测试文件 unit_test.go 代码:

代码语言:javascript
复制
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
}

执行测试:

代码语言:javascript
复制
$ 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() 把两个子测试合并起来,可以共享 setuptear-down 部分的代码。

子测试的命名

  1. Run() 方法第一个参数为子测试的名字;
  2. 实际上子测试的内部命名规则为:”<父测试名字>/<传递给Run的名字>“。
  3. 例如,传递给 Run() 的名字是“A=1”,那么子测试名字为“TestSub/A=1”。由上面的命令行输出中也可以看出。

过滤筛选

我们可以根据测试的名字来过滤一部分测试,例如使用 -run Sub/A=1 参数达到只执行 “A=1” 的子测试

代码语言:javascript
复制
$ 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() 可以保证其下的所有子测试执行结束后再返回。

代码语言:javascript
复制
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在所有子测试结束后才执行
代码语言:javascript
复制
$ 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

总结

  • 子测试适用于单元测试和性能测试;
  • 子测试可以控制并发;
  • 子测试可以共享setup和tear-down;

Main测试

Main测试,即声明一个 :func TestMain(m *testing.M) ,参数类型为 testing.M 指针,当前测试程序将不是直接执行各项测试,而是将测试交给 TestMain 调度。

源代码文件 unit.go 代码:

代码语言:javascript
复制
package gotest

func Add(a, b int) int {
 return a + b
}

测试文件 unit_test.go 代码:

代码语言:javascript
复制
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)
}

执行测试

代码语言:javascript
复制
$ go test unit_test.go -v
TestMain setup.
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
TestMain 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()方法内部还会再次解析参数,此处解析不会影响原测试过程。

图片及部分相关技术知识点来源于网络搜索,侵权删!

参考资料:

https://my.oschina.net/renhc/blog/3009124

https://blog.csdn.net/weixin_33827965/article/details/91699652

https://blog.csdn.net/ma2595162349/article/details/112437975

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微客鸟窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 子测试
    • 子测试的命名
      • 过滤筛选
        • 子测试并发
          • 总结
          • Main测试
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档