文档章节

Go testing.common公共类源码剖析

恋恋美食
 恋恋美食
发布于 2019/02/11 17:44
字数 1970
阅读 1W
收藏 13

3 月,跳不动了?>>>

简介

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

testing.Ttesting.B属于testing包中的两个数据类型,该类型提供一系列的方法用于控制函数执行流程,考虑到二者有一定的相似性,所以Go实现时抽象出一个testing.common作为一个基础类型,而testing.Ttesting.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项目,求星~ 点我即达

© 著作权归作者所有

恋恋美食

恋恋美食

粉丝 122
博文 198
码字总数 196699
作品 0
杭州
高级程序员
私信 提问
加载中

评论(0)

从JDK源码看Writer

概况 Writer 是一个用于写字符流的抽象类,它将一些相通的写相关操作抽象到此类,方便各种写操作类的实现。一般来说子类只需要实现它的 write、flush 、close 等三个方法,但如果有需要还可以...

超人汪小建
2017/10/30
0
0
Java源码--String.split(String regex)方法解析

注:博客内容主要摘抄自参考阅读中的两篇博文~ 前言 最近在翻阅《阿里巴巴Java开发手册》时发现了这样一条【推荐】性的原则: 比较好奇产生上述结果的原因,因此决定分析一波源码。 简介 一般...

_inkrain
2019/01/20
0
0
深入剖析Vue源码 - 响应式系统构建(中)

为了深入介绍响应式系统的内部实现原理,我们花了一整节的篇幅介绍了数据(包括)如何初始化成为响应式对象的过程。有了响应式数据对象的知识,上一节的后半部分在保留源码结构的基础上还构建了...

不做祖国的韭菜
2019/07/08
0
0
来,跟我一起实现diff算法!

这一节,依然是深入剖析Vue源码系列,上几节内容介绍了Virtual DOM是Vue在渲染机制上做的优化,而渲染的核心在于数据变化时,如何搞笑的更新节点,这就是diff算法。这一节不直接解析源码,而...

不做祖国的韭菜
2019/07/25
0
0
Java8新特性及实战视频教程完整版

百度网盘 资料 第1讲:课程介绍 第2讲:课程介绍续 第3讲:Lambda表达式初步与函数式接口 第4讲:深入函数式接口与方法引用 第5讲:Lambda表达式深入与流初步 第6讲:Function接口详解 第7讲...

远近高低各不同
2018/11/09
6
0

没有更多内容

加载失败,请刷新页面

加载更多

【Java系列002】正确使用@Transactional注解

你好,我是miniluo,今天我和你聊聊Spring声明式事务不生效的坑。 下面就让我和你一起学习有哪些几种情况下Spring声明式事务不生效的坑。 没有正确理解@Transactional注解 你是否曾经写过和下...

littleluoron
26分钟前
13
0
c++ boost Exector

前言 在应用开发中经常要执行一些异步的函数,有些是没有返回结果,有些是有返回结果,甚至有些是定时的任务,本文在boost io_service基础上搭建一个Exector来执行这些任务 代码 #include <...

青黑
31分钟前
14
0
MySQL5.7.25解压版安装教程

1.下载对应的zip包 2.选择合适的安装路径,并记录解压的路径 本人的是 D:\\MySQL\\mysql-5.7.25-winx64 3.配置环境变量,添加变量名path,并在下方的变量值框中输入mysql bin根路径 我的是 ...

jxlgzwh
41分钟前
16
0
闲置笔记本做服务器

最近研究树莓派,想起家里有一台10多年前的笔记本,本着不浪费的原则,拿出来充当树莓派,还自带UPS。安装了xubuntu18.04,只要不用浏览器上网,感觉也还行。下面记录一下几个要点。 替换apt...

propagator
今天
28
0
O(log n)究竟意味着什么? - What does O(log n) mean exactly?

问题: I am learning about Big O Notation running times and amortized times. 我正在学习Big O Notation运行时间和摊销时间。 I understand the notion of O(n) linear time, meaning t......

javail
今天
13
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部