Go语言内置的testing测试框架提供了子测试功能,通过t.Run方法可以在一个测试函数中定义多个独立的子测试用例,让测试逻辑的组织更加清晰,也方便单独执行某个特定的测试用例。

子测试的基本概念
子测试是指在父测试函数内部,通过t.Run方法创建的独立测试单元。每个子测试都有自己独立的名称和上下文,执行时互不影响,父测试函数的执行结果也不会因为某个子测试失败而直接终止,除非显式处理错误。
子测试的主要优势包括:
- 可以将相关的测试用例归类到同一个父测试函数下,提升测试代码的可读性
- 支持单独执行某个子测试,不需要运行整个测试函数
- 可以灵活控制子测试的执行顺序和并发情况
t.Run方法的基本用法
t.Run是testing.T结构体的方法,其函数签名如下:
func (t *T) Run(name string, f func(t *T)) bool
参数说明:
- name:子测试的名称,同一个父测试下的子测试名称需要唯一
- f:子测试的执行函数,接收一个*testing.T类型的参数,用于子测试内部的断言和错误上报
- 返回值:bool类型,表示子测试是否执行成功
基础示例
下面是一个简单的子测试使用示例,父测试函数TestAdd下定义了两个子测试,分别测试正数相加和负数相加的场景:
package main
import (
"testing"
)
// 待测试的加法函数
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
// 第一个子测试:测试正数相加
t.Run("positive_numbers", func(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Add(1,2) = %d, want 3", result)
}
})
// 第二个子测试:测试负数相加
t.Run("negative_numbers", func(t *testing.T) {
result := Add(-1, -2)
if result != -3 {
t.Errorf("Add(-1,-2) = %d, want -3", result)
}
})
}
执行上述测试时,会输出两个子测试的执行结果,格式为父测试名/子测试名,例如TestAdd/positive_numbers和TestAdd/negative_numbers。
子测试的常用场景
单独执行某个子测试
如果只需要执行某个特定的子测试,可以在go test命令中通过-run参数指定子测试的名称,格式为父测试名/子测试名:
go test -run TestAdd/positive_numbers
上述命令只会执行TestAdd下的positive_numbers子测试,其他子测试不会运行。
子测试传递参数
可以通过闭包的方式给子测试传递参数,避免重复编写测试逻辑。下面的示例通过表格驱动测试结合子测试,批量测试多组输入:
func TestAddWithTable(t *testing.T) {
// 定义测试用例表格
testCases := []struct {
name string
a int
b int
want int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"zero", 0, 5, 5},
}
// 遍历表格,为每个用例创建子测试
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := Add(tc.a, tc.b)
if result != tc.want {
t.Errorf("Add(%d,%d) = %d, want %d", tc.a, tc.b, result, tc.want)
}
})
}
}
子测试的并发执行
如果子测试之间没有依赖关系,可以在子测试函数内部调用t.Parallel()方法,让子测试并发执行,提升测试效率:
func TestParallelSubtests(t *testing.T) {
t.Run("sub1", func(t *testing.T) {
t.Parallel() // 标记为可并发执行
// 子测试1的逻辑
})
t.Run("sub2", func(t *testing.T) {
t.Parallel() // 标记为可并发执行
// 子测试2的逻辑
})
}
注意:调用t.Parallel()的子测试会在父测试函数执行到t.Parallel()时暂停,等待所有非并行的子测试执行完成后,再并发执行所有标记了Parallel的子测试。
子测试的注意事项
- 子测试的名称不能包含斜杠/,否则会被识别为多级子测试,例如t.Run("a/b", ...)会创建两级子测试a和b
- 子测试内部的错误通过t.Error或t.Fatal上报,不会影响其他子测试的执行,除非在父测试中显式检查子测试的返回值
- 如果需要在子测试执行前做初始化操作,或者在执行后做清理操作,可以在子测试函数内部使用defer语句
通过合理使用t.Run创建子测试,可以让Go语言的测试用例结构更清晰,维护更方便,也能更灵活地控制测试的执行过程,是编写高质量Go测试代码的重要技巧。