单元测试
Go语言内置了对单元测试的支持,只需要以_test.go为后缀的文件中的函数名以Test开头,这些函数就会被go test自动识别为测试函数。
单元测试一般遵循如下规则:
- 测试文件以_test.go为后缀,被测试的包和测试文件在同一目录下。
- 测试函数名以Test开头,接受t *testing.T类型的参数。
- 使用t.Error(), t.Fatal()或t.Fail()报告测试失败,而不是返回错误。
- 测试文件可以包括普通函数和变量,只有Test开头的函数会被识别为测试函数。
- go test会执行目录下所有以_test.go为后缀的文件中的测试函数。
例如有这样一个函数需要测试:
go
func Add(a, b int) int {
return a + b
}
对应的测试函数可以写在add_test.go中:
go
func TestAdd(t *testing.T) {
sum := Add(1, 2)
if sum != 3 {
t.Errorf("Expected 3, got %d", sum)
}
}
然后在终端执行go test将会运行该测试函数并报告测试结果。
Mock测试
Mock测试是一种单元测试技术,用于隔离测试代码与外部依赖之间的联系。在Mock测试中,外部依赖会被模拟对象替代,这些模拟对象的行为由我们控制,以便更精确地验证测试代码的逻辑。
Go语言标准库中没有内置对Mock测试的直接支持,但是Go社区中有几个Mock测试包可以使用:- github.com/stretchr/te…- github.com/golang/mock…- github.com/gojuno/mini…xn--github-2x8izbx350a.com/stretchr/te…:我们有一个ExternalService接口和它的实现ExternalServiceImpl:
go
// ExternalService.go
type ExternalService interface {
DoSomething(param string) error
}
// ExternalServiceImpl.go
type ExternalServiceImpl struct{}
func (s *ExternalServiceImpl) DoSomething(param string) error {
// ...
}
我们的代码调用ExternalService的DoSomething方法,这是我们要测试的逻辑。我们可以创建ExternalService的Mock对象来验证DoSomething被正确调用:
go
// ExternalService_test.go
// 导入testify/mock并创建Mock对象
import "github.com/stretchr/testify/mock"
// 声明mock对象类型
type MockExternalService struct {
mock.Mock
}
// 为DoSomething方法提供mock实现
func (m *MockExternalService) DoSomething(param string) error {
// 记录方法调用
m.Called(param)
return nil
}
// 创建测试用例使用mock对象
func TestUsingExternalService(t *testing.T) {
// 初始化mock对象
mockService := new(MockExternalService)
// 使用mockService调用DoSomething
DoSomethingWithExternalService(mockService)
// 验证DoSomething是否被调用
mockService.AssertCalled(t, "DoSomething", "param1")
}
这个测试用例中的MockExternalService对象代替了真实的ExternalServiceImpl对象,并且我们可以通过它验证DoSomething方法是否被正确调用,以及被调用时的具体参数。这样的Mock测试允许我们隔离外部依赖,更加准确地验证被测试代码本身的逻辑。它在测试驱动开发中扮演重要角色,有助于构建健壮的软件系统。
基准测试
基准测试用于测量函数或行为的性能,Go语言通过testing/benchmark包提供了对基准测试的支持。基准测试遵循以下规则:
- 基准测试函数名以Benchmark开头,接受*testing.B作为参数。
- 使用b.N表示基准测试应重复的次数,该值由测试框架决定,以达到稳定的测量。
- 使用b.ResetTimer()和b.StartTimer()控制定时器,不应将它们的调用包含在N内。
- 使用b.Log作为日志记录器记录额外的信息。
- 应避免在基准测试中执行并发性代码或I/O,以减少测量干扰。
例如,我们可以这样写基准测试:
go
func BenchmarkAdd(b *testing.B) {
sum := 0
b.ResetTimer() // 重置定时器
//b.N由框架决定,直到达到稳定
for i := 0; i < b.N; i++ {
sum += 1 + 2
}
b.StopTimer() // 停止定时器
}
然后在终端执行go test -bench=.将会运行基准测试并输出结果。基准测试结果会报告每个测试 iteration 花费的纳秒数,还会汇总所有iteration的总耗时。
除了基本的函数基准测试,testing/benchmark还支持更丰富的基准测试,比如:
- 并行基准:使用b.RunParallel()
- 内存基准:使用testing.AllocsPerRun
- 子基准:使用b.Run()启动