前言: \textcolor{Green}{前言:} 前言:
💞这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营
💞从这个专栏里面可以迅速获得Go的知识
Go测试学习
- 03 测试
- 3.1 单元测试
- 3.1.1 单元测试 - 规则
- 3.1.2 单元测试 - 例子
- 3.1.3 单元测试 - 运行
- 3.1.4 单元测试 - assert
- 3.1.5 单元测试 - 覆盖率
- 3.1.5 单元测试 - Tips
- 3.2 单元测试 - 依赖
- 3.3 单元测试 - 文件处理
- 3.4 单元测试 - Mock
- 3.5 基准测试
- 3.5.1 基准测试 - 例子
- 3.5.2 基准测试 - 运行
- 3.5.3 基准测试 - 优化
今天的学习是测试:从单元测试实践出发,提升质量意识.这是对之前内容的回顾学习,通过回顾之前的内容来提升自己。
在开发过程中测试是非常重要的。先来了解一下测试的重要性:
1、通过软件测试确保软件的质量。
2、给开发人员提供信息,以方便其为风险评估做相应的准备。
3、软件测试贯穿在整个软件开发的过程中,保证整个软件开发的过程高质量。
4、通过软件测试发现软件的错误、有效定义和实现软件成分由低层到高层的组装过程。
5、通过软件测试验证软件是否满足任务书和系统定义文档所规定的技术要求。
6、软件测试为软件质量模型的建立提供依据。
通过测试的重要性今天就来学习一下测试
03 测试
这一讲主要有go 测试相关的内容,包括单元测试规范、测试 mock、基准测试。
测试关系着系统的质量,质量决定线上系统的稳定性,一旦发现 bug,就会造成事故。
事故有:
- 营销配置错误,导致非预期用户享受权益,资金损失10W+
- 用户提现,幂等失效,短时间可以多次提现,资金损失20w+(关于幂等问题可以去查一下)
- 代码逻辑错误,广告位被占,无法出广告,收入损失500w+
- 代码指针使用错误,导致APP不可用,损失上kw+
测试是避免事故的最后一道屏障
测试分为:回归测试
是QA手动通过终端回归一些固定的主流程场景;集成测试
是对系统功能维度做测试验证;单元测试
是测试开发阶段,开发者对单独的函数、模块做功能验证;
层级从上到下,测试成本逐渐减低,测试覆盖率逐步上升,所以单元测试的覆盖率一定程度上决定着代码的质量。
3.1 单元测试
单元测试主要包括,输入->测试单元->输出->校对。
单元的概念比较广:包括接口、函数、模块等;用最后的校对来保证代码的功能与我们的预期相符;单侧一方面可以保证质量,在整体覆盖率足够的情况下,一定程度上既保证了新功能本身的正确性,又未破坏原有代码的正确性。其次可以提升效率,在代码有bug的情况下,通过编写单元测试,可以在一个较短周期内定位和修复问题。
3.1.1 单元测试 - 规则
这样从文件上就可以区分源码和测试代码,以 Test 开头,且连接的第一个字母大写。
3.1.2 单元测试 - 例子
func HelloTom() string {
return "Jerry"
}
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
if output ≠ expectOutput {
t.Errorf("Expected %s do not match actual %s", expectOutput, output)
}
}
3.1.3 单元测试 - 运行
3.1.4 单元测试 - assert
func HelloTom() string {
return "Tom"
}
import (
"github.com/stretchr/testify/assert"
"test"
)
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
assert.Equal(t, expectOutput, output)
}
使用assert
注意需要importgithub.com/stretchr/testify/assert
3.1.5 单元测试 - 覆盖率
- 如何衡量代码是否经过了足够的测试?
- 如何评价项目的测试水准?
- 如何评估项目是否达到了高水准测试等级?
这就需要 代码覆盖率
来去解决上述的问题
例子
这是一个判断是否及格的函数,超过 60 分,返回 true,否则返回 false。
右边是对输入为 70 的单元测试,通过执行右边的单测,通过指定的 cover 参数,可以看到覆盖率。
因为没有经过 false 这一行,所以仅仅是 2/3。
接下来提升覆盖率,增加一个不及格的测试 case。重新执行所有的单测,最终得到覆盖率为 100%。
3.1.5 单元测试 - Tips
在实际项目中:
- 一般覆盖率:50%~60%,较高覆盖率 80%+
- 测试分支相互独立、全面覆盖。
- 测试单元粒度足够小,函数单一职责。
3.2 单元测试 - 依赖
工程中复杂的项目,一般会依赖数据库、本地文件等强依赖,而我们的单测需要保证稳定性和幂等性,稳定是指相互隔离,能在任何时间,任何环境,运行测试。幂等是指每一次测试运行都应该产生与之前一样的结果,而要实现这一目的就要用用到 mock 机制。
3.3 单元测试 - 文件处理
替换文件中第一行字符串中的 11,将其换为 00。测试通过,会发现 单测需要依赖本地的文件,如果文件被修改或者删除就会 fail。为了保证测试 case的稳定性,需要对读取文件函数进行 mock,从而屏蔽对文件的依赖。
3.4 单元测试 - Mock
monkey
monkey 是一个开源的 mock 测试库,可以对 method 或者实例的方法进行 mock。
反射指的是指针赋值。
Mockey Patch 的作用域在 Runtime,在运行时通过 Go 的 unsafe 包,能够将内存中函数的地址替换为运行时函数的地址。
下面是一个 mock 的使用样例, 通过 patch 对 Readfineline 进行打桩 mock,默认返回 line110,通过 defer 卸载 mock,这样整个测试函数就需要本地文件的束缚和依赖。
对 ReadFirstLine 打桩测试,不再依赖本地文件
3.5 基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
go 语言提供了基准测试框架,基准测试是指测试一段程序的运行性能以及耗费CPU 的程度,而我们在实际项目开发中,经常会遇到代码性能瓶颈,为了定位问题经常要对代码进行性能分析,这就用到了基准测试。使用方法类似于单元测试。
3.5.1 基准测试 - 例子
下面是 服务器负载均衡的例子,首先有10个服务器列表,每次随机执行 select 函数随机选择一个执行。
import (
"math/rand"
)
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
func Select() int {
return ServerIndex[rand.Intn(10)]
}
10个服务器随机选择一个服务器执行。对其进行一个基准测试
3.5.2 基准测试 - 运行
基准测试 以 Benchmark 开头,入参 试 testing.B,用 b 中的 N 值反复递增循环测试。
(对于一个测试用例的默认测试时间为1s,当测试用例函数返回时还不到1s,那么testing.B中的N值将按1、2、5、10、20、50…递增,并以递增后的值重新进行用例函数测试)
Resetimer重置计时器,再 reset之前做了int或其他的准备工作,这些不作为基准测试的范围;RunParallel 是多协程并发测试;执行两个基准测试,发现代码在并发情况下存在劣化,主要原因是 rand 为了保证全局随机性和并发安全,持有了一把全局锁
3.5.3 基准测试 - 优化
为了解决随机性能问题,开源一个高性能随机数方法 fastrand。开源地址 再次进行基准测试,发现性能会提升百倍为什么?这是因为牺牲了一定的数列一致性。