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.pswp.cn/web/36929.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/36929.shtml
英文地址,請注明出處:http://en.pswp.cn/web/36929.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java面試題:解釋反應式編程的概念,并討論如何在Java中使用RxJava或Project Reactor實現

反應式編程&#xff08;Reactive Programming&#xff09;是一種基于異步數據流和變化傳播的編程范式。它強調通過聲明式編程來處理異步事件流和數據流&#xff0c;簡化了復雜的異步操作和并發編程。反應式編程適用于處理異步事件、多線程處理、大量數據流、用戶交互等場景。 …

零基礎快速上手HarmonyOS ArkTS開發4---從簡單的頁面開始

接著上一次零基礎快速上手HarmonyOS ArkTS開發3---應用程序框架的繼續往下。 常用基礎組件&#xff1a; 概述&#xff1a; 關于組件的一些基礎概念就里就不多說了&#xff0c;官方有很詳細的說明&#xff0c;而在HarmonyOS按功能分有如下幾大類組件&#xff1a;基礎組件、容…

springboot筆記示例八:yml文件數據庫連接redis密碼加密實現使用jasypt加密

springboot筆記示例八&#xff1a;yml文件數據庫連接redis密碼加密實現使用jasypt加密 本文md文件下載 https://download.csdn.net/download/a254939392/89496228點擊下載本文md文件 說明 springboot中大多數配置我們都采用yml文件配置&#xff0c;比如數據庫連接&#xff…

安卓短視頻去水印v1.7 簡潔好用

各大平臺視頻無水印提取&#xff0c;登錄即永久會員&#xff01; 無水印提取&#xff0c;圖片無水印提取 視頻旋轉&#xff0c;倒放&#xff0c;轉gif等功能 鏈接&#xff1a;https://pan.baidu.com/s/1buoJmAvSFBiRkBmHc7Nn5w?pwd2fu4 提取碼&#xff1a;2fu4

LeetCode-數值-No49字母異位詞

題目&#xff1a; 給你一個字符串數組&#xff0c;請你將 字母異位詞 組合在一起。可以按任意順序返回結果列表。字母異位詞 是由重新排列源單詞的所有字母得到的一個新單詞。 示例 1: 輸入: strs ["eat", "tea", "tan", "ate", &q…

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…

能求一個數字的字符數量的程序

目錄 開頭程序程序的流程圖程序輸入與打印的效果例1輸入輸出 例2輸入輸出 關于這個程序的一些實用內容結尾 開頭 大家好&#xff0c;我叫這是我58&#xff0c;今天&#xff0c;我們先來看一下下面的程序。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>…

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;…

Conda跨平臺環境遷移

問題描述&#xff1a; 在一臺Ubuntu電腦上完全復刻在Windows中通過conda創建的環境。 導出環境 在Windows機器上&#xff0c;需要導出當前conda環境的配置。這將生成一個environment.yml文件&#xff0c;其中包含所有已安裝的包和版本信息。 打開Anaconda Prompt&#xff08;…

第一天:SLAM整體算法框架簡介

從零開始搭建一套SLAM系統 第一天:整體算法框架簡介以及學習建議 SLAM是什么 SLAM 和 SFM 是什么關系 不同點: SFM (Structure From Motion),稱之為傳統三維重建,這是一門計算機視覺學科的分支,特點是把圖片數據集集回來,離線慢慢精細化處理。常見應用就是重建某建筑物…

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…

Linux下命令行重定向運算符的使用辦法

在Linux下&#xff0c;> 和 >> 是兩種常用的輸出重定向運算符&#xff0c;它們分別代表了覆蓋寫入和追加寫入的文件操作。這些運算符在命令行交互、腳本編程以及日常的系統管理中極為重要&#xff0c;能夠有效地控制程序或命令的輸出流向&#xff0c;提高工作效率。 …

平衡二叉搜索樹/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連接工具即可連接使用即可, 連接后, 測試一…