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

109 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 可扩展验证 API
设计可重用测试验证函数的详细参考,调用者可以将其用于验收测试。来源:Google Go Style Guide(最佳实践)。
---
## `*test` 包导出模式
当你拥有一个由他人实现的接口时,在配套的 `*test` 包中导出一个验证函数。这使实现者无需复制你的测试逻辑即可验证正确性。
```go
// Package storagetest 为 storage.Backend 提供验收测试。
package storagetest
// Verify 对任何 storage.Backend 运行验证套件。
// 返回描述第一个违规行为的错误,成功时返回 nil。
func Verify(b storage.Backend) error {
if err := verifyRoundTrip(b); err != nil {
return fmt.Errorf("round-trip: %w", err)
}
if err := verifyNotFound(b); err != nil {
return fmt.Errorf("not-found: %w", err)
}
return nil
}
```
调用者编写一个薄测试来接入他们的实现:
```go
func TestMyBackend(t *testing.T) {
b := mybackend.New(t)
if err := storagetest.Verify(b); err != nil {
t.Errorf("MyBackend failed acceptance: %v", err)
}
}
```
---
## 设计可扩展的验证函数
**返回错误,而非 `*testing.T` 失败。** 这使验证函数可作为普通 Go 函数使用 — 调用者决定违规是 `t.Error` 还是 `t.Fatal`。
```go
// 好:返回错误 — 调用者控制测试流程
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
}
// 不好:调用 t.Fatal — 调用者失去控制
func ExercisePlayer(t *testing.T, b *chess.Board, p chess.Player) {
t.Helper()
move := p.Move()
if putsOwnKingIntoCheck(b, move) {
t.Fatalf("illegal move: %v puts own king in check", move)
}
}
```
**在需要丰富诊断信息时使用自定义错误类型**:
```go
type IllegalMoveError struct {
Move chess.Move
Reason string
}
func (e *IllegalMoveError) Error() string {
return fmt.Sprintf("illegal move %v: %s", e.Move, e.Reason)
}
```
---
## 何时使用验证 API vs 简单辅助函数
| 场景 | 使用方式 |
|------|----------|
| 你拥有的接口,由他人实现 | `*test` 包中的验证 API |
| 同一包中跨测试共享设置 | 使用 `t.Helper()` 的测试辅助函数 |
| 在 2-3 个测试中重用的复杂断言 | 返回 `error` 或 `bool` 的辅助函数 |
| 一次性的设置或比较 | 内联测试代码 |
**验证 API** 在以下场景值得额外的包:
- 多个外部包将实现你的接口
- 契约有容易被忽略的非显而易见的不变量
- 你想为"正确行为"提供单一事实来源
**简单辅助函数** 在以下场景更好:
- 辅助函数是直接的设置或比较函数
- 重用是偶然的,不是已发布契约的一部分
---
## 命名约定
使用表示范围的动词命名函数:`Verify`、`Exercise`、`RunConformance`。接受被测接口作为参数 — 绝不在验证包内部构造实现。
| 包 | 函数 | 用途 |
|----|------|------|
| `storagetest` | `Verify` | 验证 `storage.Backend` |
| `chesstest` | `ExercisePlayer` | 验证 `chess.Player` |
| `cachetest` | `RunConformance` | `cache.Cache` 的完整一致性套件 |