【字节跳动青训营】后端笔记整理-3 | Go语言工程实践之测试
**本文由博主本人整理自第六届字节跳动青训营(后端组),首发于稀土掘金:🔗Go语言工程实践之测试 | 青训营
目录
一、概述
1、回归测试
2、集成测试
3、单元测试
二、单元测试
1、流程
2、规则
3、单元测试的例子
4、assert
5、覆盖率
6、依赖
7、文件处理
三、Mock测试
四、基准测试
一、概述
测试主要包括:回归测试、集成测试、单元测试。
1、回归测试
用于验证已经修改或新增功能后,软件的既有功能是否受到影响。
它主要用于确保软件在经过修改后仍然能正确运行,并且新的更改没有引入新的错误或破坏原有的功能。
比如开发抖音产品,那么回归测试就是新增了一部分功能后,由负责质量保证的部门手动在终端创造一些特定的场景(比如刷一下抖音,看一下评论等)来检查功能是否正确。
2、集成测试
用于验证多个模块或组件在一起协同工作时的正确性。
它主要用于检查不同模块之间的交互和接口是否正常,以确保整个软件系统在集成后能够正常运行。在软件开发过程中,通常会将软件系统划分为多个模块或组件,每个模块负责实现不同的功能。
在集成测试中,这些模块会被组合在一起,并进行全面的测试,以验证它们之间的协作和接口是否正确。
3、单元测试
在开发阶段,开发者对单独的函数模块进行功能测试。单元测试用于验证程序中的各个独立模块(通常是函数或方法)是否按照预期进行工作。它是在软件开发中最小的测试单位,旨在测试代码的最小功能单元,以确保每个单元都能正确地完成其预定的功能。

层级从上到下,测试的覆盖率逐层变大,成本逐层降低。因此可以说,单元测试的覆盖率一定程度上决定了代码的质量。
- 单元测试的成本较低,覆盖率较高,可以确保每个单元都能正确工作。
- 集成测试的成本适中,覆盖率较低,主要用于验证不同模块之间的协作。
- 回归测试的成本较高,主要用于验证整个系统的稳定性和功能性。
在实际开发中,这三种测试方法通常会结合使用,以便在不同的层次和阶段上确保软件的质量和稳定性。
二、单元测试
1、流程
单元测试主要包括输入、测试单元、输出以及校对。
- “单元”的概念比较广,包括接口、函数、模块等等。
- 用最后的校对来保证代码的功能与我们预期的相符。最后通过输出和期望值作校对,来验证代码的正确性。
- 单元测试一方面可以保证质量。每次编写新代码并加入了单元测试,在代码整体覆盖率足够的情况下一方面保证了新功能本身的正确性,又未破坏原有的正确性;另一方面可以提升效率,在代码有bug的情况下通过编写单测,可以在一个较短的周期内定位和修复问题。
2、规则
(1)所有的测试文件都以 _test.go 结尾。这样可以很容易辨别哪些文件是 go 的源代码,哪些是测试代码。

(2)单元测试函数的命名规范,以Test开头,且Test后面的第一个字母大写。如TestDemo。

(3)单元测试提供了一个TestMain函数,TestMain是 Go 语言中测试包(testing)的一个特殊函数,用于在运行测试之前和之后执行一些初始化和收尾操作。它不是针对单个测试用例的,而是整个测试包的入口函数。它可以被用来替代测试函数的默认入口点 func TestXxx(t *testing.T)。
TestMain的代码结构如下,它会在所有测试函数执行之前运行,并且可以在其中进行全局初始化和清理操作。然后,它会调用 m.Run() 启动所有测试函数的执行。
因为它是测试包的入口函数,所以只会执行一次。

需要注意的是,测试函数是并行执行的,所以不能依赖测试函数之间的执行顺序。
3、单元测试的例子
先在demo2.go文件中,创建一个HelloTom函数,预期返回"Tom"。
(这里为了测试,故意写成返回"Jerry"。)
//demo2.gopackage mainfunc HelloTom() string {return "Jerry"
}
然后生成测试文件: demo2_test.go。
Tips:如果使用 GoLand 为IDE,快捷键 alt+insert ,然后选择Test file,可以直接生成一个单元测试文件。


