zoukankan      html  css  js  c++  java
  • Go测试--子测试

    简介

    简单的说,子测试提供一种在一个测试函数中执行多个测试的能力,比如原来有TestA、TestB和TestC三个测试函数,每个测试函数执行开始都需要做些相同的初始化工作,那么可以利用子测试将这三个测试合并到一个测试中,这样初始化工作只需要做一次。

    除此之外, 子测试还提供了诸多便利, 下面逐一说明

    简单的例子

    先看一个简单的例子,方便了解子测试的基本用法

    package gotest_test
    
    import (
        "testing"
        "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)
        }
    }
    
    // sub3 为子测试,只做加法测试
    func sub3(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和sub3三个子测试
    func TestSub(t *testing.T) {
        // setup code
    
        t.Run("A=1", sub1)
        t.Run("A=2", sub2)
        t.Run("B=1", sub3)
    
        // tear-down code
    }
    

    例子中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部分的代码。

    在执行时,使用-v 可以打印交互信息

    D:gopathsrcGo_baselesson	est_demogotest>go test -v sub_test.go gotest.go
    === RUN   TestSub
    === RUN   TestSub/A=1
    === RUN   TestSub/A=2
    === RUN   TestSub/B=1
    --- PASS: TestSub (0.00s)
        --- PASS: TestSub/A=1 (0.00s)
        --- PASS: TestSub/A=2 (0.00s)
        --- PASS: TestSub/B=1 (0.00s)
    PASS
    ok      command-line-arguments  0.045s
    

    从输出中可以看出,三个子测试都被执行到了,而且执行次序与调用次序一致。

    子测试命名规则

    通过上面的例子我们知道Run()方法第一个参数为子测试的名字,而实际上子测试的内部命名规则为:*<父测试名字>/<传递给Run的名字>*。比如,传递给Run()的名字是A=1,那么子测试名字为TestSub/A=1。这个在上面的命令行输出中也可以看出。

    过滤筛选

    通过测试的名字,可以在执行中过滤掉一部分测试。 比如,只执行上例中A=*的子测试,那么执行时使用-run Sub/A=参数即可

    D:gopathsrcGo_baselesson	est_demogotest>go test -v sub_test.go gotest.go -run=Sub/A=
    === 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  0.074s
    

    上例中,使用参数-run Sub/A=则只会执行TestSub/A=1和TestSub/A=2两个子测试。
    对于子性能测试则使用-bench参数来筛选,此处不再赘述。

    注意:此处的筛选不是严格的正则匹配,而是包含匹配。比如,-run A=那么所有测试(含子测试)的名字中如果包含 A= 则会被选中执行。

    子测试并发

    前面提到的多个子测试共享setup和teardown有一个前提是子测试没有并发,如果子测试使用t.Parallel()指定并发,那么就没办法共享teardown了,因为执行顺序很可能是setup->子测试1->teardown->子测试2…。

    如果子测试可能并发,则可以把子测试通过Run()再嵌套一层,Run()可以保证其下的所有子测试执行结束后再返回。

    为便于说明,我们创建文件subparallel_test.go用于说明:

    package gotest_test
    
    import (
        "fmt"
        "testing"
        "time"
    )
    
    // 并发子测试,无实际测试工作,仅用于演示
    func parallelTest1(t *testing.T) {
        t.Parallel()
        time.Sleep(3 * time.Second)
        fmt.Println("parallel test 1")
    }
    
    // 并发子测试,无实际测试工作,仅用于演示
    func parallelTest2(t *testing.T) {
        t.Parallel()
        time.Sleep(2 * time.Second)
        fmt.Println("parallel test 2")
    }
    
    // 并发子测试,无实际测试工作,仅用于演示
    func parallelTest3(t *testing.T) {
        t.Parallel()
        time.Sleep(1 * time.Second)
        fmt.Println("parallel test 3")
    }
    
    // TestSubParallel 通过把多个子测试放到一个组中并发执行,同时多个子测试可以共享setup和tear-down
    func TestSubParallel(t *testing.T) {
        // setup
        t.Logf("Setup")
    
        t.Run("group", func(t *testing.T) {
            t.Run("Test1", parallelTest1)
            t.Run("Test2", parallelTest2)
            t.Run("Test3", parallelTest3)
        })
    
        // tear down
        t.Logf("teardown")
    }
    

    上面三个子测试中分别sleep了3s、2s、1s用于观察并发执行顺序。通过Run()将多个子测试“封装”到一个组中,可以保证所有子测试全部执行结束后再执行tear-down。

    输出如下:

    D:gopathsrcGo_baselesson	est_demogotest>go test -v subparallel_test.go
    === RUN   TestSubParallel
        subparallel_test.go:33: Setup
    === RUN   TestSubParallel/group
    === RUN   TestSubParallel/group/Test1
    === PAUSE TestSubParallel/group/Test1
    === RUN   TestSubParallel/group/Test2
    === PAUSE TestSubParallel/group/Test2
    === RUN   TestSubParallel/group/Test3
    === PAUSE TestSubParallel/group/Test3
    === CONT  TestSubParallel/group/Test1
    === CONT  TestSubParallel/group/Test2
    === CONT  TestSubParallel/group/Test3
    parallel test 3
    parallel test 2
    parallel test 1
    === CONT  TestSubParallel
        subparallel_test.go:42: teardown
    --- PASS: TestSubParallel (3.01s)
        --- PASS: TestSubParallel/group (0.00s)
            --- PASS: TestSubParallel/group/Test3 (1.01s)
            --- PASS: TestSubParallel/group/Test2 (2.01s)
            --- PASS: TestSubParallel/group/Test1 (3.01s)
    PASS
    ok      command-line-arguments  3.050s
    

    通过该输出可以看出:

    1. 子测试是并发执行的(Test1最先被执行却最后结束)
    2. tear-down在所有子测试结束后才执行

    总结

    1. 总测试适用于单元测试和性能测试
    2. 子测试可以控制并发
    3. 子测试提供一种类似table-driven(表格驱动测试)风格的测试
    4. 子测试可以共享setup 和 tear-down;
    ♥永远年轻,永远热泪盈眶♥
  • 相关阅读:
    分享一个Fluent风格的邮件发送封装类
    写一个ActionFilter检测WebApi接口请求和响应
    一道有趣的面试题,小鸟和火车的问题
    Centos7 查看Mysql配置文件
    Centos7 grep命令简介
    Centos7 网络配置
    django之python3.4及以上连接mysql的一些问题记录
    NetCore log4net 集成以及配置日志信息不重复显示或者记录
    ionic3中关于Ionic ui component使用的一些总结
    ionic2升级到ionic3并打包APK
  • 原文地址:https://www.cnblogs.com/failymao/p/15026186.html
Copyright © 2011-2022 走看看