go-testing-code-review
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo Testing Code Review
Go测试代码评审
Quick Reference
快速参考
| Issue Type | Reference |
|---|---|
| Test structure, naming | references/structure.md |
| Mocking, interfaces | references/mocking.md |
| 问题类型 | 参考文档 |
|---|---|
| 测试结构、命名 | references/structure.md |
| 模拟、接口 | references/mocking.md |
Review Checklist
评审检查清单
- Tests are table-driven with clear case names
- Subtests use t.Run for parallel execution
- Test names describe behavior, not implementation
- Errors include got/want with descriptive message
- Cleanup registered with t.Cleanup
- Parallel tests don't share mutable state
- Mocks use interfaces defined in test file
- Coverage includes edge cases and error paths
- 测试采用表格驱动方式,且测试用例名称清晰
- 子测试使用t.Run实现并行执行
- 测试名称描述行为而非实现细节
- 错误信息包含实际值/期望值及描述性内容
- 通过t.Cleanup注册清理操作
- 并行测试不共享可变状态
- 模拟对象使用测试文件中定义的接口
- 代码覆盖包含边缘情况和错误路径
Critical Patterns
关键模式
Table-Driven Tests
表格驱动测试
go
// BAD - repetitive
func TestAdd(t *testing.T) {
if Add(1, 2) != 3 {
t.Error("wrong")
}
if Add(0, 0) != 0 {
t.Error("wrong")
}
}
// GOOD
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 1, 2, 3},
{"zeros", 0, 0, 0},
{"negative", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}go
// 不良示例 - 重复代码
func TestAdd(t *testing.T) {
if Add(1, 2) != 3 {
t.Error("wrong")
}
if Add(0, 0) != 0 {
t.Error("wrong")
}
}
// 良好示例
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive numbers", 1, 2, 3},
{"zeros", 0, 0, 0},
{"negative", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}Error Messages
错误信息
go
// BAD
if got != want {
t.Error("wrong result")
}
// GOOD
if got != want {
t.Errorf("GetUser(%d) = %v, want %v", id, got, want)
}
// For complex types
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("GetUser() mismatch (-want +got):\n%s", diff)
}go
// 不良示例
if got != want {
t.Error("wrong result")
}
// 良好示例
if got != want {
t.Errorf("GetUser(%d) = %v, want %v", id, got, want)
}
// 复杂类型对比
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("GetUser() mismatch (-want +got):\n%s", diff)
}Parallel Tests
并行测试
go
func TestFoo(t *testing.T) {
tests := []struct{...}
for _, tt := range tests {
tt := tt // capture (not needed Go 1.22+)
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// test code
})
}
}go
func TestFoo(t *testing.T) {
tests := []struct{...}
for _, tt := range tests {
tt := tt // 捕获变量(Go 1.22+ 无需此操作)
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// 测试代码
})
}
}Cleanup
清理操作
go
// BAD - manual cleanup, skipped on failure
func TestWithTempFile(t *testing.T) {
f, _ := os.CreateTemp("", "test")
defer os.Remove(f.Name()) // skipped if test panics
}
// GOOD
func TestWithTempFile(t *testing.T) {
f, _ := os.CreateTemp("", "test")
t.Cleanup(func() {
os.Remove(f.Name())
})
}go
// 不良示例 - 手动清理,测试失败时可能被跳过
func TestWithTempFile(t *testing.T) {
f, _ := os.CreateTemp("", "test")
defer os.Remove(f.Name()) // 测试 panic 时会被跳过
}
// 良好示例
func TestWithTempFile(t *testing.T) {
f, _ := os.CreateTemp("", "test")
t.Cleanup(func() {
os.Remove(f.Name())
})
}Anti-Patterns
反模式
1. Testing Internal Implementation
1. 测试内部实现细节
go
// BAD - tests private state
func TestUser(t *testing.T) {
u := NewUser("alice")
if u.id != 1 { // testing internal field
t.Error("wrong id")
}
}
// GOOD - tests behavior
func TestUser(t *testing.T) {
u := NewUser("alice")
if u.ID() != 1 {
t.Error("wrong ID")
}
}go
// 不良示例 - 测试私有状态
func TestUser(t *testing.T) {
u := NewUser("alice")
if u.id != 1 { // 测试内部字段
t.Error("wrong id")
}
}
// 良好示例 - 测试行为
func TestUser(t *testing.T) {
u := NewUser("alice")
if u.ID() != 1 {
t.Error("wrong ID")
}
}2. Shared Mutable State
2. 共享可变状态
go
// BAD - tests interfere with each other
var testDB = setupDB()
func TestA(t *testing.T) {
t.Parallel()
testDB.Insert(...) // race!
}
// GOOD - isolated per test
func TestA(t *testing.T) {
db := setupTestDB(t)
t.Cleanup(func() { db.Close() })
db.Insert(...)
}go
// 不良示例 - 测试间相互干扰
var testDB = setupDB()
func TestA(t *testing.T) {
t.Parallel()
testDB.Insert(...) // 竞态条件!
}
// 良好示例 - 每个测试独立隔离
func TestA(t *testing.T) {
db := setupTestDB(t)
t.Cleanup(func() { db.Close() })
db.Insert(...)
}3. Assertions Without Context
3. 无上下文的断言
go
// BAD
assert.Equal(t, want, got) // "expected X got Y" - which test?
// GOOD
assert.Equal(t, want, got, "user name after update")go
// 不良示例
assert.Equal(t, want, got) // "期望X得到Y" - 但属于哪个测试?
// 良好示例
assert.Equal(t, want, got, "更新后的用户名")When to Load References
何时加载参考文档
- Reviewing test file structure → structure.md
- Reviewing mock implementations → mocking.md
- 评审测试文件结构 → structure.md
- 评审模拟实现 → mocking.md
Review Questions
评审问题
- Are tests table-driven with named cases?
- Do error messages include input, got, and want?
- Are parallel tests isolated (no shared state)?
- Is cleanup done via t.Cleanup?
- Do tests verify behavior, not implementation?
- 测试是否采用表格驱动方式且带有命名用例?
- 错误信息是否包含输入值、实际值和期望值?
- 并行测试是否相互隔离(无共享状态)?
- 是否通过t.Cleanup执行清理操作?
- 测试是否验证行为而非实现细节?