在demo2_test.go中编写以下测试代码:
package mainimport "testing"func TestHelloTom(t *testing.T) {output := HelloTom() //运行HelloTom 接收返回值expectOutput := "Tom" //预期输出Tomif output != expectOutput {t.Errorf("Expected %s do not match actual %s", expectOutput, output)}
}
通过 if 的方式校对 output 是否与 expected 相等。如果相等,则测试PASS,不相等则FAIL。
除了直接用运算符来判断,还有更加简便的方式,即使用 assert。
4、assert
除了使用运算符进行校对,也有很多开源的assert包可以帮助我们实现预期和实际输出equal或not equal的比较。
导入开源的assert包:
github.com/stretchr/testify/assert
可以在终端使用以下命令获取依赖(如果没有下载过,直接引入包会报红):
go get github.com/stretchr/testify/assert
调用 assert.Equal来对预期和实际输出进行比较。第二个参数是预期输出,第三个参数是实际输出:
package mainimport ("github.com/stretchr/testify/assert" // 导入开源的assert包"testing"
)func TestHelloTom(t *testing.T) {output := HelloTom() //运行HelloTom 接收返回值expectOutput := "Tom" //预期输出Tom//if output != expectOutput {// t.Errorf("Expected %s do not match actual %s", expectOutput, output)//}assert.Equal(t, expectOutput, output) //直接调用包中的接口
}
运行结果:actual 和 expected 不符合,测试FAIL。

若将HelloTom模块返回的值改为Tom,再次执行单元测试:

可见此时单元测试的结果是PASS。
5、覆盖率
在我们进行单元测试时需要考虑以下问题:
如何衡量代码是否经过了足够的测试?如何评价项目的测试水准?如何评估项目是否达到了高水准测试等级?
单元测试的主要评估标准是代码覆盖率,覆盖率越高则证明越多的代码经过了测试。来看下面这个例子:
创建demo3.go,其中编写了一个判断分数是否及格的功能。
package mainfunc judgePass(score int) bool {//return score >= 60if score >= 60 {return true}return false
}
生成测试文件demo3_test.go,调用 judgePass 并传入一个70,将结果与true校对。
package mainimport ("github.com/stretchr/testify/assert""testing"
)func Test_judgePass(t *testing.T) {isPass := judgePass(70)assert.Equal(t, true, isPass)
}
然后运行带有覆盖率(coverage)的测试。可以用命令单独测试某个模块(如demo3.go):
go test demo3_test.go demo3.go --cover
也可以在GoLand直接操作,点击右上角的run with coverage进行测试。测试工具会自动运行测试用例,并在执行过程中跟踪被执行的代码行数。最后,它会计算测试覆盖率并给出结果。(注意,这样执行的是该目录下的所有测试文件,给出的也是整个包的测试覆盖率结果。)

最终的代码覆盖率为66.7%。这是因为demo3.go中的3条语句在case为70时执行2条。

如果在 judgePass 中直接写成 return score >= 60,那么代码的覆盖率就会变成100%。因为这个函数中所有的语句都被执行过了。
特别注意:直接点击run with coverage,执行的是该目录下的所有测试文件,给出的也是整个包的测试覆盖率结果。如果该目录下还有其它文件代码,执行测试用例后会发现覆盖率降低:
如何提升覆盖率?
刚才只是测试了70,覆盖率只有66.7%,还有部分代码没有被运行。我们希望提升覆盖率,因此可以多传入一些测试用例case。比如这里,再传入一个50:

coverage达到了 100%。
6、依赖
实际项目中,测试依赖的组件可能会很复杂。比如可能依赖一些数据库,文件,cache等。这些都属于项目中的强依赖(即是一个模块对于另一个模块存在紧密的依赖关系。当一个组件的实现或功能发生变化时,会直接影响到依赖它的其他组件的正确性或稳定性)。
而单元测试的两个目标是幂等和稳定:
- 幂等:重复运行一个测试,其结果都是一样的。
- 稳定:单元测试是相互隔离的,在任何时间任何函数都可运行。
其实,直接写单元测试可能是不稳定的,因为它可能存在一些依赖,如网络等。解决这个问题,可以在单元测试时使用mock测试。

