mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 22:26:38 +08:00
145 lines
3.9 KiB
Markdown
145 lines
3.9 KiB
Markdown
# Go 测试:集成和高级模式
|
||
|
||
TestMain、验收测试和真实传输层测试的详细参考。
|
||
来源:Google Go Style Guide(最佳实践)。
|
||
|
||
---
|
||
|
||
## TestMain
|
||
|
||
> **来源**:Google Go Style Guide(最佳实践)
|
||
|
||
当 **包中的所有测试** 都需要共同的设置且需要清理时(例如,共享数据库),使用 `func TestMain(m *testing.M)`。这 **不应该是你的首选** — 尽可能优先使用作用域测试辅助函数或 `t.Cleanup`。
|
||
|
||
```go
|
||
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. 导出一个接受被测实现的验证函数:
|
||
|
||
```go
|
||
// 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
|
||
}
|
||
```
|
||
|
||
3. 最终用户针对验证函数编写简单测试:
|
||
|
||
```go
|
||
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:
|
||
|
||
```go
|
||
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` 能正确工作:
|
||
|
||
```go
|
||
// 不好: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))
|
||
}
|
||
```
|