Go-知识测试-工作机制

Go-知识测试-工作机制

  • 生成test的main
  • test的main如何启动case
  • 单元测试 runTests
    • tRunner
    • testing.T.Run
  • 示例测试 runExamples
    • runExample
    • processRunResult
  • 性能测试 runBenchmarks
    • runN
    • testing.B.Run

在 Go 语言的源码中,go test 命令的实现主要在 src/cmd/go/internal/test 包中。当你运行 go test 命令时,Go 的命令行工具会调用这个包中的代码来执行测试。
以下是 go test 命令的大致执行流程:

  1. 首先,go test 命令会解析命令行参数,获取需要测试的包和测试选项。
  2. 然后,go test 命令会构建一个测试的二进制文件。这个二进制文件包含了需要测试的包和测试用例,以及测试用例的运行环境和测试框架。
  3. 接着,go test 命令会启动这个二进制文件,并将命令行参数传递给它。这个二进制文件会运行测试用例,并将测试结果输出到标准输出。
  4. 最后,go test 命令会读取这个二进制文件的输出,解析测试结果,并将测试结果显示给用户。

在 src/cmd/go/internal/test 包中,runTest 函数是 go test 命令的主要入口点。这个函数负责解析命令行参数,构建测试的二进制文件,启动这个二进制文件,以及读取和解析测试结果。
在 runTest 函数中,runTest 函数会调用 load.TestPackagesFor 函数来获取需要测试的包,然后调用 builder.runTest 函数来构建和运行测试的二进制文件。builder.runTest 函数会调用 builder.runOut 函数来启动这个二进制文件,并将这个二进制文件的输出连接到 go test 命令的标准输出。
在 builder.runTest 函数中,builder.runTest 函数会调用 builder.compile 函数来编译需要测试的包,然后调用 builder.link 函数来链接这个包和测试框架,生成测试的二进制文件。

生成test的main

详细的来说:
首先执行 go test命令,是一个内部命令,在源码的cmd/go
在这里插入图片描述

在这里有个main入口
在这里插入图片描述

在main函数里面执行 invoke 函数
在这里插入图片描述

在invoke里面执行Run
在这里插入图片描述

针对 go test 执行是初始化的test命令
在这里插入图片描述

在test中执行的是runTest
在这里插入图片描述

runTest的内容如下
在这里插入图片描述

会解析入参等
然后会执行
在这里插入图片描述

在builderTest中,构建test程序
在这里插入图片描述

在load包中打包
在这里插入图片描述

在这里插入图片描述

为什么go的测试都是 _test 结尾呢?

在这里插入图片描述

在打包test的时候,会将 path_test 也加入
针对test的程序,会构造一个main入口
在这里插入图片描述

真正的go test main 生成
在这里插入图片描述

使用模板生成
在这里插入图片描述

其中 testmainTmpl 是一个模板
在这里插入图片描述

也是有一个main入口
在这里插入图片描述

在go 1.17 中,渲染后的代码如下

package mainimport ("os""testing""testing/internal/testdeps"_test "mypackage"
)var tests = []testing.InternalTest{{"TestFunc1", _test.TestFunc1},{"TestFunc2", _test.TestFunc2},
}var benchmarks = []testing.InternalBenchmark{{"BenchmarkFunc1", _test.BenchmarkFunc1},
}var examples = []testing.InternalExample{{"ExampleFunc1", _test.ExampleFunc1, "", false},
}func main() {testdeps.ImportPath = "mypackage"m := testing.MainStart(testdeps.TestDeps, tests, benchmarks, examples)os.Exit(m.Run())
}

通过main方法,直到实际上是调用 testing.MainStart获取了一个*testing.M
然后调用m.Run
这就是 Main 测试的执行原理。

test的main如何启动case

接下来看看testing.M是什么
MainStart 初始化并生成了一个testing.M
在这里插入图片描述

Init操作是解析 go test的命令行参数
在这里插入图片描述

testing.M的结构如下

type M struct {deps       testDepstests      []InternalTestbenchmarks []InternalBenchmarkexamples   []InternalExampletimer     *time.TimerafterOnce sync.OncenumRun intexitCode int
}

从上面的结构体可以看出,主要是三类测试用例:单元测试,性能测试和示例测试。
接下来看下Run方法:
在这里插入图片描述

