本节介绍如何使用go自带的测试包testing以及第三方测试包对程序进行测试。

1. 单元测试

1.1 基本的测试方法

单元测试(unit test),就是一种为验证单元的正确性而设置的自动化测试,一个单元就是程序中的一个模块化部分。一般来说,一个单元通常会与程序中的一个函数或者一个方法相对应

在Go中,以_test.go作为后缀的文件会被当做测试文件,比如main_test.go会被视为main.go的测试文件。

下面看一个例子,假设我们在main包内,编写了一个JSON解码器函数decode

type Post struct{
    Id int `json:"id"`
    Content string `json:"content"`
}

func decode(filename string)(post Post,err error){
    jsonFile,err := os.Open(filename)
    if err!=nil{
        fmt.Println("Error opening JSON file:", err)
        return
    }
    defer jsonFile.Close()

    decoder:=json.NewDecoder(jsonFile)
    err = decoder.Decode(&post)
    if err != nil {
        fmt.Println("Error decoding JSON:", err)
        return
    }
    return
}

那么,我们接下来需要创建main_test.go,然后编写:

func TestDecode(t *testing.T){
    post,err := decode("post.json")
    if err != nil {
        t.Error(err)
    }
    if post.Id != 1 {
        t.Error("Wrong id, was expecting 1 but got", post.Id)
    }
}

测试文件与被测试的源码文件位于同一个包内,它唯一导入并使用的包为testing包。函数TestDecode是一个测试用例,它代表的是对decode函数的单元测试。

testing.T结构拥有几个非常有用的函数:

  • Log——将给定的文本记录到错误日志里面,与fmt.Println类似;
  • Logf——根据给定的格式,将给定的文本记录到错误日志里面,与fmt.Printf类似;
  • Fail——将测试函数标记为“已失败”,但允许测试函数继续执行;
  • FailNow——将测试函数标记为“已失败”并停止执行测试函数。

运行测试用例的方法是,在控制台输入:

go test

然后就能看到:

D:\Go_Practice>go test
PASS
ok      _/D_/Go_Practice        4.478s

1.2 跳过部分测试

有些API暂时没有实现,或者某些功能现在不用测试,因此我们需要跳过。跳过的办法是调用skip

func TestLongRunningTest(t *testing.T) {  
    if testing.Short() {    
        t.Skip("Skipping long-running test in short mode")  
    }  
    time.Sleep(10 * time.Second)
}

如果我们使用:

go test -short

就能跳过被short引导的部分。


下面是测试部分:

当我们输入go test时,延时函数被执行:

PASS
ok      _/D_/Go_Practice        11.737s

给定short标志后:

PASS
ok      _/D_/Go_Practice        1.756s

1.3 并行测试

只要单元测试可以独立地进行,用户就可以通过并行地运行测试用例来提升测试的速度。

main_test.go文件所在的目录中创建一个名为parallel_test.go的文件,并在文件中键入代码:

func TestParallel_1(t *testing.T){
    t.Parallel()
    time.Sleep(1*time.Second)
}
func TestParallel_2(t *testing.T){
    t.Parallel()
    time.Sleep(2*time.Second)
}

这个程序利用time.Sleep函数,以2个测试用例分别模拟了2个需要耗时1s、2s的任务。只要在终端中执行以下命令,Go就会以并行的方式运行测试:

go test –v –short –parallel 2

1.4 性能测试

性能测试也叫基准测试(benchmarking),和之前的功能测试不同,这种测试的目的是检验程序的性能。

基准测试用例也需要放置到以_test.go为后缀的文件中,并且每个基准测试函数都需要符合以下格式:

func BenchmarkXxx(*testing.B) { ... }

比如我们自建一个bench_test.go,然后输入:

func BenchmarkDecode(b *testing.B){
    for i:=0;i<b.N;i++{
        decode("post.json")
    }
}

测试程序要做的就是将被测试的代码执行b.N次,以便准确地检测出代码的响应时间,其中b.N的值将根据被执行的代码而改变。

为了运行基准测试用例,用户需要在执行go test命令时使用基准测试标志-bench

go test -v -short –bench .

点号表示运行目录下的所有基准测试文件,测试结果如下:

=== RUN   TestDecode
--- PASS: TestDecode (0.00s)
=== RUN   TestLongRunningTest
--- SKIP: TestLongRunningTest (0.00s)
    main_test.go:20: Skipping long-running test in short mode
=== RUN   TestParallel_1
=== PAUSE TestParallel_1
=== RUN   TestParallel_2
=== PAUSE TestParallel_2
=== CONT  TestParallel_1
=== CONT  TestParallel_2
--- PASS: TestParallel_1 (1.00s)
--- PASS: TestParallel_2 (2.00s)
goos: windows
goarch: amd64
BenchmarkDecode-8          31851             40615 ns/op
PASS
ok      _/D_/Go_Practice        5.401s

结果中的31851为测试时b.N的实际值,也就是函数被循环执行的次数。在这个例子中,迭代进行了31851次,并且每次耗费了40615ns,即0.040615ms。

在进行基准测试时,测试用例的迭代次数是由Go自行决定的,测试程序将进行足够多次的迭代,直到获得一个准确的测量值为止。

上面的命令既运行了基准测试,也运行了功能测试。如果需要,用户也可以通过运行标志-run来忽略功能测试。

go test -run x -bench .

2. HTTP测试

前面介绍的是如何测试Go的普通程序,这一篇介绍如何进行Web方面的测试。

对Go Web应用的单元测试可以通过testing/httptest包来完成。这个包提供了模拟一个Web服务器所需的设施,用户可以利用net/http包中的客户端函数向这个服务器发送HTTP请求,然后获取模拟服务器返回的HTTP响应。

func TestHandleGet( t *testing.T){
    mux:=http.NewServeMux()
    mux.HandleFunc("/order",handleRequest)
    writer:=httptest.NewRecorder()
    request,_:=http.NewRequest("GET","order/20005",nil)
    mux.ServeHTTP(writer,request)
    if writer.Code != 200 {// 对记录器记载的响应结果进行检查
        t.Errorf("Response code is %v", writer.Code)
    }

    var order Orders
    json.Unmarshal(writer.Body.Bytes(),&order)
    if order.Order_num!=20005{
        t.Error("Cannot retrieve JSON post")
    }
}