【轉】go test命令(Go語言測試命令)完全攻略


go的單元測試, 有以Test開頭和Benchmark開頭的函數,作用是不一樣的。


go test -v -bench=.


原文: http://c.biancheng.net/view/124.html

 

 

 

 

golang單元測試

要開始一個單元測試,需要准備一個 go 源碼文件,在命名文件時需要讓文件必須以_test結尾
單元測試源碼文件可以由多個測試用例組成,每個測試用例函數需要以Test為前綴,例如:

func TestXXX( t *testing.T )
  • 測試用例文件不會參與正常源碼編譯,不會被包含到可執行文件中。
  • 測試用例文件使用 go test 指令來執行沒有也不需要 main() 作為函數入口。所有在以_test結尾的源碼內以Test開頭的函數會自動被執行
  • 單元測試文件 (*_test.go) 里的測試入口必須以 Test 開始,參數為 *testing.T 的函數一個單元測試文件可以有多個測試入口
  • 使用 testing 包的 T 結構提供的 Log() 方法打印字符串。

代碼目錄結構如下

![VR[)QZIRY%9OL0QTIQ7%MD.png

##源文件
//uc.go
package uc

import "strings"

func UpperCase(str string) string {

return strings.ToUpper(str)}

測試文件

package uc
import "testing"
type ucTest struct {
    in, out string
}

var ucTests = []ucTest{
    ucTest{"abc", "ABC"},
    ucTest{"cvo-az", "CVO-AZ"},
    ucTest{"Antwerp", "ANTWERP"},
}

func TestUC(t *testing.T) {
    for _, ut := range ucTests {
        uc := UpperCase(ut.in)
        if uc != ut.out {
            t.Errorf("uppercase(%s) = %s,must be %s", ut.in, uc, ut.out)
        }
    }
}

---------------------------------

 

Go語言擁有一套單元測試和性能測試系統,僅需要添加很少的代碼就可以快速測試一段需求代碼。

go test 命令,會自動讀取源碼目錄下面名為 *_test.go 的文件,生成並運行測試用的可執行文件。輸出的信息類似下面所示的樣子:

ok archive/tar 0.011s
FAIL archive/zip 0.022s
ok compress/gzip 0.033s
...

性能測試系統可以給出代碼的性能數據,幫助測試者分析性能問題。

提示

單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,一般要根據實際情況去判定其具體含義,如C語言中單元指一個函數,Java 里單元指一個類,圖形化的軟件中可以指一個窗口或一個菜單等。總的來說,單元就是人為規定的最小的被測功能模塊。

單元測試是在軟件開發過程中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其他部分相隔離的情況下進行測試。

單元測試——測試和驗證代碼的框架

要開始一個單元測試,需要准備一個 go 源碼文件,在命名文件時需要讓文件必須以_test結尾。默認的情況下,go test命令不需要任何的參數,它會自動把你源碼包下面所有 test 文件測試完畢,當然你也可以帶上參數。

這里介紹幾個常用的參數:

  • -bench regexp 執行相應的 benchmarks,例如 -bench=.;
  • -cover 開啟測試覆蓋率;
  • -run regexp 只運行 regexp 匹配的函數,例如 -run=Array 那么就執行包含有 Array 開頭的函數;
  • -v 顯示測試的詳細命令。


單元測試源碼文件可以由多個測試用例組成,每個測試用例函數需要以Test為前綴,例如:

func TestXXX( t *testing.T )

  • 測試用例文件不會參與正常源碼編譯,不會被包含到可執行文件中。
  • 測試用例文件使用go test指令來執行,沒有也不需要 main() 作為函數入口。所有在以_test結尾的源碼內以Test開頭的函數會自動被執行。
  • 測試用例可以不傳入 *testing.T 參數。


helloworld 的測試代碼(具體位置是./src/chapter11/gotest/helloworld_test.go):

本套教程所有源碼下載地址:https://pan.baidu.com/s/1ORFVTOLEYYqDhRzeq0zIiQ    提取密碼:hfyf

  1. package code11_3
  2. import "testing"
  3. func TestHelloWorld(t *testing.T) {
  4. t.Log("hello world")
  5. }

代碼說明如下:

  • 第 5 行,單元測試文件 (*_test.go) 里的測試入口必須以 Test 開始,參數為 *testing.T 的函數。一個單元測試文件可以有多個測試入口。
  • 第 6 行,使用 testing 包的 T 結構提供的 Log() 方法打印字符串。

1) 單元測試命令行

