每日一博 | Go testing.common 公共类源码剖析
简介
我们知道单元测试函数需要传递一个testing.T
类型的参数,而性能测试函数需要传递一个testing.B
类型的参数,该参数可用于控制测试的流程,比如标记测试失败等。
testing.T
和testing.B
属于testing
包中的两个数据类型,该类型提供一系列的方法用于控制函数执行流程,考虑到二者有一定的相似性,所以Go实现时抽象出一个testing.common
作为一个基础类型,而testing.T
和testing.B
则属于testing.common
的扩展。
本节,我们重点看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
读写锁,仅用于控制本数据内的成员访问。
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()
// 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项目,求星~ 点我即达
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
谷歌宣布将于下月停用 Material Theme Editor
谷歌曾于 2018 年推出了 Material Theming,旨在让开发人员更好地为其应用程序自定义 Material Design。最近,谷歌宣布将在下个月停用其中一项设计工具Material Theme Editor(Material 主题编辑器),并可能以新的产品取代之。 使用Material Theme Editor,开发人员可创建和自定义Material 主题,包括颜色、形状、版式等等。 在Material Theme Editor 中,可根据不同部分依次选择颜色,并应用于所有组件,还可调整对比度,根据单一颜色自动生成不同色度的调色板。组件的形状和边角样式都是高度可定制的。也可调整字体大小、优化可读性。该工具还提供了几套不同的风格化的系统图标以供选择。 但它仅在 Mac 版 Sketch 上可用。由于操作系统和可用工具的限制,谷歌最终决定在 3 月 19 日放弃 Material Theme Editor。“Material 社区的成员要求的工具不仅限于 Sketch,而且可以在任何平台上使用,对开发人员更有用,并且可以轻松地与现有源文件集成。” 谷歌表示,停用Materi...
- 下一篇
Kubernetes - 4.1 Workload - Pod
什么是Pod? Kubernetes中最小的管理单元,作为应用运行的载体。当Pod运行多个容器时,同一个Pod中的所有容器可以共享PID、Network、IPC、UTS命名空间。 打个比方,例如Pod是豆荚,Container容器就是豆子,一个豆荚里可以有一个或者多个豆子。 Pod的使用方式 通过kubectl创建kubectl run nginx-pod --image=nginx:1.16 通过yaml资源定义清单创建kubectl apply -f nginx-pod.yaml apiVersion: v1 #表示api资源是哪一个组及版本 kind: Pod #表示资源类别 metadata: #表示元数据 name: nginx #名称,作用域在名称空间内唯一 spec: #表示期望状态 containers: #表示容器资源 - name: nginx #名称,作用域在Pod内唯一 image: nginx:1.16 #指定镜像 通过kubectl命令查看Podkubectl get pods Pod的资源管理 Pod开始创建时会进行请求所需资源,Kubernetes会根据...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长