首先根据命令参数,执行不同的逻辑:
*matchList 表示执行 go test -list regStr 表示不是真的执行测试,而是列出 regStr 匹配的case 列表:
在匹配的时候,会对三类用例的name都进行匹配
在这里插入图片描述

*shuffle 表示洗牌,也就是随机,使用随机包rand的Shuffle方法进行洗牌
接着执行befor,在befor里面,主要是对执行环境的一些初始化,或者对命令参数的设置等
在这里插入图片描述

在befor执行后,依次执行三类用例
在这里插入图片描述

等用例执行完成后,执行after,after是对执行结果的汇总等
在这里插入图片描述

最核心的就是三个方法:runTests,runExamples,runBenchmarks

单元测试 runTests

func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest, deadline time.Time) (ran, ok bool) {ok = truefor _, procs := range cpuList {runtime.GOMAXPROCS(procs)for i := uint(0); i < *count; i++ {if shouldFailFast() {break}ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run"))ctx.deadline = deadlinet := &T{common: common{signal:  make(chan bool, 1),barrier: make(chan bool),w:       os.Stdout,},context: ctx,}if Verbose() {t.chatty = newChattyPrinter(t.w)}tRunner(t, func(t *T) {for _, test := range tests {t.Run(test.Name, test.F)}})select {case <-t.signal:default:panic("internal error: tRunner exited without sending on t.signal")}ok = ok && !t.Failed()ran = ran || t.ran}}return ran, ok
}

如果指定了cpu并且指定了count,那么会对单元测试执行 cpu数量乘以count次
接着初始化 TestContext
在这里插入图片描述

然后初始化testing.T
在这里插入图片描述

testing.T组合了TestContext,并且组合了testing.common
testing.common初始化了两个信号channel,用于控制单元测试执行。
最后调用tRunner执行单元测试

tRunner

func tRunner(t *T, fn func(t *T)) {t.runner = callerName(0) // 获取当前测试函数的名称//当这个goroutine完成时,要么是因为fn(t)//正常返回或由于触发测试失败//对运行时的调用。Goexit,记录持续时间并发送//表示测试完成的信号。defer func() {// 测试失败,那么将失败数+1if t.Failed() {atomic.AddUint32(&numFailed, 1)}// 如果测试惊慌失措,请在终止之前打印任何测试输出。err := recover()signal := true// 读锁定t.mu.RLock()// 获取完成状态finished := t.finished// 读锁定解锁t.mu.RUnlock()// 如果测试未完成,但是异常信息为空if !finished && err == nil {// 将错误信息赋值为空错误或空异常err = errNilPanicOrGoexit// 如果有父测试,当前是子测试for p := t.parent; p != nil; p = p.parent {p.mu.RLock()finished = p.finishedp.mu.RUnlock()if finished {t.Errorf("%v: subtest may have called FailNow on a parent test", err)err = nilsignal = falsebreak}}}// 使用延迟调用以确保我们报告测试// 完成,即使清除函数调用t.FailNow。请参见第41355期。didPanic := falsedefer func() {if didPanic {return}if err != nil {panic(err)}//只有在没有恐慌的情况下才报告测试完成,//否则,测试二进制文件可以在死机之前退出//报告给用户。请参见第41479期。t.signal <- signal}()doPanic := func(err interface{}) {// 设置测试失败t.Fail()if r := t.runCleanup(recoverAndReturnPanic); r != nil {t.Logf("cleanup panicked with %v", r)}//在终止之前将输出日志刷新到根目录。for root := &t.common; root.parent != nil; root = root.parent {root.mu.Lock()// 计算时间root.duration += time.Since(root.start)d := root.durationroot.mu.Unlock()root.flushToParent(root.name, "--- FAIL: %s (%s)\n", root.name, fmtDuration(d))if r := root.parent.runCleanup(recoverAndReturnPanic); r != nil {fmt.Fprintf(root.parent.w, "cleanup panicked with %v", r)}}didPanic = truepanic(err)}if err != nil {doPanic(err)}t.duration += time.Since(t.start)// 如果有子测试,当前是父测试if len(t.sub) > 0 {// 停止测试t.context.release()// 释放平行的子测验。close(t.barrier)// 等待子测验完成。for _, sub := range t.sub {<-sub.signal}cleanupStart := time.Now()err := t.runCleanup(recoverAndReturnPanic)t.duration += time.Since(cleanupStart)if err != nil {doPanic(err)}// 如果不是并发的if !t.isParallel {// 等待开始t.context.waitParallel()}} else if t.isParallel { // 如果是并发的//仅当此测试以并行方式运行时才释放其计数 测验请参阅Run方法中的注释。t.context.release()}// 测试执行结束上报日志t.report()t.done = true// 如果有父测试,那么设置执行标志if t.parent != nil && atomic.LoadInt32(&t.hasSub) == 0 {t.setRan()}}()defer func() {if len(t.sub) == 0 {t.runCleanup(normalPanic)}}()t.start = time.Now()t.raceErrors = -race.Errors()fn(t)// code beyond here will not be executed when FailNow is invokedt.mu.Lock()t.finished = truet.mu.Unlock()
}

在tRunner中执行的是 fn(t),其中t就是*testing.T,这也是单元测试的写法标准:

func TestXx(t *testing.T){}

而fn并不是我们在testing.M中指定的单元测试键值对,而是在runTests中进行二次包装的
在这里插入图片描述

换句话说,我们自己写的单元测试,被测试框架经过模板生成test的main启动,然后在进行了初始化后,
进行了按照参数进行分批,接着在goroutine中,按照分配的case进行逐个执行。

testing.T.Run

// 将运行f作为名为name的t的子测试。它在一个单独的goroutine中运行f
// 并且阻塞直到f返回或调用t。并行成为并行测试。
// 运行报告f是否成功(或者至少在调用t.Parallel之前没有失败)。
//
// Run可以从多个goroutine同时调用,但所有此类调用
// 必须在t的外部测试函数返回之前返回。
func (t *T) Run(name string, f func(t *T)) bool {// 将子测试的数量+1atomic.StoreInt32(&t.hasSub, 1)// 获取匹配的测试nametestName, ok, _ := t.context.match.fullName(&t.common, name)// 如果没有配置,那么直接结束if !ok || shouldFailFast() {return true}//记录此调用点的堆栈跟踪,以便如果子测试//在单独的堆栈中运行的函数被标记为助手,我们可以//继续将堆栈遍历到父测试中。var pc [maxStackLen]uintptr// 获取调用者的函数namen := runtime.Callers(2, pc[:])t = &T{ // 创建一个新的 testing.T 用于执行子测试common: common{barrier: make(chan bool),signal:  make(chan bool, 1),name:    testName,parent:  &t.common,level:   t.level + 1,creator: pc[:n],chatty:  t.chatty,},context: t.context,}t.w = indenter{&t.common}if t.chatty != nil {t.chatty.Updatef(t.name, "=== RUN   %s\n", t.name)}//而不是在调用之前减少此测试的运行计数//tRunner并在之后增加它,我们依靠tRunner保持//计数正确。这样可以确保运行一系列顺序测试//而不会被抢占,即使它们的父级是并行测试。这//如果*parallel==1,则可以特别减少意外。go tRunner(t, f)if !<-t.signal {//此时,FailNow很可能是在//其中一个子测验的家长测验。继续中止链的上行。runtime.Goexit()}return !t.failed
}

示例测试 runExamples

func runExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ran, ok bool) {ok = truevar eg InternalExamplefor _, eg = range examples {matched, err := matchString(*match, eg.Name)if err != nil {fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err)os.Exit(1)}if !matched {continue}ran = trueif !runExample(eg) {ok = false}}return ran, ok
}

