前言:
企业开发与个人开发不同,程序上线前进行测试对于企业开发来说便是一个必要的活动,任何一个小的bug都会带来呢巨大的损失,不能直接部署到生产环境。
有多种测试方法可以使用,例如回归测试,集成测试,单元测试
从上到下,覆盖率逐层变大,成本却逐层降低
- 单元测试:单元测试主要是对软件中的最小可测试单元进行检查和验证。例如,如果你正在编写一个文本编辑器,那么你可能有一个输入框(单元)来处理用户的输入。在单元测试中,测试人员会编写一些代码来检查这个输入框是否如预期那样工作。如果输入框接受了正确的输入,那么测试就会通过,否则测试就会失败。
- 集成测试:集成测试主要是检查各个单元或模块是否能够正确地一起工作。例如,在一个文本编辑器中,除了输入框之外,可能还有其他模块,如菜单、拼写检查器等。在集成测试中,测试人员会编写代码来组合这些模块并确保它们能够协同工作。如果一个模块无法正常工作,那么整个系统就无法正常工作。
- 回归测试:回归测试是在软件的某个部分进行修改或更新后重新运行测试以确保新功能没有破坏原有功能。回归测试的目标是确定任何更改都没有引入新的问题,并且旧的功能仍然能够正常工作。这通常是一个重要的测试过程,因为修复一个问题可能会引入更多的问题。
一、单元测试
1.1 规则
以 _test.go
结尾的代码会被 Go 识别为单元测试文件
一个单元测试函数的函数名应当以 Test
开头,并包含 *testing.T
形参。
可通过 func TestMain(m *testing.M)
函数对测试数据进行初始化,并调用 m.Run()
运行单元测试。
初始化逻辑放到TestMain中
1.2 例子
// In xxx.go:
func HelloTom() string {
return "Jerry"
}
// In xxx_test.go:
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
if output != expectOutput {
t.Errorf("Expected %s do not match actual %s", expectOutput, output)
}
}
1.3 运行
tip:GOland快速生成
可以利用:github.com/stretchr/testify测试库
1.4 assert
import(
"github.com/stretchr/testify/assert"
)
Github社区提供的依赖库来加快单元测试开发,诸如通过 testify/assert
库进行覆盖率测试,或通过 bouk/monkey
库对数据进行 Mock
1.5 覆盖率
如何衡量代码是否经过了足够的测试?
如何评价项目的测试水准?
如何评估项目是否达到了高水准测试等级?
这是有一个判断是否及格的函数,超过60分,返回true,否则返回talse, 右边是对输入为70 的单元测试,我们执行右边的单侧,通过指定cover参数,我们看输出了覆盖率为***?一共三行,我们的单测试执行了2行,所以为***
提升覆盖率,增加一个不及格的测试case, 重新执行所有单侧,覆盖率100%
Tips
一般覆盖率:50%~60%,较高覆盖率80%。
测试分支相互独立、全面覆盖。
测试单元粒度足够小,,函数单一职责。
2.0 依赖
外部依赖 => 稳定&幂等
单元测试的两个目标是幂等和稳定:
- 幂等:重复运行一个测试,其结果都是一样的。
- 稳定:单元测试是相互隔离的,在任何时间任何函数都可运行。
直接写单元测试可能是不稳定的(存在一些依赖),在单元测试时使用mock测试
3.0 文件处理
package main
import (
"bufio"
"os"
"strings"
)
func ReadFirstLine() string {
// 打开一个文件
open, err := os.Open("log.txt")
// defer关键字,用于延迟函数的执行
// 这里的作用是在函数 ReadFirstLine() 执行结束后即使发生错误或提前返回,
// 也会确保文件资源 open 被及时关闭,避免资源泄漏。
defer open.Close()
// 判断是否发生error
if err != nil {
return ""
}
scanner := bufio.NewScanner(open)
for scanner.Scan() {
// 只读取第一行的内容 返回
return scanner.Text() // 用于获取scanner当前所在位置的文本内容
}
return ""
}
func ProcessFirstLine() string {
// 读取文件中的行
line := ReadFirstLine()
// 把读到的行进行字符串替换,把11替换成00
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
package main
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestProcessFirstLine(t *testing.T) {
firstLine := ProcessFirstLine()
assert.Equal(t, "line00", firstLine)
}
对log.txt有强依赖
4.0 Mock
Mock测试是一种测试方法,用于模拟应用程序的某些部分(例如API、数据库、文件系统等)的行为,以便在测试期间隔离和替换这些部分。这样,测试可以专注于测试应用程序的核心功能的,而不必担心外部资源或服务的的影响。
Mock测试可以在单元测试、集成测试或系统测试中使用。在单元测试中,它们用于模拟对象之间的交互。在集成测试中,它们用于模拟子系统如何协同工作。在系统测试中,它们用于模拟整个系统的工作流程。
Mock测试有许多好处,包括:
隔离测试环境:Mock测试允许测试人员在测试环境中模拟特定部分,以便测试人员可以专注于测试核心功能,而不必担心外部因素。
模拟异常和错误情况:Mock测试允许测试人员模拟异常和错误情况,以便测试应用程序如何在异常和错误情况下运行。
可重复性:由于Mock测试是模拟的,因此可以在相同的环境下重复运行测试,以便获得一致的结果。
提高测试效率:Mock测试允许测试人员更快地运行更多测试,因为它们不需要真实资源的或服务。
Mock测试工具很多,例如JMockit、Mockito、Testify等。这些工具可以帮助开发人员和测试人员创建Mock对象,以便进行更有效的测试。
引入monkey:bou.ke/monkey
对 ReadFirstLine 打桩测试,不在依赖本地文件
5.0 基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
5.1 例子
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)]
}
package main
import "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()
}
})
}
5.2 运行
goos: darwin
goarch: arm64
pkg: go/Test
BenchmarkSelect
BenchmarkSelect-8 87115917 13.54 ns/op
BenchmarkSelectParallel
BenchmarkSelectParallel-8 12678332 101.1 ns/op
PASS