Go testing.common公共類源碼剖析

簡介

咱們知道單元測試函數須要傳遞一個testing.T類型的參數,而性能測試函數須要傳遞一個testing.B類型的參數,該參數可用於控制測試的流程,好比標記測試失敗等。git

testing.Ttesting.B屬於testing包中的兩個數據類型,該類型提供一系列的方法用於控制函數執行流程,考慮到兩者有必定的類似性,因此Go實現時抽象出一個testing.common做爲一個基礎類型,而testing.Ttesting.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.
}

common.mu

讀寫鎖,僅用於控制本數據內的成員訪問。app

common.output

存儲當前測試產生的日誌,每產生一條日誌則追加到該切片中,待測試結束後再一併輸出。函數

common.w

子測試執行結束須要把產生的日誌輸送到父測試中的output切片中,傳遞時須要考慮縮進等格式調整,經過w把日誌傳遞到父測試。性能

common.ran

僅表示是否已執行過。好比,跟據某個規範篩選測試,若是沒有測試被匹配到的話,則common.ran爲false,表示沒有測試運行過。單元測試

common.failed

若是當前測試執行失敗,則置爲true。測試

common.skipped

標記當前測試是否已跳過。ui

common.done

表示當前測試及其子測試已結束,此狀態下再執行Fail()之類的方法標記測試狀態會產生panic。this

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()

// Name returns the name of the running test or benchmark.
func (c *common) Name() string {
	return c.name
}

該方法直接返回common結構體中存儲的名稱。

common.Fail()

// 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()來標記父測試失敗。

common.FailNow()

func (c *common) FailNow() {
	c.Fail()
	c.finished = true
	runtime.Goexit()
}

FailNow()內部會調用Fail()標記測試失敗,還會標記測試結束並退出當前測試協程。 能夠簡單的把一個測試理解爲一個協程,FailNow()只會退出當前協程,並不會影響其餘測試協程,但要保證在當前測試協程中調用FailNow()纔有效,不能夠在當前測試建立的協程中調用該方法。

common.log()

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參數纔會打印,不然不會打印。而在性能測試中則老是被打印出來,由於是否打印日誌有可能影響性能測試結果。

common.Log(args ...interface{})

func (c *common) Log(args ...interface{}) {
	c.log(fmt.Sprintln(args...)) 
}

common.Log()方法用於記錄簡單日誌,經過fmt.Sprintln()方法生成日誌字符串後記錄。

common.Logf(format string, args ...interface{})

func (c *common) Logf(format string, args ...interface{}) {
	c.log(fmt.Sprintf(format, args...))
}

common.Logf()方法用於格式化記錄日誌,經過fmt.Sprintf()生成字符串後記錄。

common.Error(args ...interface{})

// 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(),即記錄日誌並標記失敗,但測試繼續進行。

common.Errorf(format string, args ...interface{})

// 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(),即記錄日誌並標記失敗,但測試繼續進行。

common.Fatal(args ...interface{})

// 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(),即記錄日誌、標記失敗並退出當前測試。

common.Fatalf(format string, args ...interface{})

// 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(),即記錄日誌、標記失敗並退出當前測試。

common.skip()

func (c *common) skip() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.skipped = true
}

common.skip()方法標記當前測試爲已跳過狀態,好比測試中檢測到某種條件,再也不繼續測試。該函數僅標記測試跳過,與測試結果無關。測試結果仍然取決於common.failed。

common.SkipNow()

func (c *common) SkipNow() {
	c.skip()
	c.finished = true
	runtime.Goexit()
}

common.SkipNow()方法標記測試跳過,並標記測試結束,最後退出當前測試。

common.Skip(args ...interface{})

// 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()。

common.Skipf(format string, args ...interface{})

// 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()。

common.Helper()

// 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項目,求星~ 點我即達

相關文章
相關標籤/搜索