實作目標
學習寫第一個Go的單元測試的作法與了解它的使用方式。
開始
為什麼要寫測試?
- 確保code執行過程中的正確性
- 確保後續維護不會出錯
- 以防edeg case的發生
準備程式執行檔與測試檔
在動手寫測試以前得先知道測試要寫在哪裡。一般在Go的環境中,測試檔案要另外寫在一個檔案並放在同一個路徑下,且命名要是程式執行檔名加 _test,例如我的程式實際執行在main.go,我的測試就會寫在main_test.go裡,並執行go test
來測試。
# 專案結構
myproject/
- calc.go
- calc_test.go
- main.go
- main_test.go
測試範例
這裡簡單在main.go寫一個回傳x+2結果的函式,並印出結果。接著在同一個目錄底下新增一個main_test.go,引入Go提供的testing套件,並寫一個test case。
# main.go
package main
import "fmt"
func Calculate(x int) (result int){
result = x + 2
return result
}
func main() {
fmt.Println("main start!!")
cal := Calculate(2)
fmt.Println("cal:", cal)
}
---
# main_test.go
package main
import "testing"
func TestCalculate(t *testing.T) {
if Calculate(2) != 4 { // 如果結果不為4,就噴出error裡的內容
t.Error("Expected 2 + 2 to equal 4")
}
}
由於,Calculate(2)結果必為4,所以通過所有測試。go test
會幫忙執行所有_test.go的test case,如果想要看到詳細的測試輸出(verbose Test output),例如那些功能被執行、那些通過可以執行go test -v
。
nacho@ubuntu:~/go/simpleTest$ go test
PASS
ok _/home/nacho/go/simpleTest 0.003s
此外,也可以設計Table driven testing來為不同範圍的輸入輸出值來進行測試,所以測試相同的函式不需要再做重複的copy&past,此外,設計多組測試也能降低Edge case跟bug在程式實際上線後才發現的情況。
# main_test.go
...
func TestTableCalculate(t * testing.T){
var tests = []struct {
input int
expected int
}{
{2, 4},
{-1, 1},
{0, 2},
{-5, -3},
{9999, 10001},
}
for _, test := range tests {
if output := Calculate(test.input); output != test.expected {
t.Error("Test Failed: input {}, expect {}, should receive: {}", test.input, test.expected, output)
}
}
}
詳細的測試輸出結果。
nacho@ubuntu:~/go/simpleTest$ go test -v
=== RUN TestCalculate
--- PASS: TestCalculate (0.00s)
=== RUN TestTableCalculate
--- PASS: TestTableCalculate (0.00s)
PASS
ok _/home/nacho/go/simpleTest 0.008s
Go還提供另一個方便的參數-cover,讓開發者能評估測試的覆蓋率(coverage)。覆蓋率是一個參考,但高覆蓋率也代表這支程式完全freebug或功能完全運作正常。不需要為所有的功能撰寫詳盡的測試,以追求高覆蓋率,而要將重點放在於重要/核心的business logic上,並確保能cover到更多可能的Edge cases發生才能發揮測試做大的價值。
nacho@ubuntu:~/go/simpleTest$ go test -cover
PASS
coverage: 40.0% of statements
ok _/home/nacho/go/simpleTest 0.003s