簡介
我們知道單元測試函數需要傳遞一個testing.T類型的參數,而性能測試函數需要傳遞一個testing.B類型的參數,該參數可用於控制測試的流程,比如標記測試失敗等。
testing.T和testing.B屬於testing包中的兩個數據類型,該類型提供一系列的方法用於控制函數執行流程,考慮到二者有一定的相似性,所以Go實現時抽象出一個testing.common作為一個基礎類型,而testing.T和testing.B則屬於testing.common的擴展。
本節,我們重點看testing.common,通過其成員及方法,來了解其實現原理。
數據結構
<code>// 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 fieldsoutput []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 infochatty bool // A copy of the chatty flag.finished bool // Test function has completed.hasSub int32 // written atomicallyraceErrors int // number of races detected during testrunner string // function name of tRunner running the testparent *commonlevel 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 startedduration time.Durationbarrier 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.}/<code>
common.mu
讀寫鎖,僅用於控制本數據內的成員訪問。
common.output
存儲當前測試產生的日誌,每產生一條日誌則追加到該切片中,待測試結束後再一併輸出。
common.w
子測試執行結束需要把產生的日誌輸送到父測試中的output切片中,傳遞時需要考慮縮進等格式調整,通過w把日誌傳遞到父測試。
common.ran
僅表示是否已執行過。比如,跟據某個規範篩選測試,如果沒有測試被匹配到的話,則common.ran為false,表示沒有測試運行過。
common.failed
如果當前測試執行失敗,則置為true。
common.skipped
標記當前測試是否已跳過。
common.done
表示當前測試及其子測試已結束,此狀態下再執行Fail()之類的方法標記測試狀態會產生panic。
common.helpers
標記當前為函數為help函數,其中打印的日誌,在記錄日誌時不會顯示其文件名及行號。
common.chatty
對應命令行中的-v參數,默認為false,true則打印更多詳細日誌。
common.finished
如果當前測試結束,則置為true。
common.hasSub
標記當前測試是否包含子測試,當測試使用t.Run()方法啟動子測試時,t.hasSub則置為1。
common.raceErrors
競態檢測錯誤數。
common.runner
執行當前測試的函數名。
common.parent
如果當前測試為子測試,則置為父測試的指針。
common.level
測試嵌套層數,比如創建子測試時,子測試嵌套層數就會加1。
common.creator
測試函數調用棧。
common.name
記錄每個測試函數名,比如測試函數TestAdd(t *testing.T), 其中t.name即“TestAdd”。 測試結束,打印測試結果會用到該成員。
common.start
記錄測試開始的時間。
common.duration
記錄測試所花費的時間。
common.barrier
用於控制父測試和子測試執行的channel,如果測試為Parallel,則會阻塞等待父測試結束後再繼續。
common.signal
通知當前測試結束。
common.sub
子測試列表。
成員方法
common.Name()
<code>// Name returns the name of the running test or benchmark.func (c *common) Name() string {return c.name}/<code>
該方法直接返回common結構體中存儲的名稱。
common.Fail()
<code>// 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}/<code>
Fail()方法會標記當前測試為失敗,然後繼續運行,並不會立即退出當前測試。如果是子測試,則除了標記當前測試結果外還通過c.parent.Fail()來標記父測試失敗。
common.FailNow()
<code>func (c *common) FailNow() {c.Fail()c.finished = trueruntime.Goexit()}/<code>
FailNow()內部會調用Fail()標記測試失敗,還會標記測試結束並退出當前測試協程。 可以簡單的把一個測試理解為一個協程,FailNow()只會退出當前協程,並不會影響其他測試協程,但要保證在當前測試協程中調用FailNow()才有效,不可以在當前測試創建的協程中調用該方法。
common.log()
<code>func (c *common) log(s string) {c.mu.Lock()defer c.mu.Unlock()c.output = append(c.output, c.decorate(s)...)}/<code>
common.log()為內部記錄日誌入口,日誌會統一記錄到common.output切片中,測試結束時再統一打印出來。 日誌記錄時會調用common.decorate()進行裝飾,即加上文件名和行號,還會做一些其他格式化處理。 調用common.log()的方法,有Log()、Logf()、Error()、Errorf()、Fatal()、Fatalf()、Skip()、Skipf()等。
注意:單元測試中記錄的日誌只有在執行失敗或指定了-v參數才會打印,否則不會打印。而在性能測試中則總是被打印出來,因為是否打印日誌有可能影響性能測試結果。
common.Log(args ...interface{})
<code>func (c *common) Log(args ...interface{}) {c.log(fmt.Sprintln(args...)) }/<code>
common.Log()方法用於記錄簡單日誌,通過fmt.Sprintln()方法生成日誌字符串後記錄。
common.Logf(format string, args ...interface{})
<code>func (c *common) Logf(format string, args ...interface{}) {c.log(fmt.Sprintf(format, args...))}/<code>
common.Logf()方法用於格式化記錄日誌,通過fmt.Sprintf()生成字符串後記錄。
common.Error(args ...interface{})
<code>// Error is equivalent to Log followed by Fail.func (c *common) Error(args ...interface{}) {c.log(fmt.Sprintln(args...))c.Fail()}/<code>
common.Error()方法等同於common.Log()+common.Fail(),即記錄日誌並標記失敗,但測試繼續進行。
common.Errorf(format string, args ...interface{})
<code>// Errorf is equivalent to Logf followed by Fail.func (c *common) Errorf(format string, args ...interface{}) {c.log(fmt.Sprintf(format, args...))c.Fail()}/<code>
common.Errorf()方法等同於common.Logf()+common.Fail(),即記錄日誌並標記失敗,但測試繼續進行。
common.Fatal(args ...interface{})
<code>// Fatal is equivalent to Log followed by FailNow.func (c *common) Fatal(args ...interface{}) {c.log(fmt.Sprintln(args...))c.FailNow()}/<code>
common.Fatal()方法等同於common.Log()+common.FailNow(),即記錄日誌、標記失敗並退出當前測試。
common.Fatalf(format string, args ...interface{})
<code>// Fatalf is equivalent to Logf followed by FailNow.func (c *common) Fatalf(format string, args ...interface{}) {c.log(fmt.Sprintf(format, args...))c.FailNow()}/<code>
common.Fatalf()方法等同於common.Logf()+common.FailNow(),即記錄日誌、標記失敗並退出當前測試。
common.skip()
<code>func (c *common) skip() {c.mu.Lock()defer c.mu.Unlock()c.skipped = true}/<code>
common.skip()方法標記當前測試為已跳過狀態,比如測試中檢測到某種條件,不再繼續測試。該函數僅標記測試跳過,與測試結果無關。測試結果仍然取決於common.failed。
common.SkipNow()
<code>func (c *common) SkipNow() {c.skip()c.finished = trueruntime.Goexit()}/<code>
common.SkipNow()方法標記測試跳過,並標記測試結束,最後退出當前測試。
common.Skip(args ...interface{})
<code>// Skip is equivalent to Log followed by SkipNow.func (c *common) Skip(args ...interface{}) {c.log(fmt.Sprintln(args...))c.SkipNow()}/<code>
common.Skip()方法等同於common.Log()+common.SkipNow()。
common.Skipf(format string, args ...interface{})
<code>// Skipf is equivalent to Logf followed by SkipNow.func (c *common) Skipf(format string, args ...interface{}) {c.log(fmt.Sprintf(format, args...))c.SkipNow()}/<code>
common.Skipf()方法等同於common.Logf() + common.SkipNow()。
common.Helper()
<code>// 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{}{}}/<code>
common.Helper()方法標記當前函數為help函數,所謂help函數,即其中打印的日誌,不記錄help函數的函數名及行號,而是記錄上一層函數的函數名和行號。
贈人玫瑰手留餘香,如果覺得不錯請給個贊~
閱讀更多 IT技術之家 的文章