• golang测试


    1. 简述

    Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试。

    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中有个打点,那么生成的profilesample就会非常多。如果你设置为0,那就是不做打点了。

    你可以通过设置memprofilerate=1GOGC=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所在位代表,和nginxnginx_worker_cpu_affinity是一个道理

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

    -test.count:设置执行测试函数的次数,默认为1

    注:上述选项中“test.”可以省略。

    2. 单元测试

    go测试文件必须命名为_test.go,测试函数定义:func TestXXX(t *testing.T)test框架运行每个测试函数。假如测试失败,通过t.Errort.Fail返回错误。

    go test命令只能在一个目录下执行所有文件,所以代码和测试代码应在同一目录下。

    测试命令

    go test //运行当前目录下所有测试文件,测试文件必须命名为_test.go

    go test -v //显示详细测试信息

    go test -v -cover //显示测试覆盖情况,即测试函数占全部函数的百分比,可不测试所有函数

    测试文件规则

    • 文件名必须是_test.go结尾的

    • 你必须import testing这个包

    • 所有的测试用例函数必须是Test开头

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

    • 测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态

    • 测试格式:func TestXxx (t *testing.T)Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。

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

    // reverse.go
    package stringutil
    
    func Reverse(s string) string {
            r := []rune(s)
            for i,j := 0, len(r)-1; i< len(r)/2; i,j= i+1, j-1 {
                    r[i], r[j] = r[j], r[i]
            }
            return string(r)
    }
    
    func PrefixString(s, pre string) string {
            return pre+s
    }
    
    // reverse_test.go
    package stringutil
    import (
            "testing"
            "strconv"
    )
    
    func TestReverse(t *testing.T) {
            cases := []struct {
                    in, want string
            }{
                    {"Hello, world", "dlrow ,olleH"},
                    {"Hello, 世界", "界世 ,olleH"},
                    {"", ""},
            }
            for _, c := range cases {
                    got := Reverse(c.in)
                    if got != c.want {
                            t.Errorf("Reverse(%q) == %q, wang %q", c.in, got, c.want)
                    }
            }
    }
    
    func TestReverse2(t *testing.T){
            s1 := "5556"
            s2 := "6555"
    
            sr := Reverse(s1)
            if sr != s2 {
                    t.Error("测试不通过")
            } else {
                    t.Log("测试通过")
            }
    }
    
    func BenchmarkReverse1(b *testing.B){
            for i := 0; i < b.N; i++{
                    Reverse(PrefixString(strconv.Itoa(100+i), "012"))
            }
    }
    
    func BenchmarkTimeConsume(b *testing.B){
            b.StopTimer()
            b.Log("Starting test Benchmark...
    ")
            b.StartTimer()
            for i := 0; i < b.N; i++{
                    Reverse(PrefixString(strconv.Itoa(100+i), "012"))
            }
    }

    运行结果:

    $ go test -v -cover
    === RUN   TestReverse
    --- PASS: TestReverse (0.00s)
    === RUN   TestReverse2
    --- PASS: TestReverse2 (0.00s)
        reverse_test.go:31: 测试通过
    PASS
    coverage: 80.0% of statements
    ok      github.com/yuxi-o/golang/stringutil     0.003s

    3. 压力测试

    压力测试用来检测函数(方法)的性能。

    • 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母。func BenchmarkXXX(b *testing.B) { ... }

    • go test不会默认执行压力测试的函数,如果要执行压力测试需要带上参数-test.bench,语法:-test.bench="test_name_regex",例如go test -test.bench=".*"表示测试全部的压力测试函数

    • 在压力测试用例中,请记得在循环体内使用testing.B.N,以使测试可以正常的运行

    • 文件名也必须以_test.go结尾,可与单元测试在同一个文件

    • -benchmem可附加内存信息,观察内存分配情况。
    $ go test -bench=. -count=2
    goos: linux
    goarch: amd64
    pkg: github.com/yuxi-o/golang/stringutil
    BenchmarkReverse1-2              5000000               266 ns/op
    BenchmarkReverse1-2             10000000               262 ns/op
    BenchmarkTimeConsume-2           5000000               242 ns/op
    --- BENCH: BenchmarkTimeConsume-2
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
            ... [output truncated]
    BenchmarkTimeConsume-2          10000000               258 ns/op
    --- BENCH: BenchmarkTimeConsume-2
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
        reverse_test.go:43: Starting test Benchmark...
    
            ... [output truncated]
    PASS
    ok      github.com/yuxi-o/golang/stringutil     8.805s

    4. BDD

    行为驱动开发(Behavior-Driven Development)

    行为驱动开发的根基是一种“通用语言”。这种通用语言同时被客户和开发者用来定义系统的行为。由于客户和开发者使用同一种“语言”来描述同一个系统,可以最大程度避免表达不一致带来的问题。

    用业务领域的语言来描述:

    Given: a user is creating an account

    When: they specify an insecure password

    Then: they see a message, "Passwords must be at least 8 characters long with at least one letter, one number, and one symbol"

    goconvey框架实现了一个BDD: https://github.com/smartystreets/goconvey

    go get github.com/smartystreets/goconvey
    
    启动web ui
    $GOPATH/bin/goconvey

     

    5. 高级测试技术

    1. 一个例子程序

    outyet是一个web服务,用于宣告某个特定Go版本是否已经打标签发布了。其获取方法:

    go get github.com/golang/example/outyet

    注:go get执行后,cd $GOPATH/src/github.com/golang/example/outyet下,执行go run main.go。然后用浏览器打开http://localhost:8080即可访问该Web服务了。

    1. 测试Http客户端和服务端

    net/http/httptest包提供了许多帮助函数,用于测试那些发送或处理Http请求的代码。

    1. httptest.Server

    httptest.Server在本地回环网口的一个系统选择的端口上listen。它常用于端到端的HTTP测试。

    type Server struct {
        URL      string // base URL of form http://ipaddr:port with no trailing slash
        Listener net.Listener
        // TLS is the optional TLS configuration, populated with a new config
        // after TLS is started. If set on an unstarted server before StartTLS
        // is called, existing fields are copied into the new config.
        TLS *tls.Config
        // Config may be changed after calling NewUnstartedServer and
        // before Start or StartTLS.
        Config *http.Server
    }
    func NewServer(handler http.Handler) *Server
    func (*Server) Close() error
    1. httptest.Server实战

    下面代码创建了一个临时Http Server,返回简单的Hello应答:

     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)
    1. httptest.ResponseRecorder

    httptest.ResponseRecorderhttp.ResponseWriter的一个实现,用来记录变化,用在测试的后续检视中。

    type ResponseRecorder struct {
        Code      int           // the HTTP response code from WriteHeader
        HeaderMap http.Header   // the HTTP response headers
        Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
        Flushed   bool
    }
    1. httptest.ResponseRecorder实战

    向一个HTTP handler中传入一个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())
    1. 竞争检测(race detection)

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

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

    通过传入-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
    mainroutine: i 1
    ==================
    WARNING: DATA RACE
    Read by goroutine 5:
      main.func·001()
          /Users/tony/Test/Go/testrace.go:10 +0×49
    Previous write by main goroutine:
      main.main()
          /Users/tony/Test/Go/testrace.go:17 +0xd5
    Goroutine 5 (running) created at:
      main.main()
          /Users/tony/Test/Go/testrace.go:14 +0xaf
    ==================
    subroutine: i 2
    mainroutine: i 3
    subroutine: i 4
    mainroutine: i 5
    subroutine: i 6
    mainroutine: i 7
    subroutine: i 8
    1. 测试并发(testing with concurrency)

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

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

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

    1. 使用静态分析工具vet查找错误

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

        – 错误的printf格式

        – 错误的构建tag

        – 在闭包中使用错误的range循环变量

        – 无用的赋值操作

        – 无法到达的代码

        – 错误使用mutex

        等等。

    使用方法:

        go vet [package]

    1. 从内部测试

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

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

    1. 从外部测试

    有些时候,你需要从被测包的外部对被测包进行测试,比如测试代码在package foo_test下,而不是在package foo下。

    这样可以打破依赖循环,比如:

        – testing包使用fmt

        – fmt包的测试代码还必须导入testing

        – 于是,fmt包的测试代码放在fmt_test包下,这样既可以导入testing包,也可以同时导入fmt包。

    1. Mocksfakes

    通过在代码中使用interfaceGo可以避免使用mockfake测试机制。

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

    func Parser(f *os.File) error

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

    func Parser(r io.Reader) error

    bytes.Bufferstrings.Reader一样,*os.File也实现了io.Reader接口。

    1. 子进程测试

    有些时候,你需要测试的是一个进程的行为,而不仅仅是一个函数。例如:

    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("process ran with err %v, want exit status 1", err)
    }

    参考:

    1. https://studygolang.com/articles/2801 Golang测试技术

    2. https://studygolang.com/articles/11954 golang测试

    3. https://studygolang.com/articles/16801 go mock测试 模仿测试

    4. https://studygolang.com/articles/9391 go语言单元测试

    5. https://studygolang.com/articles/21445 go mock测试 httptest应用

    6. Go语言从入门到实战 蔡超 极客时间  
  • 相关阅读:
    使用AStyle进行代码格式化
    ubuntu14.04设置静态ip
    网络模拟器WANem使用配置图文教程
    ServerSocket 默认邦定IP
    shell判断文件是否存在
    linux文本模式下使用PPPOE拨号ADSL上网的方法
    几个国内速度最快的centos yum(更新源)
    linux命令执行返回值(附错误对照表)
    如何在java程序中调用linux命令或者shell脚本
    windows多线程详解
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/12006454.html
Copyright © 2020-2023  润新知