t.Run允许在单个测试函数内组织多个独立子测试,提升可读性与维护性。通过t.Run(name, func(t *testing.T))定义子测试,每个子测试拥有独立名称、执行上下文和失败报告,支持并行执行(t.Parallel)与精细化资源管理。结合表格驱动测试,可为每个测试用例动态生成子测试,输出清晰的层级化结果。父测试可进行共享资源设置,子测试通过t.Cleanup实现独立清理,确保资源安全释放,提高测试隔离性与可靠性。

在Golang的测试框架中,
t.Run
使用
t.Run
t.Run(name, func(t *testing.T){ ... })name
func(t *testing.T)
*testing.T
t.Error
t.Fatal
t.Skip
设想一下,你正在测试一个复杂的函数,它在不同输入下有多种行为模式。如果不使用
t.Run
TestXxx
t.Run
TestParent
package mypackage
import (
"fmt"
"testing"
)
// Add 函数,用于演示测试
func Add(a, b int) int {
return a + b
}
func TestAddFunction(t *testing.T) {
// 这是一个父测试,用于组织所有关于 Add 函数的测试
t.Log("开始测试 Add 函数的不同场景...")
// 场景一:正常正数相加
t.Run("PositiveNumbers", func(t *testing.T) {
t.Parallel() // 允许此子测试与其他并行子测试并发运行
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) 预期 %d, 得到 %d", expected, result)
}
})
// 场景二:包含负数相加
t.Run("NegativeNumbers", func(t *testing.T) {
t.Parallel()
result := Add(-2, 3)
expected := 1
if result != expected {
t.Errorf("Add(-2, 3) 预期 %d, 得到 %d", expected, result)
}
})
// 场景三:零值相加
t.Run("ZeroValue", func(t *testing.T) {
result := Add(0, 0)
expected := 0
if result != expected {
t.Errorf("Add(0, 0) 预期 %d, 得到 %d", expected, result)
}
})
// 场景四:大数相加,模拟潜在溢出(如果 Add 有溢出逻辑的话)
t.Run("LargeNumbers", func(t *testing.T) {
result := Add(1000000, 2000000)
expected := 3000000
if result != expected {
t.Errorf("Add(1000000, 2000000) 预期 %d, 得到 %d", expected, result)
}
})
// 可以在父测试中进行一些通用的断言或清理,但通常子测试更聚焦
t.Log("Add 函数所有场景测试完成。")
}运行
go test -v
立即学习“go语言免费学习笔记(深入)”;
=== RUN TestAddFunction
=== RUN TestAddFunction/PositiveNumbers
=== RUN TestAddFunction/NegativeNumbers
=== RUN TestAddFunction/ZeroValue
=== RUN TestAddFunction/LargeNumbers
--- PASS: TestAddFunction (0.00s)
--- PASS: TestAddFunction/PositiveNumbers (0.00s)
--- PASS: TestAddFunction/NegativeNumbers (0.00s)
--- PASS: TestAddFunction/ZeroValue (0.00s)
--- PASS: TestAddFunction/LargeNumbers (0.00s)
PASS如果
TestAddFunction/PositiveNumbers
t.Run
t.Run
TestXxx
TestXxx
TestXxx
t.Parallel()
t.Run
TestXxx
最显著的区别在于测试的层次结构和报告。当一个
TestXxx
t.Run
TestXxx
TestXxx
t.Run
TestXxx/SpecificScenario
此外,
t.Run
TestXxx
t.Run
表格驱动测试(Table-Driven Tests)是 Go 社区中非常推崇的一种测试模式,它通过定义一个包含输入和预期输出的结构体切片(或数组),然后遍历这个切片来执行一系列测试用例。结合
t.Run
我们来扩展一下之前的
Add
Add
int
package mypackage
import (
"fmt"
"testing"
)
// Subtract 函数,用于演示表格驱动测试
func Subtract(a, b int) int {
return a - b
}
func TestSubtractFunction(t *testing.T) {
// 定义一个测试用例的结构体
type testCase struct {
name string // 测试用例的名称
a, b int // 输入参数
expected int // 预期结果
hasError bool // 模拟是否预期有错误发生
}
// 定义所有测试用例的切片
tests := []testCase{
{"PositiveResult", 5, 3, 2, false},
{"NegativeResult", 3, 5, -2, false},
{"ZeroResult", 5, 5, 0, false},
{"SubtractFromZero", 0, 5, -5, false},
{"SubtractZero", 5, 0, 5, false},
// 假设这里有一个特殊情况,比如输入是负数且结果会触发某个内部错误
// 这里我们简化为hasError标记
{"SpecialCaseError", -1, 1, -2, false}, // 实际上可能需要一个 error 字段来断言
}
// 遍历所有测试用例,为每个用例创建一个子测试
for _, tc := range tests {
// 注意这里捕获 tc 变量,防止闭包问题,因为 t.Run 会在新 goroutine 中执行
// 更好的做法是将其作为参数传递,或者在循环内部重新声明一个局部变量
// 示例中我们使用 `tc := tc` 这种 Go 惯用法
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel() // 允许子测试并行运行,提高效率
actual := Subtract(tc.a, tc.b)
if tc.hasError {
// 模拟错误断言,这里简化为直接失败
t.Error("预期有错误发生,但没有检查到")
return
}
if actual != tc.expected {
t.Errorf("Subtract(%d, %d) 预期 %d, 实际 %d", tc.a, tc.b, tc.expected, actual)
}
})
}
}在这个例子中,
TestSubtractFunction
testCase
for
tests
testCase
t.Run
tc.name
TestSubtractFunction/PositiveResult
t.Parallel()
t.Run
t.Parallel()
t.Parallel()
t.Run
t.Run
首先是并发测试。前面提到了在
t.Run
t.Parallel()
t.Run
tc := tc
t.Parallel()
其次是资源管理。在许多实际应用中,测试可能需要访问数据库、文件系统、网络服务或其他外部资源。这些资源的设置(Setup)和清理(Teardown)往往是昂贵且复杂的。
t.Run
t.Cleanup()
t.Cleanup()
t.Cleanup()
package mypackage
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
// SimulateDBConnection 模拟数据库连接
type SimulateDBConnection struct {
id int
}
func NewSimulateDBConnection(id int) *SimulateDBConnection {
fmt.Printf("DB Connection %d 建立\n", id)
return &SimulateDBConnection{id: id}
}
func (db *SimulateDBConnection) Close() {
fmt.Printf("DB Connection %d 关闭\n", db.id)
}
func TestResourceManagement(t *testing.T) {
// 父测试级别的资源设置:创建一个临时目录,所有子测试共享
tempDir, err := ioutil.TempDir("", "test_data_")
if err != nil {
t.Fatalf("无法创建临时目录: %v", err)
}
// 使用 t.Cleanup 确保临时目录在父测试结束后被删除
t.Cleanup(func() {
fmt.Printf("清理临时目录: %s\n", tempDir)
os.RemoveAll(tempDir)
})
fmt.Printf("临时目录创建: %s\n", tempDir)
// 子测试一:使用共享资源
t.Run("FileOperation", func(t *testing.T) {
t.Parallel()
filePath := filepath.Join(tempDir, "test.txt")
err := ioutil.WriteFile(filePath, []byte("hello world"), 0644)
if err != nil {
t.Errorf("写入文件失败: %v", err)
}
content, err := ioutil.ReadFile(filePath)
if err != nil {
t.Errorf("读取文件失败: %v", err)
}
if string(content) != "hello world" {
t.Errorf("文件内容不匹配: %s", string(content))
}
})
// 子测试二:独立的数据库连接
t.Run("DBTransaction", func(t *testing.T) {
t.Parallel()
dbConn := NewSimulateDBConnection(1)
// 子测试级别的清理,确保这个连接在子测试结束后关闭
t.Cleanup(func() {
dbConn.Close()
})
// 模拟一些数据库操作
fmt.Printf("DB Connection %d 进行操作...\n", dbConn.id)
// ... 断言数据库操作结果
})
// 子测试三:另一个独立的数据库连接
t.Run("AnotherDBTransaction", func(t *testing.T) {
t.Parallel()
dbConn := NewSimulateDBConnection(2)
t.Cleanup(func() {
dbConn.Close()
})
fmt.Printf("DB Connection %d 进行操作...\n", dbConn.id)
// ...
})
}在这个示例中,
TestResourceManagement
t.Cleanup
FileOperation
DBTransaction
AnotherDBTransaction
t.Cleanup
defer
以上就是Golang测试中使用t.Run管理子测试的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号