示例测试就简单一点了,首先根据正则进行匹配,匹配到了就执行,否则就跳过,出错就退出

runExample

在runExample中,首先对标准输出进行拷贝,将控制输出进行解析
在这里插入图片描述

然后在defer中对输出进行比对
在这里插入图片描述

processRunResult

输出结果比对就简单,主要是字符串的一些比较
在这里插入图片描述

在示例测试中,输出结果的行不需要顺序一致,是因为在比对前,会进行排序

性能测试 runBenchmarks

性能测试和单元测试差不多,只是结构体不同,性能测试的结构体是testing.B
在这里插入图片描述

同样的,也是先创建了一个main的testing.B用于启动性能测试,相当于作为初始case
在这里插入图片描述

然后启动初始case的runN启动

runN

runN作为启动性能测试的初始测试,也是逐个执行用户定义的性能测试case
在这里插入图片描述

实际执行的是testing.B.Run方法
在这里插入图片描述

testing.B.Run

testing.B.Runtesting.T.Run类似,主要是对子测试等做处理,然后执行用户的case
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/36929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础快速上手HarmonyOS ArkTS开发4---从简单的页面开始

接着上一次零基础快速上手HarmonyOS ArkTS开发3---应用程序框架的继续往下。 常用基础组件&#xff1a; 概述&#xff1a; 关于组件的一些基础概念就里就不多说了&#xff0c;官方有很详细的说明&#xff0c;而在HarmonyOS按功能分有如下几大类组件&#xff1a;基础组件、容…

