mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 14:26:36 +08:00
109 lines
3.2 KiB
Markdown
109 lines
3.2 KiB
Markdown
# 可扩展验证 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` 的完整一致性套件 |
|