Go testing.common公共類源碼剖析

簡介

我們知道單元測試函數需要傳遞一個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函數的函數名及行號,而是記錄上一層函數的函數名和行號。

贈人玫瑰手留餘香,如果覺得不錯請給個贊~


分享到:


相關文章: