關於 Go 測試,咱們應該知道測試方式(或者說測試手段)、測試對象及測試緣由。html
舉個例子。針對字符串分割函數(以下),實現單元測試。git
package goTest import "strings" // Split slices s into all substrings separated by sep and // returns a slice of the substrings between those separators. func Split(s, sep string) []string { var result []string i := strings.Index(s, sep) for i > -1 { result = append(result, s[:i]) s = s[i+len(sep):] i = strings.Index(s, sep) } return append(result, s) }
在當前目錄下且同樣的包名 goTest ,寫一個簡單的 go 測試函數,以下:github
package goTest import ( "reflect" "testing" ) func TestSplit(t *testing.T) { got := Split("a/b/c", "/") want := []string{"a", "b", "c"} if !reflect.DeepEqual(want, got) { t.Fatalf("expected: %v, got: %v", want, got) } }
測試函數必須以 Test 開頭, 且必須攜帶一個 *testing.T 參數。 t *testing.T 提供改測試函數的打印、跳過、失敗功能。sql
當前目錄下,執行 go test ,輸出以下:bash
> go test PASS ok goTest 0.005s
若是項目中存在多個 package ,若要執行全部包的測試能夠在項目根目錄下使用 go test ./... ,輸出以下(例子:github.com/mattn/go-sqlite3):app
> go test ./... ok github.com/mattn/go-sqlite3 14.693s ? github.com/mattn/go-sqlite3/upgrade [no test files]
仍是以字符串分割函數爲例, 獲取當前代碼測試覆蓋率方式以下:函數
> go test -coverprofile=c.out PASS coverage: 100.0% of statements ok goTest 0.005s
數據顯示覆蓋率爲 100% 。若要以 HTML 方式顯示可使用命令 go tool cover -html=c.out 。單元測試
【tip】 一行命令 cover 獲取當前目錄下的代碼測試覆蓋度。 在 ~/.bashrc 中添加以下命令:測試
cover () { local t=$(mktemp -t cover) go test $COVERFLAGS -coverprofile=$t $@ \ && go tool cover -func=$t \ && unlink $t }
執行後獲取的測試覆蓋度結果以下:優化
> cover PASS coverage: 100.0% of statements ok goTest 0.008s goTest/wwg_split.go:7: Split 100.0% total: (statements) 100.0%
問題:測試覆蓋率 100% ,結束了?
多個測試用例的狀況下,使用表組測試用例裝填。更改 TestSplit 以下:
func TestSplit(t *testing.T) { tests := []struct{ input string sep string want []string }{ {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}}, {input: "a/b/c", sep: ",", want: []string{"a/b/c"}}, {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}}, // trailing sep {input: "abc", sep: "/", want: []string{"abc"}}, } for _, tc := range tests { got := Split(tc.input, tc.sep) if !reflect.DeepEqual(tc.want, got) { t.Fatalf("expected: %v, got: %v", tc.want, got) } } }
增長測試用例 trailing sep 後,執行測試,結果以下:
> go test --- FAIL: TestSplit (0.00s) wwg_split_test.go:23: expected: [a b c], got: [a b c ] FAIL exit status 1 FAIL goTest 0.005s
根據該結果很難一會兒在表組測試用例中查出是哪條。能夠將 表組測試用例實現改成 map 形式 ,具體以下:
func TestSplit(t *testing.T) { tests := map[string]struct{ input string sep string want []string }{ "simple": {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}}, "wrong sep": {input: "a/b/c", sep: ",", want: []string{"a/b/c"}}, "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}}, "no sep": {input: "abc", sep: "/", want: []string{"abc"}}, } for name, tc := range tests { got := Split(tc.input, tc.sep) if !reflect.DeepEqual(tc.want, got) { t.Errorf("%s expected: %v, got: %v", name, tc.want, got) } } }
執行測試結果以下:
> go test --- FAIL: TestSplit (0.00s) wwg_split_test.go:23: trailing sep expected: [a b c], got: [a b c ] FAIL exit status 1 FAIL goTest 0.005s
Sub tests 使用,及 '%#v' format 使用,更改 TestSplit 以下:
func TestSplit(t *testing.T) { tests := map[string]struct{ input string sep string want []string }{ "simple": {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}}, "wrong sep": {input: "a/b/c", sep: ",", want: []string{"a/b/c"}}, "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}}, "no sep": {input: "abc", sep: "/", want: []string{"abc"}}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { got := Split(tc.input, tc.sep) if !reflect.DeepEqual(tc.want, got) { t.Fatalf("%s expected: %#v, got: %#v", name, tc.want, got) } }) } }
測試結果以下:
> go test --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/trailing_sep (0.00s) wwg_split_test.go:24: trailing sep expected: []string{"a", "b", "c"}, got: []string{"a", "b", "c", ""} FAIL exit status 1 FAIL goTest 0.005s
更好的打印格式,能夠訪問:
使用 google/go-cmp 優化打印, 更改 TestSplit 以下:
for name, tc := range tests { t.Run(name, func(t *testing.T) { got := Split(tc.input, tc.sep) diff := cmp.Diff(tc.want, got) if diff != "" { t.Fatalf(diff) } }) }
執行測試結果以下:
> go test --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/trailing_sep (0.00s) wwg_split_test.go:29: []string{ "a", "b", "c", + "", } FAIL exit status 1 FAIL goTest 0.005s
修復bug後 Split 代碼以下:
// Split slices s into all substrings separated by sep and // returns a slice of the substrings between those separators. func Split(s, sep string) []string { var result []string i := strings.Index(s, sep) for i > -1 { result = append(result, s[:i]) s = s[i+len(sep):] i = strings.Index(s, sep) } **if len(s) > 0 {** result = append(result, s) } return result** }
執行測試,結果以下:
> go test PASS ok goTest 0.006s > cover PASS coverage: 100.0% of statements ok goTest 0.006s goTest/wwg_split.go:7: Split 100.0% total: (statements) 100.0%
Q_1:Go 應該測試全部因子嗎?
A_1:顯然不是。
Q_2:什麼時候編寫測試? 1.編碼完成後? 2.編碼前? 3.其餘人遍寫測試,像QA、TE? 4.項目設計人員編寫測試?
A_2:編碼的同時編寫測試代碼(TDD)Article TheThreeRulesOfTdd
Q_3:C 單元測試對象是 function ,Java 單元測試對象是 Class ,類內部的方法, Go 的單元測試對象是?
A_3:package 。測試行爲,而非實施。 "The public API of a package declare this is what(行爲) I do, not this is how(實施) I do it."
即便你不作代碼測試,別人也會作。本身發現 issues 總比別人發現來得好,不是嗎?
【注】部分資料源於GopherChina 2019 - 'How to write testable code'