Files
build-web-application-with-…/zh-tw/11.3.md
2019-06-22 23:41:28 +08:00

175 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 11.3 Go 怎麼寫測試案例
開發程式其中很重要的一點是測試,我們如何保證程式碼的品質,如何保證每個函式是可執行,執行結果是正確的,又如何保證寫出來的程式碼效能是好的,我們知道單元測試的重點在於發現程式設計或實現的邏輯錯誤,使問題及早暴露,便於問題的定位解決,而效能測試的重點在於發現程式設計上的一些問題,讓線上的程式能夠在高併發的情況下還能保持穩定。本小節將帶著這一連串的問題來講解 Go 語言中如何來實現單元測試和效能測試。
Go 語言中自帶有一個輕量級的測試框架 `testing` 和自帶的`go test`命令來實現單元測試和效能測試,`testing`框架和其他語言中的測試框架類似,你可以基於這個框架寫針對相應函式的測試案例,也可以基於該框架寫相應的壓力測試案例,那麼接下來讓我們一一來看一下怎麼寫。
另外建議安裝[gotests](https://github.com/cweill/gotests)外掛自動產生測試程式碼:
```Go
go get -u -v github.com/cweill/gotests/...
```
## 如何編寫測試案例
由於`go test`命令只能在一個相應的目錄下執行所有檔案,所以我們接下來建立一個專案目錄`gotest`,這樣我們所有的程式碼和測試程式碼都在這個目錄下。
接下來我們在該目錄下面建立兩個檔案gotest.go 和 gotest_test.go
1. gotest.go:這個檔案裡面我們是建立了一個套件,裡面有一個函式實現了除法運算:
```Go
package gotest
import (
"errors"
)
func Division(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除數不能為 0")
}
return a / b, nil
}
```
2. gotest_test.go:這是我們的單元測試檔案,但是記住下面的這些原則:
- 檔名必須是`_test.go`結尾的,這樣在執行`go test`的時候才會執行到相應的程式碼
- 你必須 import `testing`這個包
- 所有的測試案例函式必須是 `Test` 開頭
- 測試案例會按照原始碼中寫的順序依次執行
- 測試函式`TestXxx()`的參數是`testing.T`,我們可以使用該型別來記錄錯誤或者是測試狀態
- 測試格式:`func TestXxx (t *testing.T)`,`Xxx`部分可以為任意的字母數字的組合,但是首字母不能是小寫字母[a-z],例如 `Testintdiv` 是錯誤的函式名。
- 函式中透過呼叫`testing.T``Error`, `Errorf`, `FailNow`, `Fatal`, `FatalIf`方法,說明測試不透過,呼叫 `Log` 方法用來記錄測試的資訊。
下面是我們的測試案例的程式碼:
```Go
package gotest
import (
"testing"
)
func Test_Division_1(t *testing.T) {
if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function
t.Error("除法函式測試沒透過") // 如果不是如預期的那麼就報錯
} else {
t.Log("第一個測試通過了") //記錄一些你期望記錄的資訊
}
}
func Test_Division_2(t *testing.T) {
t.Error("就是不透過")
}
```
我們在專案目錄下面執行`go test`,就會顯示如下資訊:
--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: 就是不透過
FAIL
exit status 1
FAIL gotest 0.013s
從這個結果顯示測試沒有透過,因為在第二個測試函式中我們寫死了測試不透過的程式碼`t.Error`,那麼我們的第一個函式執行的情況怎麼樣呢?預設情況下執行`go test`是不會顯示測試透過的資訊的,我們需要帶上參數`go test -v`,這樣就會顯示如下資訊:
=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: 第一個測試通過了
=== RUN Test_Division_2
--- FAIL: Test_Division_2 (0.00 seconds)
gotest_test.go:16: 就是不透過
FAIL
exit status 1
FAIL gotest 0.012s
上面的輸出詳細的展示了這個測試的過程,我們看到測試函式 1`Test_Division_1` 測試透過,而測試函式 2`Test_Division_2` 測試失敗了,最後得出結論測試不透過。接下來我們把測試函式 2 修改成如下程式碼:
```Go
func Test_Division_2(t *testing.T) {
if _, e := Division(6, 0); e == nil { //try a unit test on function
t.Error("Division did not work as expected.") // 如果不是如預期的那麼就報錯
} else {
t.Log("one test passed.", e) //記錄一些你期望記錄的資訊
}
}
```
然後我們執行`go test -v`,就顯示如下資訊,測試通過了:
=== RUN Test_Division_1
--- PASS: Test_Division_1 (0.00 seconds)
gotest_test.go:11: 第一個測試通過了
=== RUN Test_Division_2
--- PASS: Test_Division_2 (0.00 seconds)
gotest_test.go:20: one test passed. 除數不能為 0
PASS
ok gotest 0.013s
## 如何編寫壓力測試
壓力測試用來檢測函式(方法)的效能,和編寫單元功能測試的方法類似,此處不再贅述,但需要注意以下幾點:
- 壓力測試案例必須遵循如下格式,其中 XXX 可以是任意字母數字的組合,但是首字母不能是小寫字母
```Go
func BenchmarkXXX(b *testing.B) { ... }
```
- `go test`不會預設執行壓力測試的函式,如果要執行壓力測試需要帶上參數`-test.bench`,語法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示測試全部的壓力測試函式
- 在壓力測試案例中,請記得在迴圈體內使用`testing.B.N`,以使測試可以正常的執行
- 檔名也必須以`_test.go`結尾
下面我們建立一個壓力測試檔案 webbench_test.go程式碼如下所示
```Go
package gotest
import (
"testing"
)
func Benchmark_Division(b *testing.B) {
for i := 0; i < b.N; i++ { //use b.N for looping
Division(4, 5)
}
}
func Benchmark_TimeConsumingFunction(b *testing.B) {
b.StopTimer() //呼叫該函式停止壓力測試的時間計數
//做一些初始化的工作,例如讀取檔案資料,資料庫連線之類別的,
//這樣這些時間不影響我們測試函式本身的效能
b.StartTimer() //重新開始時間
for i := 0; i < b.N; i++ {
Division(4, 5)
}
}
```
我們執行命令`go test webbench_test.go -test.bench=".*"`,可以看到如下結果:
```
Benchmark_Division-4 500000000 7.76 ns/op 456 B/op 14 allocs/op
Benchmark_TimeConsumingFunction-4 500000000 7.80 ns/op 224 B/op 4 allocs/op
PASS
ok gotest 9.364s
```
上面的結果顯示我們沒有執行任何 `TestXXX` 的單元測試函式,顯示的結果只執行了壓力測試函式,第一條顯示了 `Benchmark_Division` 執行了 500000000 次,每次的執行平均時間是 7.76 納秒,第二條顯示了 `Benchmark_TimeConsumingFunction` 執行了 500000000每次的平均執行時間是 7.80 納秒。最後一條顯示總共的執行時間。
## 小結
透過上面對單元測試和壓力測試的學習,我們可以看到 `testing` 套件很輕量,編寫單元測試和壓力測試案例非常簡單,配合內建的`go test`命令就可以非常方便的進行測試,這樣在我們每次修改完程式碼,執行一下 go test 就可以簡單的完成迴歸測試了。
## links
* [目錄](<preface.md>)
* 上一節:[使用 GDB 除錯](<11.2.md>)
* 下一節:[小結](<11.4.md>)