天天看點

Go語言編寫單元測試用例

Go單元測試示例

example/
   |--division.go
   |--division_test.go      

為什麼被測試檔案和測試檔案通常放到同一個檔案夾下以及同一個聲明包裡

通常情況下,我們把被測試的檔案與另外寫的測試檔案放到同一個聲明包裡面,稱為包内測試;

當然也可以把測試函數的檔案放到獨立的一個包裡面,稱為包外測試。

不過,包外測試源碼檔案存在一個弊端,那就是在它們的測試函數中無法測試被測源碼檔案中的包級私有的程式實體,比如包級私有的變量、函數和結構體類型。這是因為這兩者的所屬代碼包是不相同的。是以,一般很少會編寫包外測試源碼檔案。

單元測試規則

  • 測試檔案名必須是_test.go結尾的,這樣在執行go test的時候才會執行到相應的代碼
  • 你必須import testing這個包
  • 所有的測試用例函數必須是Test開頭
  • 測試用例會按照源代碼中寫的順序依次執行
  • 測試函數TestXxx(t *testing.T)隻有一個參數 t, 可以用 t 記錄錯誤或者是測試狀态
  • 測試函數的格式:func TestXxx (t *testing.T),Xxx部分可以為任意的字母數字的組合,但是首字母不能是小寫字母[a-z],例如Testintdiv是錯誤的函數名。
  • 函數中通過調用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,說明測試不通過,調用Log方法用來記錄測試的資訊。

單元測試寫法

兩種寫法:自己手寫、借助工具生成

方法1:自己手寫

等待被測試的檔案 division.go 如下,需要在同一個包裡寫一個測試檔案命名為mydivision_test.go

1 package math
 2 
 3 import (
 4     "errors"
 5 )
 6 
 7 func Division(a, b float64) (float64, error) {
 8     if b == 0 {
 9         return 0, errors.New("除數不能為0")
10     }
11 
12     return a / b, nil
13 }      

直接自己撸測試函數,這樣比較靈活想怎麼測就怎麼測

mydivision_test.go

1 package math
 2 
 3 import (
 4     "testing"
 5 )
 6 
 7 //用例1
 8 func Test_Division_1(t *testing.T) {
 9     if i, e := Division(6, 2); i != 3 || e != nil { 
10         t.Error("除法函數測試沒通過") // 如果不是如預期的那麼就報錯
11     } else {
12         t.Log("第一個測試通過了") //記錄一些你期望記錄的資訊
13     }
14 }
15 //用例2
16 func Test_Division_2(t *testing.T) {
17     t.Error("執行t.Error分支就會提示測試失敗")
18 }      

運作指令

go test -v .

結果如下

$ go test  -v .
=== RUN   Test_Division_1
--- PASS: Test_Division_1 (0.00s)        
        mydivision_test.go:11: 第一個測試通過了
=== RUN   Test_Division_2
--- FAIL: Test_Division_2 (0.00s)
        mydivision_test.go:16: 執行t.Error分支就會提示測試失敗
FAIL
exit status 1
FAIL    mytest/goTest   0.001s      

方法二:借助工具生成

步驟:

  1. 先安裝gotests工具包: 

    go get -v github.com/cweill/gotests/...

  2. gotests -all -w .

    為目前目錄所有檔案生成對應測試檔案

結果為division.go生成了division_test.go檔案如下,生成檔案的預設字首為對應的原檔案名

division_test.go,然後留出一片“// TODO: Add test cases.”區域來填用例

1 package math
 2 
 3 import "testing"
 4 
 5 func TestDivision(t *testing.T) {
 6     type args struct {
 7         a float64
 8         b float64
 9     }
10     tests := []struct {
11         name    string  //用例名字
12         args    args    //傳給被測函數的參數
13         want    float64 //預期傳回結果
14         wantErr bool    //用bool友善判斷是否傳回error,如果類型改為error反而不好判斷
15     }{
16         // TODO: Add test cases.
17         {"case 0", args{6, 2}, 3, false},
18         {"case 1", args{6, 0}, 0, true},//注意第二個用例是會傳回error的,因為除數不能為0,是以此處wantErr為true
19     }
20     for _, tt := range tests {
21         t.Run(tt.name, func(t *testing.T) {
22             got, err := Division(tt.args.a, tt.args.b)
23             if (err != nil) != tt.wantErr {
24                 t.Errorf("Division() error = %v, wantErr %v", err, tt.wantErr)
25                 return
26             }
27             if got != tt.want {
28                 t.Errorf("Division() = %v, want %v", got, tt.want)
29             }
30         })
31     }
32 }      

運作結果

兩個用例都通過了測試

$ go test -v .
=== RUN TestDivision
=== RUN TestDivision/case_0
=== RUN TestDivision/case_1
--- PASS: TestDivision (0.00s)
--- PASS: TestDivision/case_0 (0.00s)
--- PASS: TestDivision/case_1 (0.00s)
PASS
ok mytest/goTest 0.001s