單元測試使用 go test 命令啟動,例如:

$ go test helloworld_test.go
ok          command-line-arguments        0.003s
$ go test -v helloworld_test.go
=== RUN   TestHelloWorld
--- PASS: TestHelloWorld (0.00s)
        helloworld_test.go:8: hello world
PASS
ok          command-line-arguments        0.004s

代碼說明如下:

  • 第 1 行,在 go test 后跟 helloworld_test.go 文件,表示測試這個文件里的所有測試用例。
  • 第 2 行,顯示測試結果,ok 表示測試通過,command-line-arguments 是測試用例需要用到的一個包名,0.003s 表示測試花費的時間。
  • 第 3 行,顯示在附加參數中添加了-v,可以讓測試時顯示詳細的流程。
  • 第 4 行,表示開始運行名叫 TestHelloWorld 的測試用例。
  • 第 5 行,表示已經運行完 TestHelloWorld 的測試用例,PASS 表示測試成功。
  • 第 6 行打印字符串 hello world。

2) 運行指定單元測試用例

go test指定文件時默認執行文件內的所有測試用例。可以使用-run參數選擇需要的測試用例單獨執行,參考下面的代碼。

一個文件包含多個測試用例(具體位置是./src/chapter11/gotest/select_test.go

  1. package code11_3
  2. import "testing"
  3. func TestA(t *testing.T) {
  4. t.Log("A")
  5. }
  6. func TestAK(t *testing.T) {
  7. t.Log("AK")
  8. }
  9. func TestB(t *testing.T) {
  10. t.Log("B")
  11. }
  12. func TestC(t *testing.T) {
  13. t.Log("C")
  14. }

這里指定 TestA 進行測試:

$ go test -v -run TestA select_test.go
=== RUN   TestA
--- PASS: TestA (0.00s)
        select_test.go:6: A
=== RUN   TestAK
--- PASS: TestAK (0.00s)
        select_test.go:10: AK
PASS
ok          command-line-arguments        0.003s

TestA 和 TestAK 的測試用例都被執行,原因是-run跟隨的測試用例的名稱支持正則表達式,使用-run TestA$即可只執行 TestA 測試用例。

3) 標記單元測試結果

當需要終止當前測試用例時,可以使用 FailNow,參考下面的代碼。

測試結果標記(具體位置是./src/chapter11/gotest/fail_test.go

  1. func TestFailNow(t *testing.T) {
  2. t.FailNow()
  3. }

還有一種只標記錯誤不終止測試的方法,代碼如下:

  1. func TestFail(t *testing.T) {
  2. fmt.Println("before fail")
  3. t.Fail()
  4. fmt.Println("after fail")
  5. }

測試結果如下:

=== RUN   TestFail
before fail
after fail
--- FAIL: TestFail (0.00s)
FAIL
exit status 1
FAIL        command-line-arguments        0.002s

從日志中看出,第 5 行調用 Fail() 后測試結果標記為失敗,但是第 7 行依然被程序執行了。

4) 單元測試日志

每個測試用例可能並發執行,使用 testing.T 提供的日志輸出可以保證日志跟隨這個測試上下文一起打印輸出。testing.T 提供了幾種日志輸出方法,詳見下表所示。

單元測試框架提供的日志方法
方  法 備  注
Log 打印日志,同時結束測試
Logf 格式化打印日志,同時結束測試
Error 打印錯誤日志,同時結束測試
Errorf 格式化打印錯誤日志,同時結束測試
Fatal 打印致命日志,同時結束測試
Fatalf 格式化打印致命日志,同時結束測試


開發者可以根據實際需要選擇合適的日志。

基准測試——獲得代碼內存占用和運行效率的性能數據

基准測試可以測試一段程序的運行性能及耗費 CPU 的程度。Go語言中提供了基准測試框架,使用方法類似於單元測試,使用者無須准備高精度的計時器和各種分析工具,基准測試本身即可以打印出非常標准的測試報告。