安卓短视频去水印v1.7 简洁好用

各大平台视频无水印提取&#xff0c;登录即永久会员&#xff01; 无水印提取&#xff0c;图片无水印提取 视频旋转&#xff0c;倒放&#xff0c;转gif等功能 链接&#xff1a;https://pan.baidu.com/s/1buoJmAvSFBiRkBmHc7Nn5w?pwd2fu4 提取码&#xff1a;2fu4

Lr、LrC软件下载安装 Adobe Lightroom专业摄影后期处理软件安装包分享

Adobe Lightroom它不仅为摄影师们提供了一个强大的照片管理平台&#xff0c;更以其出色的后期处理功能&#xff0c;成为了摄影爱好者们争相追捧的必备工具。 在这款软件中&#xff0c;摄影师们可以轻松地管理自己的照片库&#xff0c;无论是按拍摄日期、主题还是其他自定义标签…

浅谈如何在linux上部署java环境

文章目录 一、部署环境1.1、JDK1.2、Tomcat1.3、MySQL 二、将自己写的的程序部署到云服务器上 一、部署环境 为了在linux上部署 Java web 程序&#xff0c;需要安装一下环境。 1.1、JDK 直接使用 yum 命令安装 openjdk。我们 windows系统上 下载的是 oracle 官方的 jdk。而 …

用Python将PowerPoint演示文稿转换到图片和SVG

PowerPoint演示文稿作为展示创意、分享知识和表达观点的重要工具&#xff0c;被广泛应用于教育、商务汇报及个人项目展示等领域。然而&#xff0c;面对不同的分享场景与接收者需求&#xff0c;有时需要我们将PPT内容以图片形式保存与传播。这样能够避免软件兼容性的限制&#x…

Cisco Identity Services Engine (ISE) 3.3 Patch 2 - 基于身份的网络访问控制和策略实施系统

Cisco Identity Services Engine (ISE) 3.3 Patch 2 - 基于身份的网络访问控制和策略实施系统 思科身份服务引擎 (ISE) - 下一代 NAC 解决方案 请访问原文链接&#xff1a;Cisco Identity Services Engine (ISE) 3.3 Patch 2 - 基于身份的网络访问控制和策略实施系统&#xf…

centos上部署Ollama平台,实现语言大模型本地部署

网上有很多大模型&#xff0c;很多都是远程在线调用ChatGPT的api来实现的&#xff0c;自己本地是没有大模型的&#xff0c;这里和大家分享一个大模型平台&#xff0c;可以实现本地快速部署大模型。 Ollama是一个开源项目&#xff0c;它提供了一个平台和工具集&#xff0c;用于部…

C语言单链表的算法之逆序

一&#xff1a;什么是链表的逆序 &#xff08;1&#xff09;链表的逆序又叫反向&#xff0c;意思就是把链表中所有的有效节点在链表中的顺序给反过来 二&#xff1a;单链表逆序算法分析 &#xff08;1&#xff09;当需要对一个数据结构进行操作时&#xff0c;就有必要有一套算…

JS乌龟吃鸡游戏

