mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
155 lines
3.7 KiB
Markdown
155 lines
3.7 KiB
Markdown
# 表驱动测试、子测试和并行测试
|
|
|
|
在 Go 中组织表驱动测试和子测试的详细参考。
|
|
来源:Google Go Style Guide、Uber Go Style Guide。
|
|
|
|
---
|
|
|
|
## 基本结构
|
|
|
|
```go
|
|
func TestCompare(t *testing.T) {
|
|
tests := []struct {
|
|
a, b string
|
|
want int
|
|
}{
|
|
{"", "", 0},
|
|
{"a", "", 1},
|
|
{"", "a", -1},
|
|
{"abc", "abc", 0},
|
|
}
|
|
for _, tt := range tests {
|
|
got := Compare(tt.a, tt.b)
|
|
if got != tt.want {
|
|
t.Errorf("Compare(%q, %q) = %v, want %v", tt.a, tt.b, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 最佳实践
|
|
|
|
当测试用例跨越多行或有相同类型的相邻字段时,**使用字段名**:
|
|
|
|
```go
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want int
|
|
}{
|
|
{name: "empty", input: "", want: 0},
|
|
{name: "single", input: "a", want: 1},
|
|
}
|
|
```
|
|
|
|
**不要通过索引标识行** — 在失败信息中包含输入,而非使用 `Case #%d failed`。
|
|
|
|
---
|
|
|
|
## 避免表测试中的复杂性
|
|
|
|
当测试用例需要复杂设置、条件 mock 或多个分支时,优先使用单独的测试函数而非表测试。
|
|
|
|
```go
|
|
// 不好:太多条件字段使测试难以理解
|
|
tests := []struct {
|
|
give string
|
|
want string
|
|
wantErr error
|
|
shouldCallX bool
|
|
shouldCallY bool
|
|
giveXResponse string
|
|
giveXErr error
|
|
giveYResponse string
|
|
giveYErr error
|
|
}{...}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.give, func(t *testing.T) {
|
|
if tt.shouldCallX {
|
|
xMock.EXPECT().Call().Return(tt.giveXResponse, tt.giveXErr)
|
|
}
|
|
if tt.shouldCallY {
|
|
yMock.EXPECT().Call().Return(tt.giveYResponse, tt.giveYErr)
|
|
}
|
|
// ...
|
|
})
|
|
}
|
|
|
|
// 好:单独的专注测试更清晰
|
|
func TestShouldCallX(t *testing.T) {
|
|
xMock.EXPECT().Call().Return("XResponse", nil)
|
|
got, err := DoComplexThing("inputX", xMock, yMock)
|
|
// 断言...
|
|
}
|
|
|
|
func TestShouldCallYAndFail(t *testing.T) {
|
|
yMock.EXPECT().Call().Return("YResponse", nil)
|
|
_, err := DoComplexThing("inputY", xMock, yMock)
|
|
// 断言错误...
|
|
}
|
|
```
|
|
|
|
**表测试最适合以下场景:**
|
|
|
|
- 所有用例运行相同逻辑(无条件断言)
|
|
- 所有用例的设置相同
|
|
- 没有基于测试用例字段的条件 mock
|
|
- 所有表字段在所有测试中都被使用
|
|
|
|
如果测试体短且直接,单个 `shouldErr` 字段用于成功/失败检查是可以接受的。
|
|
|
|
---
|
|
|
|
## 子测试
|
|
|
|
使用 `t.Run` 实现更好的组织、过滤和并行执行。
|
|
|
|
### 子测试命名
|
|
|
|
- 使用清晰、简洁的名称:`t.Run("empty_input", ...)`、`t.Run("hu_to_en", ...)`
|
|
- 避免冗长的描述或斜杠(斜杠会破坏测试过滤)
|
|
- 子测试必须独立 — 不共享状态或执行顺序依赖
|
|
|
|
### 带子测试的表测试
|
|
|
|
```go
|
|
func TestTranslate(t *testing.T) {
|
|
tests := []struct {
|
|
name, srcLang, dstLang, input, want string
|
|
}{
|
|
{"hu_en_basic", "hu", "en", "köszönöm", "thank you"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := Translate(tt.srcLang, tt.dstLang, tt.input); got != tt.want {
|
|
t.Errorf("Translate(%q, %q, %q) = %q, want %q",
|
|
tt.srcLang, tt.dstLang, tt.input, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 并行测试
|
|
|
|
在表测试中使用 `t.Parallel()` 时,注意循环变量捕获:
|
|
|
|
```go
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
// Go 1.22+:tt 在每次迭代中被正确捕获
|
|
// Go 1.21-:在此处添加 "tt := tt" 来捕获变量
|
|
got := Process(tt.give)
|
|
if got != tt.want {
|
|
t.Errorf("Process(%q) = %q, want %q", tt.give, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
```
|