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

168 lines
4.4 KiB
Markdown

# 测试组织参考
来源:Google Go Style Guide(最佳实践、决策)。
---
## 测试替身类型
| 替身 | 用途 | 有状态? | 验证调用? |
|------|------|----------|-----------|
| Stub | 返回预设数据 | 否 | 否 |
| Fake | 可工作但简化的实现 | 是 | 否 |
| Spy | 记录调用以供后续检查 | 是 | 是 |
**优先使用 fake 而非 mock。** Fake 更具可读性且不需要 mock 框架。仅在验证副作用(例如,分析事件)时使用 spy。
```go
// Fake:可工作的内存实现
type FakeUserStore struct {
users map[string]*User
}
func (f *FakeUserStore) GetUser(id string) (*User, error) {
u, ok := f.users[id]
if !ok {
return nil, ErrNotFound
}
return u, nil
}
// Spy:记录调用以供后续断言
type SpyEmailSender struct{ Sent []string }
func (s *SpyEmailSender) Send(to, body string) error {
s.Sent = append(s.Sent, to)
return nil
}
```
---
## 测试替身命名约定
> **建议**:为测试替身(stub、fake、spy)遵循一致的命名。
**包命名**:在生产代码旁边创建一个 `*test` 包(例如,为 `creditcard` 包创建 `creditcardtest`,为独立的 fake 服务创建 `fakeauthservice`)。
```go
// 好:在 creditcardtest 包中
// 单个替身 — 使用简单名称
type Stub struct{}
func (Stub) Charge(*creditcard.Card, money.Money) error { return nil }
// 多种行为 — 按行为命名
type AlwaysCharges struct{}
type AlwaysDeclines struct{}
// 多种类型 — 包含类型名
type StubService struct{}
type StubStoredValue struct{}
```
**局部变量**:为测试替身变量添加替身类型前缀,使调用处更清晰:
```go
// 好:替身类型立即可见
spyCC := &creditcardtest.Spy{}
stubDB := &dbtest.Stub{Balance: 100}
// 不好:模糊 — 这是真实的还是替身?
cc := &creditcardtest.Spy{}
db := &dbtest.Stub{Balance: 100}
```
---
## 独立测试辅助包
当多个包需要相同的替身、辅助函数有足够的逻辑需要自己的测试、或者你想为接口实现者提供验收测试套件时,创建独立的测试辅助包。
| 模式 | 使用场景 | 示例 |
|------|----------|------|
| `footest` | `foo` 包的通用测试辅助 | `creditcardtest`、`usertest` |
| `fakeX` | 独立的 fake 服务包 | `fakeauthservice`、`fakestorage` |
```go
package usertest
func NewFakeStore(t *testing.T, users ...*user.User) *FakeUserStore {
t.Helper()
store := &FakeUserStore{users: make(map[string]*user.User)}
for _, u := range users {
store.users[u.ID] = u
}
return store
}
```
导出接受 `*testing.T` 的构造函数,以便调用 `t.Helper()` 和 `t.Cleanup()`。
---
## 测试包
| 包声明 | 使用场景 |
|--------|----------|
| `package foo` | 同包测试,可以访问非导出标识符 |
| `package foo_test` | 黑盒测试,避免循环依赖 |
两者都放在同一目录下的 `foo_test.go` 文件中。
**使用 `package foo`(白盒)** 当你需要测试非导出函数或内部状态时。
**使用 `package foo_test`(黑盒)** 当仅测试公共 API、打破导入循环或验证外部可用性时。
```go
package parser_test // 黑盒:仅测试导出的 API
import "mymodule/parser"
func TestParse(t *testing.T) {
got, err := parser.Parse("input")
// ...
}
```
如果黑盒测试需要非导出符号,在 `package foo`(非 `foo_test`)中创建 `export_test.go` 来暴露它。谨慎使用。
---
## 设置作用域
> **建议**:保持设置仅限于需要它的测试。
每个测试中的显式设置更清晰,避免惩罚不相关的测试:
```go
// 好:在需要它的测试中显式设置
func TestParseData(t *testing.T) {
data := mustLoadDataset(t)
// ...
}
func TestUnrelated(t *testing.T) {
// 不需要为数据集加载付出代价
}
```
**避免使用全局 `init` 进行测试设置** — 它会对文件中的每个测试运行,即使是不相关的测试。
**子测试设置**:当一组子测试共享设置时,使用带 `t.Run` 的父测试:
```go
func TestDatabase(t *testing.T) {
db := setupTestDB(t)
t.Run("Insert", func(t *testing.T) {
// 使用 db
})
t.Run("Select", func(t *testing.T) {
// 使用 db
})
}
```
这将数据库的生命周期限定在需要它的子测试范围内。仅在万不得已时使用 `TestMain`(参见 [INTEGRATION.md](INTEGRATION.md))。