测试
测试分为:回归测试、集成测试、单元测试
单元测试
单元测试方便调试,避免每次出现bug时都直接重新编译部署,代价太大。
- 所有测试文件以_test.go结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
func TestPublishPost(t *testing.T) {
}
func TestMain(m *testing.M) {
// 测试前:数据装载、配置初始化等前置工作
code := m.Run() // 运行该package下的所有单元测试
// 测试后:释放资源等收尾工作
os.Exit(code)
}
覆盖率
-
衡量代码是否经过了足够的测试
-
评价项目的测试水准
-
评估项目是否达到了高水准测试等级
package test
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
func TestJudgePassLineFail(t *testing.T) {
isPass := JudgePassLine(50)
assert.Equal(t, false, isPass)
}
Tips:
- 一般覆盖率:50%~60%,较高覆盖率80%+。
- 测试分支相互独立、全面覆盖
- 测试单元力度足够小,函数单一职责
依赖
幂等性:单元测试执行多少次结果都一样
稳定性:单元测试相互隔离、能在任何时间、任何函数进行独立运行
如果直接在File、DB、Cache上运行单元测试则缺少稳定性,下面是一个文件处理的单元测试
// readFileFirstLine.go
func ReadFirstLine() string {
open, err := os.Open("log")
defer open.Close()
if err != nil {
return ""
}
scanner := bufio.NewScanner(open)
for scanner.Scan() {
return scanner.Text()
}
return ""
}
func ProcessFirstLine() string {
line := ReadFirstLine()
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
// TestreadFileFirstLine.go
func TestProcessFirstLine(t *testing.T) {
firstLine := ProcessFirstLine()
assert.Equal(t, "line00", firstLine)
}
这里的单元测试依赖于log文件,如果log文件被删除或者篡改那么单元测试的结果也会随着改变,不具有稳定性。所以我们采取具有稳定性的Mock测试
Mock测试
Mock测试是一种软件测试方法,用于测试程序中独立的模块或组件。Mock测试的目的是模拟测试组件的行为,以测试程序的其他部分是否能正确地与它交互。在Mock测试中,我们可以通过使用模拟对象来模拟一个组件或模块的行为,而不必实际运行该组件或模块。这种方法可以使测试更加快读、高效,也可以更容易地调试和诊断问题。Mock测试通常用于单元测试中,但也可以用于其他类型的测试。
快速mock函数
- 为一个函数打桩
- 为一个方法打桩
monkey: github.com/bouk/monkey 开源mock测试包,课程视频中采用的是这种,但介于该库已经很多年没有更新了,这里我采用的是这个包 github.com/stretchr/testify/mock。举一个简单的例子:
// readFileFirstLine.go
type LineReader interface {
ReadFirstLine() string
}
func ReadFirstLine() string {
open, err := os.Open("log")
defer open.Close()
if err != nil {
return ""
}
scanner := bufio.NewScanner(open)
for scanner.Scan() {
return scanner.Text()
}
return ""
}
func ProcessFirstLine(lr LineReader) string {
line := lr.ReadFirstLine()
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
// readFileFirstLine_test.go
type MockLineReader struct {
mock.Mock
}
func (mlr *MockLineReader) ReadFirstLine() string {
args := mlr.Called()
return args.String(0)
}
func TestProcessFirstLine(t *testing.T) {
expectedLine := "110"
mockReader := new(MockLineReader)
mockReader.On("ReadFirstLine").Return(expectedLine)
result := ProcessFirstLine(mockReader)
expectedDestLine := "000"
if result != expectedDestLine {
t.Errorf("ProcessFirstLine() returned %v, expected %v", result, expectedDestLine)
}
mockReader.AssertExpectations(t)
}
基准测试
benchmark testing基准测试是一种测试方法,用于评估软件系统的性能、稳定性和可靠性。它通过在实际环境中执行各种任务和负载,来测试系统的性能,并且可以用来检测和排除性能问题和瓶颈。
在Go语言中,基准测试通过testing包的Benchmark函数来实现的。基准测试函数需要满足一定的命名规则,并且通过testing.B结构体来记录测试的结果,包括测试的时间、内存使用情况等等。基准测试可以用于测试各种代码片段的性能,例如算法实现、函数调用等等。
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
下面是一个基准测试的例子:
// benchTest.go
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
func Select() int {
return ServerIndex[rand.Intn(10)]
// rand里面有全局锁,会影响并发性能
}
func FastSelect() int {
// fastrand牺牲了一定的随机数的一致性
return ServerIndex[fastrand.Intn(10)]
}
// benchTest_test.go
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
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()
}
})
}
func BenchmarkSelectParallel1(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
FastSelect()
}
})
}
go test
-
进入项目根目录,执行go test,它会自动运行所有的测试文件,并输出测试结果。
-
通过命令行参数来控制
go test命令的行为。例如:- -v 参数可以输出更详细的测试信息,包括每个测试用例的名称和运行时间等。
- -run 参数可以指定运行哪些用例,可以使用通配符进行匹配。
- -cover 参数可以生成测试覆盖率报告,用于评估测试用例的质量
-
在测试文件中,需要导入testing包,并编写测试函数。测试函数的名称必须以Test开头,并接受一个指向testing.T类型的参数,例如:
func TestMyFunction(t *testing.T){ // 测试代码 }
在测试函数中,可以使用t.Run()函数来执行子测试,也可以使用t.Helper()函数来标记测试函数是一个辅助函数,用于定位测试失败。
- 运行
go test命令时,会自动搜索项目中所有以_test.go结尾的文件,并执行其中的测试函数。