zoukankan      html  css  js  c++  java
  • Golang 语言的单元测试和性能测试(也叫 压力测试)

    Golang单元测试对文件名和方法名,参数都有很严格的要求。
    例如:
      1、文件名必须以xx_test.go命名
      2、方法必须是Test[^a-z]开头(T必须大写),func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。
      3、方法参数必须 t *testing.T

           4、测试用例会按照源代码中写的顺序依次执行

           5、函数中通过调用testing.TErrorErrorfFailNowFatalFatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。

    test的运行方式:

    go test -v github.com/welhzh/dago/test

    其中后面的test为test目录

    Go 提供了 TestMain(*testing.M) 的函数,它在需要的时候代替运行所有的测试。使用 TestMain() 函数时,您有机会在测试运行之前或之后插入所需的任何自定义代码,但唯一需要注意的是必须处理 flag 解析并使用测试结果调用 os.Exit()。这听起来可能很复杂,但实际上只有两行代码。

    测试的覆盖率

    go tool命令可以报告测试覆盖率统计。使用 go test -cover 测试覆盖率。

    可视化查看覆盖率:

    执行go tool cover -html=cover.out命令,会在/tmp目录下生成目录coverxxxxxxx,比如/tmp/cover404256298。目录下有一个 coverage.html文件。用浏览器打开coverage.html,即可以可视化的查看代码的测试覆盖情况。

    从内部测试

    golang中大多数测试代码都是被测试包的源码的一部分。这意味着测试代码可以访问包种未导出的符号以及内部逻辑。就像我们之前看到的那样。

    注:比如$GOROOT/src/pkg/path/path_test.go与path.go都在path这个包下。

    从外部测试

    有些时候,你需要从被测包的外部对被测包进行测试,比如测试代码在package foo_test下,而不是在package foo下。这样可以打破依赖循环。

    1、功能测试

    上一个完整的例子:

    // how to run: go test -v github.com/welhzh/dago/test
    package client
    
    import (
        "flag"
        "fmt"
        "os"
        "testing"
    
        "github.com/welhzh/dago/client"
    )
    
    func initTest() {
    }
    
    func destroyTest() {
    }
    // test example func TestSum(t *testing.T) { // 任选一种测试方式进行测试 // ================= test 方式一 ==================== var testInputs = []struct { slice []int // other inputs expect int }{ {[]int{1, 2, 3, 4, 5}, 15}, {[]int{1, 2, 3, 4, -5}, 5}, } for _, oneInput := range testInputs { actual := oneInput.slice[2] if actual != oneInput.expect { t.Errorf("Sum(%q, %q) = %v; want %v", oneInput, oneInput, actual, oneInput.expect) } } // ================================================= // ================= test 方式二 ==================== t.Run("[1,2,3,4,5]", testSumFunc(testInputs[0].slice, testInputs[0].expect)) t.Run("[1,2,3,4,-5]", testSumFunc(testInputs[1].slice, testInputs[1].expect)) // ================================================= } func testSumFunc(numbers []int, expected int) func(*testing.T) { return func(t *testing.T) { actual := numbers[4] if actual != expected { t.Error(fmt.Sprintf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, actual)) } } } func TestMain(m *testing.M) { fmt.Println("test begin ...") initTest() flag.Parse() exitCode := m.Run() destroyTest() fmt.Println("test end ...") // Exit os.Exit(exitCode) }

    格式形如:

     

    go test [-c] [-i] [build flags] [packages] [flags for test binary]

    参数解读:

    -c : 编译go test成为可执行的二进制文件,但是不运行测试。

    -i : 安装测试包依赖的package,但是不运行测试。

    关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空

    关于packages,调用go help packages,这些是关于包的管理,一般设置为空

    关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数

    -test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。

    -test.run pattern: 只跑哪些单元测试用例

    -test.bench patten: 只跑那些性能测试用例

    -test.benchmem : 是否在性能测试的时候输出内存情况

    -test.benchtime t : 性能测试运行的时间,默认是1s

    -test.cpuprofile cpu.out : 是否输出cpu性能分析文件

    -test.memprofile mem.out : 是否输出内存性能分析文件

    -test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件

    -test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,那就是不做打点了。

    你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。

    -test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-test.blockprofilerate=1,每一纳秒都打点记录一下

    -test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。

    -test.timeout t : 如果测试用例运行时间超过t,则抛出panic

    -test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理

    -test.short : 将那些运行时间较长的测试用例运行时间缩短

     

    2、性能测试

    如果需要进行性能测试,则函数开头使用Benchmark就可以了。

    //性能测试
    func BenchmarkFibonacci(b *testing.B) {
        for i := 0; i < b.N; i++ {
            Fibonacci(10)
        }
    }

    接下来执行这个性能测试:

    $ go test -bench=. lib
     PASS
     BenchmarkFibonacci 5000000 436 ns/op
     ok lib 2.608s

    其中第二行输出表示这个函数运行了5000000次,平均运行一次的时间是436ns。

    这个性能测试只测试参数为10的情况。如果有需要可以测试多个参数:

    //测试参数为5的性能
    func BenchmarkFibonacci5(b *testing.B) {
        for i := 0; i < b.N; i++ {
            Fibonacci(5)
        }
    }
     
    //测试参数为20的性能
    func BenchmarkFibonacci20(b *testing.B) {
        for i := 0; i < b.N; i++ {
            Fibonacci(20)
        }
    }

    运行一下:

    $ go test -bench=. lib
     PASS
     BenchmarkFibonacci 5000000 357 ns/op
     BenchmarkFibonacci5 100000000 29.5 ns/op
     BenchmarkFibonacci20 50000 44688 ns/op
     ok lib 7.824s

    如果性能测试的方法非常多,那需要的时间就会比较久。可以通过-bench=参数设置需要运行的性能测试的函数:

    $ go test -bench=Fibonacci20 lib
     PASS
     BenchmarkFibonacci20 50000 44367 ns/op
     ok lib 2.677s

    3、IO相关测试 (高级测试技术)

    testing/iotest包中实现了常用的出错的Reader和Writer,可供我们在io相关的测试中使用。主要有:

    触发数据错误dataErrReader,通过DataErrReader()函数创建

    读取一半内容的halfReader,通过HalfReader()函数创建

    读取一个byte的oneByteReader,通过OneByteReader()函数创建

    触发超时错误的timeoutReader,通过TimeoutReader()函数创建

    写入指定位数内容后停止的truncateWriter,通过TruncateWriter()函数创建

    读取时记录日志的readLogger,通过NewReadLogger()函数创建

    写入时记录日志的writeLogger,通过NewWriteLogger()函数创建

    4、黑盒测试 (高级测试技术)

    testing/quick包实现了帮助黑盒测试的实用函数 Check和CheckEqual。

    Check函数的第1个参数是要测试的只返回bool值的黑盒函数f,Check会为f的每个参数设置任意值并多次调用,如果f返回false,Check函数会返回错误值 *CheckError。Check函数的第2个参数 可以指定一个quick.Config类型的config,传nil则会默认使用quick.defaultConfig。quick.Config结构体包含了测试运行的选项。

    # /usr/local/go/src/math/big/int_test.go
    func checkMul(a, b []byte) bool {
     var x, y, z1 Int
     x.SetBytes(a)
     y.SetBytes(b)
     z1.Mul(&x, &y)
     
     var z2 Int
     z2.SetBytes(mulBytes(a, b))
     
     return z1.Cmp(&z2) == 0
    }
     
    func TestMul(t *testing.T) {
     if err := quick.Check(checkMul, nil); err != nil {
      t.Error(err)
     }
    }

    CheckEqual函数是比较给定的两个黑盒函数是否相等,函数原型如下:

    func CheckEqual(f, g interface{}, config *Config) (err error)

    5、http测试 (高级测试技术)

    net/http/httptest包提供了HTTP相关代码的工具,我们的测试代码中可以创建一个临时的httptest.Server来测试发送HTTP请求的代码:

    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()
     
    res, err := http.Get(ts.URL)
    if err != nil {
     log.Fatal(err)
    }
     
    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
     log.Fatal(err)
    }
     
    fmt.Printf("%s", greeting)

    还可以创建一个应答的记录器httptest.ResponseRecorder来检测应答的内容:

    handler := func(w http.ResponseWriter, r *http.Request) {
     http.Error(w, "something failed", http.StatusInternalServerError)
    }
     
    req, err := http.NewRequest("GET", "http://example.com/foo", nil)
    if err != nil {
     log.Fatal(err)
    }
     
    w := httptest.NewRecorder()
    handler(w, req)
     
    fmt.Printf("%d - %s", w.Code, w.Body.String())

    6、在进程里测试 (高级测试技术)

    当我们被测函数有操作进程的行为,可以将被测程序作为一个子进程执行测试。下面是一个例子:

    //被测试的进程退出函数
    func Crasher() {
     fmt.Println("Going down in flames!")
     os.Exit(1)
    }
     
    //测试进程退出函数的测试函数
    func TestCrasher(t *testing.T) {
     if os.Getenv("BE_CRASHER") == "1" {
      Crasher()
      return
     }
     cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")
     cmd.Env = append(os.Environ(), "BE_CRASHER=1")
     err := cmd.Run()
     if e, ok := err.(*exec.ExitError); ok && !e.Success() {
      return
     }
     t.Fatalf(

    7、竞争检测 (高级测试技术)

    当两个goroutine并发访问同一个变量,且至少一个goroutine对变量进行写操作时,就会发生数据竞争(data race)。

    为了协助诊断这种bug,Go提供了一个内置的数据竞争检测工具。

    通过传入-race选项,go tool就可以启动竞争检测。

    $ go test -race mypkg // to test the package
    $ go run -race mysrc.go // to run the source file
    $ go build -race mycmd // to build the command
    $ go install -race mypkg // to install the package

    一个数据竞争检测的例子:

    //testrace.go
    
    package main
    
    import “fmt” 
    import “time”
    
    func main() {
    
    var i int = 0
        go func() {
                for {
                        i++
                        fmt.Println("subroutine: i = ", i)
                        time.Sleep(1 * time.Second)
                }
        }()
    
        for {
                i++
                fmt.Println("mainroutine: i = ", i)
                time.Sleep(1 * time.Second)
        }
    }

    运行:

    $ go run -race testrace.go

    8、并发测试 (高级测试技术)testing with concurrency

    当测试并发代码时,总会有一种使用sleep的冲动。大多时间里,使用sleep既简单又有效。

    但大多数时间不是”总是“。

    我们可以使用Go的并发原语让那些奇怪不靠谱的sleep驱动的测试更加值得信赖。

    9、静态分析工具vet查找错误 (高级测试技术)

    vet工具用于检测代码中程序员犯的常见错误:

    – 错误的printf格式
    – 错误的构建tag
    – 在闭包中使用错误的range循环变量
    – 无用的赋值操作
    – 无法到达的代码
    – 错误使用mutex
    等等。

    使用方法:

    go vet [package]

    10、Mocks和fakes (高级测试技术)

    通过在代码中使用interface,Go可以避免使用mock和fake测试机制。

    例如,如果你正在编写一个文件格式解析器,不要这样设计函数:

    func Parser(f *os.File) error

    作为替代,你可以编写一个接受interface类型的函数:

    func Parser(r io.Reader) error

    和bytes.Buffer、strings.Reader一样,*os.File也实现了io.Reader接口。

  • 相关阅读:
    EasyUI前后端分离
    easyUI权限
    EasyUI入门
    MVC(增删改查)
    MYSQL01 CentOS7下搭建mysql5.6
    Linux02:CentOS7配置静态IP
    Linux01:CentOS7桥接模式主机和虚拟机ping不通问题解决
    windows环境01 xampp+phpwind环境搭建
    Python模块之目录
    Django
  • 原文地址:https://www.cnblogs.com/welhzh/p/8918781.html
Copyright © 2011-2022 走看看