Skip to main content

[Note] Golang Test 測試筆記

# 檢視測試覆蓋以及沒執行到的程式碼
# 來源:https://link.medium.com/C5pFX18tnab
$ cd go-server
$ go test -coverprofile cover.out ./...
$ go tool cover -html=cover.out -o cover.html
$ open cover.html

CLI#

# -v 顯示詳細測試結果
# -cover 顯示測試覆蓋率
# -failfast 發生錯誤就停止測試
# -coverprofile 產生測試結果的檔案
# -html 產生測試結果的 HTML 檔案
# -short 跳過有 t.Skip 或 testing.Short() 的函式
# 測試某個 package
$ go test <package-name> -v
$ go test sandbox/go-sandbox/car -v
# 測試專案內的所有檔案
$ go test ./...
# 測試某資料夾內的所有檔案
$ go test -cover -v ./car/...
# 只測試檔案中的某個 function
$ go test -run=TestCar_SetName -cover -v ./car/...
# 產生測試結果的檔案
$ go test -coverprofile=prof.out
$ go tool cover -html=prof.out
# 清除測試的 cache
$ go clean -testcache
# 避免多個 package 的 test 同時執行
$ go test -p 1 ./... # 限制 parallel 的數量為 1

建立測試#

  • 匯入 testing 這個 package
  • TestXXX 作為測試函式的名稱
  • 在測試的函式中,帶入參數 t *testing.T
  • 當有非預期的錯誤產生時,使用 t.Errorf()t.Fail 來表示測試失敗
  • 使用 go test 執行測試
package deck
import (
"testing"
)
func TestNewDeck(t *testing.T) {
d := newDeck()
// 檢驗 d 是否為 16
if len(d) != 16 {
// 錯誤訊息
t.Errorf("Expected deck length of 20, but got %v", len(d))
}
}

概念#

.
├── car
│   ├── car.go
│   └── car_test.go
├── go.mod
├── go.sum
└── main.go
  • 檔案命名:針對 car.go 的測試檔案,只需在相同資料夾中建立 car_test.go 的檔案即可。
  • 函式命名:測試用的函式名稱都會以 Test 作為開頭

使用 testing package#

// https://blog.wu-boy.com/2018/05/how-to-write-testing-in-golang/
func TestNew(t *testing.T) {
c, err := New("", 100)
if err != nil {
t.Fatal("got errors:", err)
}
if c == nil {
t.Error("car should be nil")
}
}
  • 在 go 中內建 testing package 可以使用,其中 testing.T 型別提供了 t.Fatalt.Error 這兩個方法,t.Fatal 會讓程式終止,t.Error 則不會

sub testing#

使用 t.Run(name, func(t *testing.T) {/* ... */}) 可以執行 sub test:

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
//t.Parallel()
c := &Car{
Name: tt.fields.Name,
Price: tt.fields.Price,
}
if got := c.SetName(tt.args.name); got != tt.want {
t.Errorf("SetName() = %v, want %v", got, tt.want)
}
})
}

使用 testify#

testify @ Github

資料庫(Database)測試#

API 測試#

測試具有 params 的 API#

測試具有 params 的 API 時,需要透過 ctx.Params 把參數傳入:

// 特別留意 params 的寫法
// 假設我們要呼叫的路由是 /api/outsources/5
// 直接寫在路由中會吃不到,要使用 ctx.Params 傳進去
// s.ctx.Params = gin.Params{{Key: "id", Value: "5"}}
func (s *OutsourceSuite) Test_UpdateOutsource_expectNotFound() {
s.ctx.Params = gin.Params{{Key: "id", Value: "5"}}
payload := `{"dataSyncAt": "2020-08-20T09:15:41.567Z", "dataSyncStatus": "OK"}`
s.withJSONData("PATCH", "/api/outsources", payload)
s.OutsourceAPI.UpdateOutsource(s.ctx)
assert.Equal(s.T(), http.StatusNotFound, s.recorder.Code)
}
func (s *OutsourceSuite) withJSONData(method string, endPoint string, payload string) {
s.ctx.Request = httptest.NewRequest(method, endPoint, strings.NewReader(payload))
s.ctx.Request.Header.Set("Content-Type", "application/json")
}

Response#

Response Header#

⚠️ 特別留意 w.WriteHeader() 一定要放在最後面,也就是所有 Header 都 Set 完後才呼叫 WriteHeader()

func TestUpdateRemainingQuota(t *testing.T) {
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Quota-Limit", "10000")
w.Header().Set("X-Quota-Remaining", "9998")
w.Header().Set("X-Quota-Time-To-Reset", "1603540800000")
w.WriteHeader(http.StatusOK)
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
pcc, err := NewClient(clientID, clientSecret, certPath, keyPath)
assert.NoError(t, err)
assert.NoError(t, pcc.updateRemainingQuota(resp))
expect := &Quota{
Limit: 10000,
Remaining: 9998,
TimeToReset: time.Date(2020, 10, 24, 12, 0, 0, 0, time.UTC),
}
assert.Equal(t, expect, pcc.Quota)
}

參考#

Last updated on