7、文件处理
可以用文件依赖来演示一下单元测试中的依赖问题。

首先创建文件 log.txt,其中内容如下:

创建demo4.go,编写函数用于实现“读取文件的第一行”这一功能。
package mainimport ("bufio""os""strings"
)func ReadFirstLine() string {// 打开一个文件open, err := os.Open("log.txt")// defer关键字,用于延迟函数的执行// 这里的作用是在函数 ReadFirstLine() 执行结束后即使发生错误或提前返回,// 也会确保文件资源 open 被及时关闭,避免资源泄漏。defer open.Close()// 判断是否发生errorif err != nil {return ""}// 创建文件扫描器scannerscanner := bufio.NewScanner(open)for scanner.Scan() {// 只读取第一行的内容 返回return scanner.Text() // 用于获取scanner当前所在位置的文本内容}return ""
}func ProcessFirstLine() string {// 读取文件中的行line := ReadFirstLine()// 把读到的行进行字符串替换,把11替换成00destLine := strings.ReplaceAll(line, "11", "00")return destLine
}
生成单元测试。同时创建log.txt文件进行文件操作。
package mainimport ("github.com/stretchr/testify/assert""testing"
)func Test(t *testing.T) {firstLine := ProcessFirstLine()assert.Equal(t, "line00", firstLine) // 别忘了第二个参数是expected,第三个是actual
}
运行:

这里对文件 log.txt 的依赖就是强依赖。
一旦文件被别人篡改,那测试文件可能也会受到影响,甚至无法执行。这样就无法达到单元测试幂等、稳定的目标。
三、Mock测试
Mock(模拟)是一种测试技术,用于在测试代码时替代某些依赖项或功能,以便进行可控制和可预测的测试。这种技术的目的是模拟真实环境中的特定行为,从而使开发人员能够对软件的不同部分进行独立测试,而不需要依赖其他组件的完整性或稳定性。
打桩(Stubbing)是Mock技术的一种应用。它是在单元测试中使用模拟对象(通常称为“桩”或“stub”)代替真实的依赖项或功能,以模拟这些依赖项或功能的行为。打桩的目的是在测试代码的过程中隔离被测试代码,并使测试更简单、可控、可重复和高效。
举个例子,假设有一个函数A,它依赖于函数B的返回结果。在单元测试函数A时,我们可以打桩函数B的行为,使其返回我们预先设定好的值,而不是实际去调用函数B。这样就能够独立地测试函数A的逻辑,而不必担心函数B的实际行为。
演示一下对 demo4.go 中的 ReadFirstLine 进行打桩测试,不再依赖本地文件:
首先引入monkey:bou.ke/monkey
monkey是一个开源的mock测试库,可以对函数或方法进行mock。这是monkey中的打桩函数和卸载打桩函数:

我们就调用它们来实现mock。在测试文件中编写以下代码:
package mainimport ("bou.ke/monkey""github.com/stretchr/testify/assert""testing"
)func TestProcessFirstLine(t *testing.T) {// 调用打桩函数 打桩函数实现了mock的功能// 对ReadFirstLine进行打桩操作,替换函数为输出“line110”monkey.Patch(ReadFirstLine, func() string {return "line11"})// 使用defer完成打桩函数的卸载defer monkey.Unpatch(ReadFirstLine)// 再次获取函数的返回值firstLine := ProcessFirstLine()// 通过mock,就避免了对log.txt的强依赖。这个单元测试可以在任何时间任何环境去执行assert.Equal(t, "line00", firstLine)
}
调用打桩函数,模拟ReadFirstLine函数的功能。这样,在该测试模块中并没有实际调用ReadFirstLine函数,也没有用到 log.txt ,但是也能完成测试。而且不会因为文件遭到破坏而无法验证。
四、基准测试
go中也提供了基准测试的框架。在 Go 语言中,基准测试用于衡量代码的性能(如运行性能和cpu损耗),特别是在处理大量数据时的性能表现。在实际中,当遇到代码性能瓶颈时,为了定位问题经常要对代码做性能分析,这就用到了基准测试。基准测试的使用方法类似于单元测试。
创建demo5.go,这是一个模拟随机选择服务器的程序:
package mainimport "math/rand"var ServerIndex [10]intfunc InitServerIndex() {for i := 0; i < 10; i++ {ServerIndex[i] = i + 100}
}func Select() int {// 模拟通过下标来随机选择服务器return ServerIndex[rand.Intn(10)]
}
编写测试文件,下面写了两个基准测试函数:
package mainimport ("testing"
)func BenchmarkSelect(b *testing.B) {InitServerIndex()b.ResetTimer() //定时器重置,因为函数InitServerIndex不属于要测试的函数的损耗,所以计时要把这个时间去掉for i := 0; i < b.N; i++ {Select()}
}// 基准测试也支持并行
func BenchmarkSelectParallel(b *testing.B) {InitServerIndex()b.ResetTimer()b.RunParallel(func(pb *testing.PB) {for pb.Next() {Select()}})
}
BenchmarkSelect:这个基准测试函数用于测试Select函数的性能。在基准测试开始之前,调用InitServerIndex函数进行初始化工作。然后,通过循环运行Select函数b.N次,b.N表示测试运行的迭代次数。
BenchmarkSelectParallel:这个基准测试函数也用于测试Select函数的性能,不同之处在于它使用了并行测试。通过b.RunParallel函数,我们可以在多个 goroutine 中并行运行Select函数。这样可以更好地利用多核处理器的性能,加快测试的执行速度。
运行结果:

