ns解析网站,代做网站公司哪家好,免费做淘宝店铺招牌的网站,备案网站域名被抢注**本文由博主本人整理自第六届字节跳动青训营#xff08;后端组#xff09;#xff0c;首发于稀土掘金#xff1a;#x1f517;Go语言工程实践之测试 | 青训营
目录
一、概述
1、回归测试
2、集成测试
3、单元测试
二、单元测试
1、流程
2、规则
3、单元测试的例…**本文由博主本人整理自第六届字节跳动青训营后端组首发于稀土掘金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快捷键 altinsert 然后选择Test file可以直接生成一个单元测试文件。 在demo2_test.go中编写以下测试代码
package mainimport testingfunc 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/asserttesting
)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 (bufioosstrings
)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/asserttesting
)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 进行打桩测试不再依赖本地文件
首先引入monkeybou.ke/monkey
monkey是一个开源的mock测试库可以对函数或方法进行mock。这是monkey中的打桩函数和卸载打桩函数 我们就调用它们来实现mock。在测试文件中编写以下代码
package mainimport (bou.ke/monkeygithub.com/stretchr/testify/asserttesting
)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/randvar 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。