go-testing-code-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Testing Code Review

Go测试代码评审

Quick Reference

快速参考

Issue TypeReference
Test structure, namingreferences/structure.md
Mocking, interfacesreferences/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

评审问题

  1. Are tests table-driven with named cases?
  2. Do error messages include input, got, and want?
  3. Are parallel tests isolated (no shared state)?
  4. Is cleanup done via t.Cleanup?
  5. Do tests verify behavior, not implementation?
  1. 测试是否采用表格驱动方式且带有命名用例?
  2. 错误信息是否包含输入值、实际值和期望值?
  3. 并行测试是否相互隔离(无共享状态)?
  4. 是否通过t.Cleanup执行清理操作?
  5. 测试是否验证行为而非实现细节?