Go Test

测试代码的风格

如果要测试xxx.go中的函数,可以创建一个xxx_test.go作为测试文件,放在一个包下。

Testt *testing.T*testing.B*testing.M

测试指令

//该 package 下所有的测试用例都会被执行
go test
//-v 参数会显示每个用例的测试结果,另外 -cover 参数可以查看覆盖率。
go test -v 
go test -cover
//如果只想运行其中的一个用例,例如 TestAdd,可以用 -run 参数指定,该参数支持通配符 *,和部分正则表达式,例如 ^、$。
go test -run TestAdd -v

划分子测试

func TestMul(t *testing.T) {
   t.Run("pos", func(t *testing.T) {
      if Mul(2, 3) != 6 {
         t.Fatal("fail")
      }
   })
   t.Run("neg", func(t *testing.T) {
      if Mul(2, -3) != -6 {
         t.Fatal("fail")
      }
   })
}
t.Fatal/t.Fatalft.Error/t.Errorf
func TestMul2(t *testing.T) {
   cases := []struct {
      Name           string
      A, B, Expected int
   }{
      {"pos", 2, 3, 6},
      {"neg", 2, -3, -6},
      {"zero", 2, 0, 0},
   }

   for _, c := range cases {
      t.Run(c.Name, func(t *testing.T) {
         if ans := Mul(c.A, c.B); ans != c.Expected {
            t.Fatalf("%d * %d expected %d, but %d got",
               c.A, c.B, c.Expected, ans)
         }
      })
   }
}

如果有重复的逻辑,可以抽取出来作为帮助函数,增加可读性和可维护性。

package main

import "testing"

type calcCase struct{ A, B, Expected int }

func createMulTestCase(t *testing.T, c *calcCase) {
        // t.Helper()
        if ans := Mul(c.A, c.B); ans != c.Expected {
                t.Fatalf("%d * %d expected %d, but %d got",
                        c.A, c.B, c.Expected, ans)
        }

}

func TestMul(t *testing.T) {
        createMulTestCase(t, &calcCase{2, 3, 6})
        createMulTestCase(t, &calcCase{2, -3, -6})
        createMulTestCase(t, &calcCase{2, 0, 1}) // wrong case
}

setup and teardown

如果在测试文件中,测试用例前后的逻辑是相同的,一般写在setup和teardown中。比如执行之前要实例化待测试对象;在执行之后做一些资源回收。

func setup() {
        fmt.Println("Before all tests")
}

func teardown() {
        fmt.Println("After all tests")
}

func Test1(t *testing.T) {
        fmt.Println("I'm test1")
}

func Test2(t *testing.T) {
        fmt.Println("I'm test2")
}

func TestMain(m *testing.M) {
        setup()
        code := m.Run()
        teardown()
        os.Exit(code)
}
TestMainm.Run()os.Exit()Test1m.Run()m.Run()

网络测试

比如我们要测试某个API接口的handler,可以建立一个真实的网络连接进行测试。

net.Listen("tcp", "127.0.0.1:0")http.Serve(ln, nil)http.Gethttpnet
// test code
import (
        "io/ioutil"
        "net"
        "net/http"
        "testing"
)

func handleError(t *testing.T, err error) {
        t.Helper()
        if err != nil {
                t.Fatal("failed", err)
        }
}

func TestConn(t *testing.T) {
        ln, err := net.Listen("tcp", "127.0.0.1:0")
        handleError(t, err)
        defer ln.Close()

        http.HandleFunc("/hello", helloHandler)
        go http.Serve(ln, nil)

        resp, err := http.Get("http://" + ln.Addr().String() + "/hello")
        handleError(t, err)

        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        handleError(t, err)

        if string(body) != "hello world" {
                t.Fatal("expected hello world, but got", string(body))
        }
}
net/http/httptest
func TestConn(t *testing.T) {
        req := httptest.NewRequest("GET", "http://example.com/foo", nil)
        w := httptest.NewRecorder()
        helloHandler(w, req)
        bytes, _ := ioutil.ReadAll(w.Result().Body)

        if string(bytes) != "hello world" {
                t.Fatal("expected hello world, but got", string(bytes))
        }
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("hello world"))
}

Benchmark 基准测试

基准测试用例定义:

Benchmarkb *testing.B-bench
func BenchmarkName(b *testing.B){
    // ...
}

基准测试报告每一行对应的含义如下:

type BenchmarkResult struct {
    N         int           // 迭代次数
    T         time.Duration // 基准测试花费的时间
    Bytes     int64         // 一次迭代处理的字节数
    MemAllocs uint64        // 总的分配内存的次数
    MemBytes  uint64        // 总的分配内存的字节数
}
b.ResetTimer()
func BenchmarkHello(b *testing.B) {
    ... // 耗时操作
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        fmt.Sprintf("hello")
    }
}

基准测试-并发性能

RunParallel
func BenchmarkParallel(b *testing.B) {
        templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
        b.RunParallel(func(pb *testing.PB) {
                var buf bytes.Buffer
                for pb.Next() {
                        // 所有 goroutine 一起,循环一共执行 b.N 次
                        buf.Reset()
                        templ.Execute(&buf, "World")
                }
        })
}