Files
OpenFlare/.agents/skills/go-testing/references/INTEGRATION.md
T
2026-08-04 11:40:32 +08:00

3.9 KiB
Raw Blame History

Go 测试:集成和高级模式

TestMain、验收测试和真实传输层测试的详细参考。 来源:Google Go Style Guide(最佳实践)。


TestMain

来源:Google Go Style Guide(最佳实践)

当 包中的所有测试 都需要共同的设置且需要清理时(例如,共享数据库),使用 func TestMain(m *testing.M)。这 不应该是你的首选 — 尽可能优先使用作用域测试辅助函数或 t.Cleanup。

var db *sql.DB

func TestInsert(t *testing.T) { /* 使用 db */ }
func TestSelect(t *testing.T) { /* 使用 db */ }

func runMain(ctx context.Context, m *testing.M) (code int, err error) {
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    d, err := setupDatabase(ctx)
    if err != nil {
        return 0, err
    }
    defer d.Close()
    db = d

    return m.Run(), nil
}

func TestMain(m *testing.M) {
    code, err := runMain(context.Background(), m)
    if err != nil {
        log.Fatal(err)
    }
    // defer 语句在 os.Exit 之后不会执行
    os.Exit(code)
}

关键点:

  • 将设置提取到辅助函数(runMain)中,使 defer 能正确工作
  • 通过 log.Fatal 将失败信息写入 stderr
  • 确保各个测试用例保持独立 — 重置它们修改的任何全局状态

验收测试

来源:Google Go Style Guide(最佳实践)

验收测试验证实现是否遵循契约,将其视为黑盒。当用户实现你的接口并且你想提供可重用的验证套件时,这种模式很有用。

结构

  1. 创建测试辅助包(例如,为 chess 包创建 chesstest)
  2. 导出一个接受被测实现的验证函数:
// Package chesstest 为 chess.Player 实现提供验收测试。
package chesstest

// ExercisePlayer 在单回合中测试 Player 实现。
// 如果玩家走了正确的一步,返回 nil,否则返回描述违规行为的错误。
func ExercisePlayer(b *chess.Board, p chess.Player) error {
    move := p.Move()
    if putsOwnKingIntoCheck(b, move) {
        return &IllegalMoveError{Move: move, Reason: "puts own king in check"}
    }
    return nil
}
  1. 最终用户针对验证函数编写简单测试:
func TestAcceptance(t *testing.T) {
    player := deepblue.New()
    if err := chesstest.ExerciseGame(t, chesstest.SimpleGame, player); err != nil {
        t.Errorf("Deep Blue player failed acceptance test: %v", err)
    }
}

仅在设置失败时使用 t.Fatal — 验证错误应该返回,而非 fatal。


使用真实传输层

来源:Google Go Style Guide(最佳实践)

在测试基于 HTTP 或 RPC 的组件集成时,优先使用真实传输层往返而非手动实现的客户端 mock:

func TestAPIIntegration(t *testing.T) {
    // 使用假后端启动测试服务器
    srv := httptest.NewServer(newFakeHandler())
    t.Cleanup(srv.Close)

    // 对测试服务器使用真实 HTTP 客户端
    client := api.NewClient(srv.URL)
    result, err := client.GetUser(context.Background(), "user-123")
    if err != nil {
        t.Fatalf("GetUser() error: %v", err)
    }
    if result.Name != "Test User" {
        t.Errorf("GetUser().Name = %q, want %q", result.Name, "Test User")
    }
}

使用生产客户端配合测试服务器,可以确保测试尽可能多地覆盖真实代码,避免模拟客户端行为的复杂性。


常见错误

在 TestMain 中直接调用 os.Exit

os.Exit 立即终止进程 — defer 的清理函数永远不会执行。将设置/清理提取到辅助函数中,使 defer 能正确工作:

// 不好:defer 不会执行
func TestMain(m *testing.M) {
    setup()
    defer cleanup()
    os.Exit(m.Run()) // cleanup() 永远不会执行
}

// 好:提取到辅助函数中,使 defer 在 os.Exit 之前执行
func runTests(m *testing.M) int {
    setup()
    defer cleanup()
    return m.Run()
}

func TestMain(m *testing.M) {
    os.Exit(runTests(m))
}