基准测试也支持并行执行,但是可见并行去做基准测试的情况下,它的性能退化了。原因是Select中用到了rand函数,而rand为了保证全局的随机性和并发安全,它持有全局锁。因此就降低了并发的性能。
用fastrand可以提升性能。后期如果有随机场景,推荐使用fastrand。

相关文章:
【字节跳动青训营】后端笔记整理-3 | Go语言工程实践之测试
**本文由博主本人整理自第六届字节跳动青训营(后端组),首发于稀土掘金:🔗Go语言工程实践之测试 | 青训营 目录 一、概述 1、回归测试 2、集成测试 3、单元测试 二、单元测试 1、流程 2、规则 3、单元测试的例…...
【Android】Recyclerview的缓存复用
介绍 RecyclerView是Android开发中常用的一个高度可定制的列表视图组件。它是在ListView和GridView的基础上进行了改进和增强,旨在提供更好的性能和更灵活的布局管理。 RecyclerView的主要特点如下: 灵活的布局管理器(LayoutManager&#…...
机器学习:混合高斯聚类GMM(求聚类标签)+PCA降维(3维降2维)习题
使用混合高斯模型 GMM,计算如下数据点的聚类过程: Datanp.array([1,2,6,7]) 均值初值为: μ1,μ21,5 权重初值为: w1,w20.5,0.5 方差: std1,std21,1 K2 10 次迭代后数据的聚类标签是多少? 采用python代码实现: from scipy import…...
libuv库学习笔记-processes
Processes libuv提供了相当多的子进程管理函数,并且是跨平台的,还允许使用stream,或者说pipe完成进程间通信。 在UNIX中有一个共识,就是进程只做一件事,并把它做好。因此,进程通常通过创建子进程来完成不…...
c++ 给无名形参提供默认值
如上图,若函数的形参不在函数体里使用,可以不提供形参名,而且可以给此形参提供默认值。也能编译通过。 在看vs2019上的源码时,也出现了这种写法。应用SFINAE(substitute false is not an error)原则&#x…...
NO1.使用命令行创建Maven工程
①在工作空间目录下打开命令窗口 ②使用命令行生成Maven工程 mvn archetype:generate 运行 MVN 原型:生成命令,下面根据提示操作 选择一个数字或应用过滤器(格式:[groupId:]artifactId,区分大小写包含)&a…...
深度学习入门(一):神经网络基础
一、深度学习概念 1、定义 通过训练多层网络结构对位置数据进行分类或回归,深度学习解决特征工程问题。 2、深度学习应用 图像处理语言识别自然语言处理 在移动端不太好,计算量太大了,速度可能会慢 eg.医学应用、自动上色 3、例子 使用…...
网络知识整理
网络知识整理 网络拓扑网关默认网关 数据传输拓扑结构层面协议层面 网络拓扑 网关 连接两个不同的网络的设备都可以叫网关设备,网关的作用就是实现两个网络之间进行通讯与控制。 网关设备可以是交换机(三层及以上才能跨网络) 、路由器、启用了路由协议的服务器、代…...
如何有效地使用ChatGPT写小说讲故事?
构思故事情节,虽有趣但耗时,容易陷入写作瓶颈。ChatGPT可提供灵感,帮你解决写作难题。要写出引人入胜的故事,关键在于抓住八个要素——主题、人物、视角、背景、情节、语气、冲突和解决办法。 直接给出故事模板,你可…...
原生求生记:揭秘UniApp的原生能力限制
文章目录 1. 样式适配问题2. 性能问题3. 原生能力限制4. 插件兼容性问题5. 第三方组件库兼容性问题6. 全局变量污染7. 调试和定位问题8. 版本兼容性问题9. 前端生态限制10. 文档和支持附录:「简历必备」前后端实战项目(推荐:⭐️⭐️⭐️⭐️…...
网络编程 IO多路复用 [epoll版] (TCP网络聊天室)
//head.h 头文件 //TcpGrpSer.c 服务器端 //TcpGrpUsr.c 客户端 通过IO多路复用实现服务器在单进程单线程下可以与多个客户端交互 API epoll函数 #include<sys/epoll.h> int epoll_create(int size); 功能:创建一个epoll句柄//创建红黑树根…...
【go-zero】浅析 01
“github.com/google/uuid” uuid.New().String() go-zero 文档 https://www.w3cschool.cn/gozero/ go-zero 官网 https://go-zero.dev/ 快速开始: $ mkdir go-zero-demo $ cd go-zero-demo $ go mod init go-zero-demo $ goctl api new greet $ go mod tidy Done…...
音视频——视频流H264编码格式
1 H264介绍 我们了解了什么是宏快,宏快作为压缩视频的最小的一部分,需要被组织,然后在网络之间做相互传输。 H264更深层次 —》宏块 太浅了 如果单纯的用宏快来发送数据是杂乱无章的,就好像在没有集装箱 出现之前,…...
【使用深度学习的城市声音分类】使用从提取音频特征(频谱图)中提取的深度学习进行声音分类研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
机器学习完整路径
一个机器学习项目从开始到结束大致分为 5 步,分别是定义问题、收集数据和预处理、选择算法和确定模型、训练拟合模型、评估并优化模型性能。是一个循环迭代的过程,优秀的模型都是一次次迭代的产物。 定义问题 要剖析业务场景,设定清晰的目标…...
CK-00靶机详解
CK-00靶机详解 靶场下载地址:https://download.vulnhub.com/ck/CK-00.zip 这个靶场扫描到ip打开后发现主页面css是有问题的,一般这种情况就是没有配置域名解析。 我们网站主页右击查看源代码,发现一个域名。 把域名添加到我们hosts文件中。…...
17-C++ 数据结构 - 栈
📖 1.1 什么是栈 栈是一种线性数据结构,具有后进先出(Last-In-First-Out,LIFO)的特点。可以类比为装满盘子的餐桌,每次放盘子都放在最上面,取盘子时也从最上面取,因此最后放进去的盘…...
Redis如何实现排行榜?
今天给大家简单聊聊 Redis Sorted Set 数据类型底层的实现原理和游戏排行榜实战。特别简单,一点也不深入,也就 7 张图,粉丝可放心食用,哈哈哈哈哈~~~~。 1. 是什么 Sorted Sets 与 Sets 类似,是一种集合类型ÿ…...
Pycharm debug程序,跳转至指定循环条件/循环次数
在断点出右键,然后设置条件 示例 for i in range(1,100):a i 1b i 2print(a, b, i) 注意: 1、你应该debug断点在循环后的位置而不是循环上的位置,然后你就可以设置你的条件进入到指定的循环上了 2、设置条件,要使用等于符号…...
react实现markdown
参考:https://blog.csdn.net/Jack_lzx/article/details/118495763 参考:https://blog.csdn.net/m0_48474585/article/details/119742984 0. 示例 用react实现markdown编辑器 1.基本布局及样式 <><div classNametf_editor_header>头部&…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

