软件测试

101 阅读3分钟

单元测试

Go语言内置了对单元测试的支持,只需要以_test.go为后缀的文件中的函数名以Test开头,这些函数就会被go test自动识别为测试函数。

单元测试一般遵循如下规则:

  1. 测试文件以_test.go为后缀,被测试的包和测试文件在同一目录下。
  2. 测试函数名以Test开头,接受t *testing.T类型的参数。
  3. 使用t.Error(), t.Fatal()或t.Fail()报告测试失败,而不是返回错误。
  4. 测试文件可以包括普通函数和变量,只有Test开头的函数会被识别为测试函数。
  5. 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包提供了对基准测试的支持。基准测试遵循以下规则:

  1. 基准测试函数名以Benchmark开头,接受*testing.B作为参数。
  2. 使用b.N表示基准测试应重复的次数,该值由测试框架决定,以达到稳定的测量。
  3. 使用b.ResetTimer()和b.StartTimer()控制定时器,不应将它们的调用包含在N内。
  4. 使用b.Log作为日志记录器记录额外的信息。
  5. 应避免在基准测试中执行并发性代码或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()启动