• 简介
  • 数据结构
    • common.mu
    • common.output
    • common.w
    • common.ran
    • common.failed
    • common.skipped
    • common.done
    • common.helpers
    • common.chatty
    • common.finished
    • common.hasSub
    • common.raceErrors
    • common.runner
    • common.parent
    • common.level
    • common.creator
    • common.name
    • common.start
    • common.duration
    • common.barrier
    • common.signal
    • common.sub
  • 成员方法
    • common.Name()
    • common.Fail()
    • common.FailNow()
    • common.log()
    • common.Log(args …interface{})
    • common.Logf(format string, args …interface{})
    • common.Error(args …interface{})
    • common.Errorf(format string, args …interface{})
    • common.Fatal(args …interface{})
    • common.Fatalf(format string, args …interface{})
    • common.skip()
    • common.SkipNow()
    • common.Skip(args …interface{})
    • common.Skipf(format string, args …interface{})
    • common.Helper()

    简介

    我们知道单元测试函数需要传递一个testing.T类型的参数,而性能测试函数需要传递一个testing.B类型的参数,该参数可用于控制测试的流程,比如标记测试失败等。

    testing.Ttesting.B属于testing包中的两个数据类型,该类型提供一系列的方法用于控制函数执行流程,考虑到二者有一定的相似性,所以Go实现时抽象出一个testing.common作为一个基础类型,而testing.Ttesting.B则属于testing.common的扩展。

    本节,我们重点看testing.common,通过其成员及方法,来了解其实现原理。

    数据结构

    1. // common holds the elements common between T and B and
    2. // captures common methods such as Errorf.
    3. type common struct {
    4. mu sync.RWMutex // guards this group of fields
    5. output []byte // Output generated by test or benchmark.
    6. w io.Writer // For flushToParent.
    7. ran bool // Test or benchmark (or one of its subtests) was executed.
    8. failed bool // Test or benchmark has failed.
    9. skipped bool // Test of benchmark has been skipped.
    10. done bool // Test is finished and all subtests have completed.
    11. helpers map[string]struct{} // functions to be skipped when writing file/line info
    12. chatty bool // A copy of the chatty flag.
    13. finished bool // Test function has completed.
    14. hasSub int32 // written atomically
    15. raceErrors int // number of races detected during test
    16. runner string // function name of tRunner running the test
    17. parent *common
    18. level int // Nesting depth of test or benchmark.
    19. creator []uintptr // If level > 0, the stack trace at the point where the parent called t.Run.
    20. name string // Name of test or benchmark.
    21. start time.Time // Time test or benchmark started
    22. duration time.Duration
    23. barrier chan bool // To signal parallel subtests they may start.
    24. signal chan bool // To signal a test is done.
    25. sub []*T // Queue of subtests to be run in parallel.
    26. }

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

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

    该方法直接返回common结构体中存储的名称。

    common.Fail()

    1. // Fail marks the function as having failed but continues execution.
    2. func (c *common) Fail() {
    3. if c.parent != nil {
    4. c.parent.Fail()
    5. }
    6. c.mu.Lock()
    7. defer c.mu.Unlock()
    8. // c.done needs to be locked to synchronize checks to c.done in parent tests.
    9. if c.done {
    10. panic("Fail in goroutine after " + c.name + " has completed")
    11. }
    12. c.failed = true
    13. }

    Fail()方法会标记当前测试为失败,然后继续运行,并不会立即退出当前测试。如果是子测试,则除了标记当前测试结果外还通过c.parent.Fail()来标记父测试失败。

    common.FailNow()

    1. func (c *common) FailNow() {
    2. c.Fail()
    3. c.finished = true
    4. runtime.Goexit()
    5. }

    FailNow()内部会调用Fail()标记测试失败,还会标记测试结束并退出当前测试协程。可以简单的把一个测试理解为一个协程,FailNow()只会退出当前协程,并不会影响其他测试协程,但要保证在当前测试协程中调用FailNow()才有效,不可以在当前测试创建的协程中调用该方法。

    common.log()

    1. func (c *common) log(s string) {
    2. c.mu.Lock()
    3. defer c.mu.Unlock()
    4. c.output = append(c.output, c.decorate(s)...)
    5. }

    common.log()为内部记录日志入口,日志会统一记录到common.output切片中,测试结束时再统一打印出来。日志记录时会调用common.decorate()进行装饰,即加上文件名和行号,还会做一些其他格式化处理。调用common.log()的方法,有Log()、Logf()、Error()、Errorf()、Fatal()、Fatalf()、Skip()、Skipf()等。

    注意:单元测试中记录的日志只有在执行失败或指定了-v参数才会打印,否则不会打印。而在性能测试中则总是被打印出来,因为是否打印日志有可能影响性能测试结果。

    common.Log(args …interface{})

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

    common.Log()方法用于记录简单日志,通过fmt.Sprintln()方法生成日志字符串后记录。

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

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

    common.Logf()方法用于格式化记录日志,通过fmt.Sprintf()生成字符串后记录。

    common.Error(args …interface{})

    1. // Error is equivalent to Log followed by Fail.
    2. func (c *common) Error(args ...interface{}) {
    3. c.log(fmt.Sprintln(args...))
    4. c.Fail()
    5. }

    common.Error()方法等同于common.Log()+common.Fail(),即记录日志并标记失败,但测试继续进行。

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

    1. // Errorf is equivalent to Logf followed by Fail.
    2. func (c *common) Errorf(format string, args ...interface{}) {
    3. c.log(fmt.Sprintf(format, args...))
    4. c.Fail()
    5. }

    common.Errorf()方法等同于common.Logf()+common.Fail(),即记录日志并标记失败,但测试继续进行。

    common.Fatal(args …interface{})

    1. // Fatal is equivalent to Log followed by FailNow.
    2. func (c *common) Fatal(args ...interface{}) {
    3. c.log(fmt.Sprintln(args...))
    4. c.FailNow()
    5. }

    common.Fatal()方法等同于common.Log()+common.FailNow(),即记录日志、标记失败并退出当前测试。

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

    1. // Fatalf is equivalent to Logf followed by FailNow.
    2. func (c *common) Fatalf(format string, args ...interface{}) {
    3. c.log(fmt.Sprintf(format, args...))
    4. c.FailNow()
    5. }

    common.Fatalf()方法等同于common.Logf()+common.FailNow(),即记录日志、标记失败并退出当前测试。

    common.skip()

    1. func (c *common) skip() {
    2. c.mu.Lock()
    3. defer c.mu.Unlock()
    4. c.skipped = true
    5. }

    common.skip()方法标记当前测试为已跳过状态,比如测试中检测到某种条件,不再继续测试。该函数仅标记测试跳过,与测试结果无关。测试结果仍然取决于common.failed。

    common.SkipNow()

    1. func (c *common) SkipNow() {
    2. c.skip()
    3. c.finished = true
    4. runtime.Goexit()
    5. }

    common.SkipNow()方法标记测试跳过,并标记测试结束,最后退出当前测试。

    common.Skip(args …interface{})

    1. // Skip is equivalent to Log followed by SkipNow.
    2. func (c *common) Skip(args ...interface{}) {
    3. c.log(fmt.Sprintln(args...))
    4. c.SkipNow()
    5. }

    common.Skip()方法等同于common.Log()+common.SkipNow()。

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

    1. // Skipf is equivalent to Logf followed by SkipNow.
    2. func (c *common) Skipf(format string, args ...interface{}) {
    3. c.log(fmt.Sprintf(format, args...))
    4. c.SkipNow()
    5. }

    common.Skipf()方法等同于common.Logf() + common.SkipNow()。

    common.Helper()

    1. // Helper marks the calling function as a test helper function.
    2. // When printing file and line information, that function will be skipped.
    3. // Helper may be called simultaneously from multiple goroutines.
    4. func (c *common) Helper() {
    5. c.mu.Lock()
    6. defer c.mu.Unlock()
    7. if c.helpers == nil {
    8. c.helpers = make(map[string]struct{})
    9. }
    10. c.helpers[callerName(1)] = struct{}{}
    11. }

    common.Helper()方法标记当前函数为help函数,所谓help函数,即其中打印的日志,不记录help函数的函数名及行号,而是记录上一层函数的函数名和行号。