• go进阶--测试



    Go语言提供了go test 命令行工具,使用该工具可以很方便的进行测试。

    不仅Go语言源码中大量使用go test,在各种开源框架中的应用也极为普遍。

    目前go test支持的测试类型有:

    • 单元测试
    • 性能测试
    • 示例测试

    1.单元测试

    1.1项目结构

    项目中单元测试的结构如下:

    [GoExpert]
    |--[src]
       |--[gotest]
          |--unit.go
          |--unit_test.go
    

    其中,util.go为源代码文件,unit_test.go为测试文件。要保证测试文件以_test.go结尾。

    1.2 源代码文件

    源代码文件unit.go中包含一个Add()方法,如下所示:

    package gotest
    
    // Add 方法用于演示go test使用
    func Add(a int, b int) int {
        return a + b
    }
    

    Add()方法仅提供两数加法,实际项目中不可能出现类似的方法,此处仅供单元测试示例。

    1.3 单元测试

    测试文件unit_test.go中包含一个测试方法TestAdd(),如下所示:

    package gotest_test
    
    import (
        "testing"
        "gotest"
    )
    
    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)
        }
    }
    

    通过package语句可以看到,测试文件属于gotest_test包,测试文件也可以跟源文件在同一个包,但常见的做法是创建一个包专用于测试,这样可以使测试文件和源文件隔离。GO源代码以及其他知名的开源框架通常会创建测试包,而且规则是在原包名上加上”_test”。

    测试函数命名规则为TestXxx,其中“Test”为单元测试的固定开头,go test只会执行以此为开头的方法。紧跟“Test”是以首字母大写的单词,用于识别待测试函数。

    测试函数参数并不是必须要使用的,但”testing.T”提供了丰富的方法帮助控制测试流程。

    t.Errorf()用于标记测试失败,标记失败还有几个方法,在介绍testing.T结构时再详细介绍。

    1.4 执行测试

    命令行下,使用go test命令即可启动单元测试,如下所示:

    $ go test
    PASS
    ok      gotest  0.378s
    
    # 还可以使用交互的方式启动
    $ go test -v unit_test.go
    === RUN   TestAdd
    --- PASS: TestAdd (0.00s)
            unit_test.go:8: 3
    PASS
    ok          command-line-arguments        0.004s
    

    通过打印可知,测试通过,花费时间为0.378s。

    1.5 单元测试总结

    • 测试文件名必须以_test.go结尾;
    • 测试函数名必须以TestXxx开始;
    • 命令行下使用go test即可启动测试;

    2.性能测试

    2.1 项目结构

    通常性能测试也称 基准测试, 目录结构如下

    [GoExpert]
    |--[src]
       |--[gotest]
          |--benchmark.go
          |--benchmark_test.go
    

    其中benchmark.go为源代码文件,benchmark_test.go为测试文件。

    2.2 源码文件

    源代码文件benchmark.go中包含MakeSliceWithoutAlloc()MakeSliceWithPreAlloc()两个方法,如下所示:

    package gotest
    
    // MakeSliceWithPreAlloc 不预分配
    func MakeSliceWithoutAlloc() []int {
        var newSlice []int
    
        for i := 0; i < 100000; i++ {
            newSlice = append(newSlice, i)
        }
    
        return newSlice
    }
    
    // MakeSliceWithPreAlloc 通过预分配Slice的存储空间构造
    func MakeSliceWithPreAlloc() []int {
        var newSlice []int
    
        newSlice = make([]int, 0, 100000)
        for i := 0; i < 100000; i++ {
            newSlice = append(newSlice, i)
        }
    
        return newSlice
    }
    

    两个函数分别用来测试切片预分配内存和不分配内存的性能问题。

    两个方法都会构造一个容量为100000的切片,所不同的是MakeSliceWithPreAlloc()会预先分配内存,而MakeSliceWithoutAlloc()不预先分配内存,二者理论上存在性能差异,本次就来测试一下二者的性能差异。

    2.3 测试文件

    测试文件benchmark_test.go中包含两个测试方法,用于测试源代码中两个方法的性能,测试文件如下所示:

    package gotest_test
    
    import (
        "testing"
        "gotest"
    )
    
    func BenchmarkMakeSliceWithoutAlloc(b *testing.B) {
        for i := 0; i < b.N; i++ {
            gotest.MakeSliceWithoutAlloc()
        }
    }
    
    func BenchmarkMakeSliceWithPreAlloc(b *testing.B) {
        for i := 0; i < b.N; i++ {
            gotest.MakeSliceWithPreAlloc()
        }
    }
    

    性能测试函数命名规则为BenchmarkXxx,其中Xxx为自定义的标识,需要以大写字母开始,通常为待测函数。

    testing.B提供了一系列的用于辅助性能测试的方法或成员,比如本例中的b.N表示循环执行的次数,而N值不用程序员特别关心,按照官方说法,N值是动态调整的,直到可靠地算出程序执行时间后才会停止,具体执行次数会在执行结束后打印出来。

    2.4 执行测试

    命令行下,使用go test -bench=.命令即可启动性能测试,如下所示:

    $ go test -bench=.
    BenchmarkMakeSliceWithoutAlloc-4            2000           1103822 ns/op
    BenchmarkMakeSliceWithPreAlloc-4            5000            328944 ns/op
    PASS
    ok      gotest  4.445s
    
    # 可以指定某个测试文件中运行性能测试
    $ go test -v -bench=. benchmark_test.go
    

    其中-bench为go test的flag,该flag指示go test进行性能测试,即执行测试文件中符合”BenchmarkXxx”规则的方法。
    紧跟flag的为flag的参数,本例表示执行当前所有的性能测试。

    通过输出可以直观的看出,BenchmarkMakeSliceWithoutAlloc执行了2000次,平均每次1103822纳秒,BenchmarkMakeSliceWithPreAlloc执行了5000次,平均每次328944纳秒。

    从测试结果上看,虽然构造切片很快,但通过给切片预分配内存,性能还可以进一步提升,符合预期。关于原理分析,请参考Slice相关章节。

    2.5 是如何工作的

    benchmark 用例的参数 b *testing.B,有个属性 b.N 表示这个用例需要运行的次数。b.N 对于每个用例都是不一样的。

    那这个值是如何决定的呢?b.N 从 1 开始,如果该用例能够在 1s 内完成,b.N 的值便会增加,再次执行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快。

    2.6 性能测试总结

    • 文件名必须以_test.go结尾;
    • 函数名必须以BenchmarkXxx开始;
    • 使用命令go test -bench=.即可开始性能测试;

    3. 示例测试(example test)

    示例测试平时很少用到,但是在很多流行的开源项目中能经常见到,它的目的是指导开发人员如何通过示例测试代码,衍生出对应功能的代码

    3.1 项目结构

    项目结构如下

    [GoExpert]
    |--[src]
       |--[gotest]
          |--example.go
          |--example_test.go
    

    3.2 源码文件

    源代码文件example.go中包含SayHello()SayGoodbye()``PrintNames()三个方法,如下所示:

    package gotest
    
    import "fmt"
    
    // SayHello 打印一行字符串
    func SayHello() {
        fmt.Println("Hello World")
    }
    
    // SayGoodbye 打印两行字符串
    func SayGoodbye() {
        fmt.Println("Hello,")
        fmt.Println("goodbye")
    }
    
    // PrintNames 打印学生姓名
    func PrintNames() {
        students := make(map[int]string, 4)
        students[1] = "Jim"
        students[2] = "Bob"
        students[3] = "Tom"
        students[4] = "Sue"
        for _, value := range students {
            fmt.Println(value)
        }
    }
    

    这几个方法打印内容略有不同,分别代表一种典型的场景:

    • SayHello():只有一行打印输出
    • SayGoodbye():有两行打印输出
    • PrintNames():有多行打印输出,且由于Map数据结构的原因,多行打印次序是随机的。

    3.3 测试文件

    测试文件example_test.go中包含3个测试方法,于源代码文件中的3个方法一一对应,测试文件如下所示:

    package gotest_test
    
    import "gotest"
    
    // 检测单行输出
    func ExampleSayHello() {
        gotest.SayHello()
        // OutPut: Hello World
    }
    
    // 检测多行输出
    func ExampleSayGoodbye() {
        gotest.SayGoodbye()
        // OutPut:
        // Hello,
        // goodbye
    }
    
    // 检测乱序输出
    func ExamplePrintNames() {
        gotest.PrintNames()
        // Unordered output:
        // Jim
        // Bob
        // Tom
        // Sue
    }
    

    例子测试函数命名规则为ExampleXxx,其中Xxx为自定义的标识,通常为待测函数名称。

    三个测试函数分别代表三种场景:

    • ExampleSayHello(): 待测试函数只有一行输出,使用 // OutPut: 检测。
    • ExampleSayGoodbye():待测试函数有多行输出,使用// OutPut: 检测,其中期望值也是多行。
    • ExamplePrintNames():待测试函数有多行输出,但输出次序不确定,使用// Unordered output:检测。

    注:字符串比较时会忽略前后的空白字符。

    3.4 执行测试

    命令行下,使用go test或go test example_test.go命令即可启动测试,如下所示:

    3.5 总结

    1. 例子测试函数名需要以Example开头;
    2. 检测单行输出格式为// Output: <期望字符串>
    3. 检测多行输出格式为// Output: <期望字符串> <期望字符串>,每个期望字符串占一行;
    4. 检测无序输出格式为// Unordered output: <期望字符串> <期望字符串>,每个期望字符串占一行;
    5. 测试字符串时会自动忽略字符串前后的空白字符;
    6. 如果测试函数中没有Output标识,则该测试函数不会被执行;
    7. 执行测试可以使用go test,此时该目录下的其他测试文件也会一并执行;
    8. 执行测试可以使用go test <xxx_test.go>,此时仅执行特定文件中的测试函数;
    ♥永远年轻,永远热泪盈眶♥
  • 相关阅读:
    从学算法体会如何更好的学习
    java数据结构与算法
    数据结构与算法资料汇总
    Oracle元数据查询总结
    Antlr词法分析之技巧——修改某个token
    动态规划公共子序列
    k8s笔记
    MiniDao1.9.0 版本发布,轻量级Java持久化框架
    autpoi 1.4.3版本发布—Excel傻瓜式API,快速实现Excel导入导出、Word模板导出
    喜讯!喜讯!JeecgBoot Github超 30000 Star—这个低代码平台你还不知道吗?
  • 原文地址:https://www.cnblogs.com/failymao/p/15022689.html
Copyright © 2020-2023  润新知