代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>乌龟游戏</title><script type"text/javascript">function move(obj){//乌龟图片高度var wuGui_height 67;…

Django 模版继承

1&#xff0c;设计母版页 Test/templates/6/base.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><!-- 修正了模板标签的全角字符问题 -->{% block title %}<title>这个是母版页</title>{…

算法:链表

目录 链表的技巧和操作总结 常用技巧&#xff1a; 链表中的常用操作 题目一&#xff1a;反转一个单链表 题目二&#xff1a;链表的中间结点 题目三&#xff1a;返回倒数第k个结点 题目四&#xff1a;合并两个有序链表 题目五&#xff1a;移除链表元素 题目六&#xff…

平衡二叉搜索树/AVL树

VAL树的特性 左右子树高度差的绝对值不超过1。&#xff08;即左右子树高度差取值为-1&#xff0c;0&#xff0c;1&#xff09;且左右子树均为VAL树右子树的值大于左子树的值 在搜索二叉树中我们提及了搜索二叉树的退化问题。 当有序&#xff08;升序或降序&#xff09;地插入…

摸鱼大数据——Spark基础——Spark环境安装——Spark Local[*]搭建

一、虚拟机配置 查看每一台的虚拟机的IP地址和网关地址 查看路径: cat /etc/sysconfig/network-scripts/ifcfg-ens33 2.修改 VMware的网络地址: 使用VMnet8 3.修改windows的对应VMware的网卡地址 4.通过finalshell 或者其他的shell连接工具即可连接使用即可, 连接后, 测试一…

AD PCB板子裁剪与泪滴设置

在剪裁板子时。首先&#xff0c;选择选择板子的机械层&#xff0c;之后选择画线。在原来的板子上画上自己想要裁剪的图形。如下下图 之后&#xff0c;选择按照所画的线裁剪板子即可&#xff0c;如下 在焊接PCB时&#xff0c;为了防止多次焊接导至焊盘脱落可以加大焊点的接触面积…

ESP32-C3模组上跑通MQTT(6)—— tcp例程(1)

接前一篇文章:ESP32-C3模组上跑通MQTT(5) 《ESP32-C3 物联网工程开发实战》 一分钟了解MQTT协议 ESP32 MQTT API指南-CSDN博客 ESP-IDF MQTT 示例入门_mqtt outbox-CSDN博客 ESP32用自签CA进行MQTT的TLS双向认证通信_esp32 mqtt ssl-CSDN博客 特此致谢! 本回开始正式讲…

华润万家超市卡怎么用?

华润的礼品卡不仅能线下门店使用&#xff0c;还能直接叫送货上门 我最近用积分兑了几张华润卡&#xff0c;但是又没有购物需求&#xff0c;送朋友吧面值又不大&#xff0c;朋友也说用不上 最后朋友建议我在收卡云上把卡出掉&#xff0c;我试了下92折出掉了&#xff0c;价格还…

代码随想录算法训练营第四十七天| 188.买卖股票的最佳时机IV ,309.最佳买卖股票时机含冷冻期 ,714.买卖股票的最佳时机含手续费

188. 买卖股票的最佳时机 IV - 力扣&#xff08;LeetCode&#xff09; class Solution {public int maxProfit(int k, int[] prices) {int[][] dp new int[prices.length][2*k];for(int i0;i<2*k;i){if(i%2 0){dp[0][i] -prices[0];}else{dp[0][i] 0;} }for(int i1;i…

综合项目实战--jenkins节点模式

一、DevOps流程 DevOps是一种方法论,是一系列可以帮助开发者和运维人员在实现各自目标的前提下,向自己的客户或用户交付最大化价值及最高质量成果的基本原则和实践,能让开发、测试、运维效率协同工作的方法。 DevOps流程(自动化测试部分) DevOps完整流程 二、gitee+j…

计算Dice损失的函数

计算Dice损失的函数 def Dice_loss(inputs, target, beta1, smooth 1e-5):n,c, h, w inputs.size() #nt,ht, wt, ct target.size() #nt,if h ! ht and w ! wt:inputs F.interpolate(inputs, size(ht, wt), mode"bilinear", align_cornersTrue)temp_inputs t…

LLaMA-Factory安装

安装代码 https://github.com/echonoshy/cgft-llm/blob/master/llama-factory/README.md https://github.com/hiyouga/LLaMA-Factory/tree/mainLLaMA-Factoryhttps://github.com/hiyouga/LLaMA-Factory/tree/main 【大模型微调】- 使用Llama Factory实现中文llama3微调_哔哩…