咱們知道單元測試函數須要傳遞一個testing.T
類型的參數,而性能測試函數須要傳遞一個testing.B
類型的參數,該參數可用於控制測試的流程,好比標記測試失敗等。git
testing.T
和testing.B
屬於testing
包中的兩個數據類型,該類型提供一系列的方法用於控制函數執行流程,考慮到兩者有必定的類似性,因此Go實現時抽象出一個testing.common
做爲一個基礎類型,而testing.T
和testing.B
則屬於testing.common
的擴展。github
本節,咱們重點看testing.common
,經過其成員及方法,來了解其實現原理。數據結構
// common holds the elements common between T and B and // captures common methods such as Errorf. type common struct { mu sync.RWMutex // guards this group of fields output []byte // Output generated by test or benchmark. w io.Writer // For flushToParent. ran bool // Test or benchmark (or one of its subtests) was executed. failed bool // Test or benchmark has failed. skipped bool // Test of benchmark has been skipped. done bool // Test is finished and all subtests have completed. helpers map[string]struct{} // functions to be skipped when writing file/line info chatty bool // A copy of the chatty flag. finished bool // Test function has completed. hasSub int32 // written atomically raceErrors int // number of races detected during test runner string // function name of tRunner running the test parent *common level int // Nesting depth of test or benchmark. creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run. name string // Name of test or benchmark. start time.Time // Time test or benchmark started duration time.Duration barrier chan bool // To signal parallel subtests they may start. signal chan bool // To signal a test is done. sub []*T // Queue of subtests to be run in parallel. }
讀寫鎖,僅用於控制本數據內的成員訪問。app
存儲當前測試產生的日誌,每產生一條日誌則追加到該切片中,待測試結束後再一併輸出。函數
子測試執行結束須要把產生的日誌輸送到父測試中的output切片中,傳遞時須要考慮縮進等格式調整,經過w把日誌傳遞到父測試。性能
僅表示是否已執行過。好比,跟據某個規範篩選測試,若是沒有測試被匹配到的話,則common.ran爲false,表示沒有測試運行過。單元測試
若是當前測試執行失敗,則置爲true。測試
標記當前測試是否已跳過。ui
表示當前測試及其子測試已結束,此狀態下再執行Fail()之類的方法標記測試狀態會產生panic。this
標記當前爲函數爲help函數,其中打印的日誌,在記錄日誌時不會顯示其文件名及行號。
對應命令行中的-v參數,默認爲false,true則打印更多詳細日誌。
若是當前測試結束,則置爲true。
標記當前測試是否包含子測試,當測試使用t.Run()方法啓動子測試時,t.hasSub則置爲1。
競態檢測錯誤數。
執行當前測試的函數名。
若是當前測試爲子測試,則置爲父測試的指針。
測試嵌套層數,好比建立子測試時,子測試嵌套層數就會加1。
測試函數調用棧。
記錄每一個測試函數名,好比測試函數TestAdd(t *testing.T)
, 其中t.name即「TestAdd」。 測試結束,打印測試結果會用到該成員。
記錄測試開始的時間。
記錄測試所花費的時間。
用於控制父測試和子測試執行的channel,若是測試爲Parallel,則會阻塞等待父測試結束後再繼續。
通知當前測試結束。
子測試列表。
// Name returns the name of the running test or benchmark. func (c *common) Name() string { return c.name }
該方法直接返回common結構體中存儲的名稱。
// Fail marks the function as having failed but continues execution. func (c *common) Fail() { if c.parent != nil { c.parent.Fail() } c.mu.Lock() defer c.mu.Unlock() // c.done needs to be locked to synchronize checks to c.done in parent tests. if c.done { panic("Fail in goroutine after " + c.name + " has completed") } c.failed = true }
Fail()方法會標記當前測試爲失敗,而後繼續運行,並不會當即退出當前測試。若是是子測試,則除了標記當前測試結果外還經過c.parent.Fail()
來標記父測試失敗。
func (c *common) FailNow() { c.Fail() c.finished = true runtime.Goexit() }
FailNow()內部會調用Fail()標記測試失敗,還會標記測試結束並退出當前測試協程。 能夠簡單的把一個測試理解爲一個協程,FailNow()只會退出當前協程,並不會影響其餘測試協程,但要保證在當前測試協程中調用FailNow()纔有效,不能夠在當前測試建立的協程中調用該方法。
func (c *common) log(s string) { c.mu.Lock() defer c.mu.Unlock() c.output = append(c.output, c.decorate(s)...) }
common.log()爲內部記錄日誌入口,日誌會統一記錄到common.output切片中,測試結束時再統一打印出來。 日誌記錄時會調用common.decorate()進行裝飾,即加上文件名和行號,還會作一些其餘格式化處理。 調用common.log()的方法,有Log()、Logf()、Error()、Errorf()、Fatal()、Fatalf()、Skip()、Skipf()等。
注意:單元測試中記錄的日誌只有在執行失敗或指定了-v
參數纔會打印,不然不會打印。而在性能測試中則老是被打印出來,由於是否打印日誌有可能影響性能測試結果。
func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) }
common.Log()方法用於記錄簡單日誌,經過fmt.Sprintln()方法生成日誌字符串後記錄。
func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) }
common.Logf()方法用於格式化記錄日誌,經過fmt.Sprintf()生成字符串後記錄。
// Error is equivalent to Log followed by Fail. func (c *common) Error(args ...interface{}) { c.log(fmt.Sprintln(args...)) c.Fail() }
common.Error()方法等同於common.Log()+common.Fail(),即記錄日誌並標記失敗,但測試繼續進行。
// Errorf is equivalent to Logf followed by Fail. func (c *common) Errorf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) c.Fail() }
common.Errorf()方法等同於common.Logf()+common.Fail(),即記錄日誌並標記失敗,但測試繼續進行。
// Fatal is equivalent to Log followed by FailNow. func (c *common) Fatal(args ...interface{}) { c.log(fmt.Sprintln(args...)) c.FailNow() }
common.Fatal()方法等同於common.Log()+common.FailNow(),即記錄日誌、標記失敗並退出當前測試。
// Fatalf is equivalent to Logf followed by FailNow. func (c *common) Fatalf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) c.FailNow() }
common.Fatalf()方法等同於common.Logf()+common.FailNow(),即記錄日誌、標記失敗並退出當前測試。
func (c *common) skip() { c.mu.Lock() defer c.mu.Unlock() c.skipped = true }
common.skip()方法標記當前測試爲已跳過狀態,好比測試中檢測到某種條件,再也不繼續測試。該函數僅標記測試跳過,與測試結果無關。測試結果仍然取決於common.failed。
func (c *common) SkipNow() { c.skip() c.finished = true runtime.Goexit() }
common.SkipNow()方法標記測試跳過,並標記測試結束,最後退出當前測試。
// Skip is equivalent to Log followed by SkipNow. func (c *common) Skip(args ...interface{}) { c.log(fmt.Sprintln(args...)) c.SkipNow() }
common.Skip()方法等同於common.Log()+common.SkipNow()。
// Skipf is equivalent to Logf followed by SkipNow. func (c *common) Skipf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) c.SkipNow() }
common.Skipf()方法等同於common.Logf() + common.SkipNow()。
// Helper marks the calling function as a test helper function. // When printing file and line information, that function will be skipped. // Helper may be called simultaneously from multiple goroutines. func (c *common) Helper() { c.mu.Lock() defer c.mu.Unlock() if c.helpers == nil { c.helpers = make(map[string]struct{}) } c.helpers[callerName(1)] = struct{}{} }
common.Helper()方法標記當前函數爲help
函數,所謂help
函數,即其中打印的日誌,不記錄help
函數的函數名及行號,而是記錄上一層函數的函數名和行號。
贈人玫瑰手留餘香,若是以爲不錯請給個贊~
本篇文章已歸檔到GitHub項目,求星~ 點我即達