1) 基礎測試基本使用

下面通過一個例子來了解基准測試的基本使用方法。

基准測試(具體位置是./src/chapter11/gotest/benchmark_test.go

  1. package code11_3
  2. import "testing"
  3. func Benchmark_Add(b *testing.B) {
  4. var n int
  5. for i := 0; i < b.N; i++ {
  6. n++
  7. }
  8. }

這段代碼使用基准測試框架測試加法性能。第 7 行中的 b.N 由基准測試框架提供。測試代碼需要保證函數可重入性及無狀態,也就是說,測試代碼不使用全局變量等帶有記憶性質的數據結構。避免多次運行同一段代碼時的環境不一致,不能假設 N 值范圍。

使用如下命令行開啟基准測試:

$ go test -v -bench=. benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Add-4           20000000         0.33 ns/op
PASS
ok          command-line-arguments        0.700s

代碼說明如下:

  • 第 1 行的-bench=.表示運行 benchmark_test.go 文件里的所有基准測試,和單元測試中的-run類似。
  • 第 4 行中顯示基准測試名稱,2000000000 表示測試的次數,也就是 testing.B 結構中提供給程序使用的 N。“0.33 ns/op”表示每一個操作耗費多少時間(納秒)。


注意:Windows 下使用 go test 命令行時,-bench=.應寫為-bench="."

2) 基准測試原理

基准測試框架對一個測試用例的默認測試時間是 1 秒。開始測試時,當以 Benchmark 開頭的基准測試用例函數返回時還不到 1 秒,那么 testing.B 中的 N 值將按 1、2、5、10、20、50……遞增,同時以遞增后的值重新調用基准測試用例函數。

3) 自定義測試時間

通過-benchtime參數可以自定義測試時間,例如:

$ go test -v -bench=. -benchtime=5s benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Add-4           10000000000                 0.33 ns/op
PASS
ok          command-line-arguments        3.380s

4) 測試內存

基准測試可以對一段代碼可能存在的內存分配進行統計,下面是一段使用字符串格式化的函數,內部會進行一些分配操作。

  1. func Benchmark_Alloc(b *testing.B) {
  2. for i := 0; i < b.N; i++ {
  3. fmt.Sprintf("%d", i)
  4. }
  5. }

在命令行中添加-benchmem參數以顯示內存分配情況,參見下面的指令:

$ go test -v -bench=Alloc -benchmem benchmark_test.go
goos: linux
goarch: amd64
Benchmark_Alloc-4 20000000 109 ns/op 16 B/op 2 allocs/op
PASS
ok          command-line-arguments        2.311s

代碼說明如下:

  • 第 1 行的代碼中-bench后添加了 Alloc,指定只測試 Benchmark_Alloc() 函數。
  • 第 4 行代碼的“16 B/op”表示每一次調用需要分配 16 個字節,“2 allocs/op”表示每一次調用有兩次分配。


開發者根據這些信息可以迅速找到可能的分配點,進行優化和調整。

5) 控制計時器

有些測試需要一定的啟動和初始化時間,如果從 Benchmark() 函數開始計時會很大程度上影響測試結果的精准性。testing.B 提供了一系列的方法可以方便地控制計時器,從而讓計時器只在需要的區間進行測試。我們通過下面的代碼來了解計時器的控制。

基准測試中的計時器控制(具體位置是./src/chapter11/gotest/benchmark_test.go):

  1. func Benchmark_Add_TimerControl(b *testing.B) {
  2. // 重置計時器
  3. b.ResetTimer()
  4. // 停止計時器
  5. b.StopTimer()
  6. // 開始計時器
  7. b.StartTimer()
  8. var n int
  9. for i := 0; i < b.N; i++ {
  10. n++
  11. }
  12. }

從 Benchmark() 函數開始,Timer 就開始計數。StopTimer() 可以停止這個計數過程,做一些耗時的操作,通過 StartTimer() 重新開始計時。ResetTimer() 可以重置計數器的數據。

計數器內部不僅包含耗時數據,還包括內存分配的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM