mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-30 14:26:36 +08:00
4.4 KiB
4.4 KiB
测试组织参考
来源:Google Go Style Guide(最佳实践、决策)。
测试替身类型
| 替身 | 用途 | 有状态? | 验证调用? |
|---|---|---|---|
| Stub | 返回预设数据 | 否 | 否 |
| Fake | 可工作但简化的实现 | 是 | 否 |
| Spy | 记录调用以供后续检查 | 是 | 是 |
优先使用 fake 而非 mock。 Fake 更具可读性且不需要 mock 框架。仅在验证副作用(例如,分析事件)时使用 spy。
// 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)。
// 好:在 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{}
局部变量:为测试替身变量添加替身类型前缀,使调用处更清晰:
// 好:替身类型立即可见
spyCC := &creditcardtest.Spy{}
stubDB := &dbtest.Stub{Balance: 100}
// 不好:模糊 — 这是真实的还是替身?
cc := &creditcardtest.Spy{}
db := &dbtest.Stub{Balance: 100}
独立测试辅助包
当多个包需要相同的替身、辅助函数有足够的逻辑需要自己的测试、或者你想为接口实现者提供验收测试套件时,创建独立的测试辅助包。
| 模式 | 使用场景 | 示例 |
|---|---|---|
footest |
foo 包的通用测试辅助 |
creditcardtest、usertest |
fakeX |
独立的 fake 服务包 | fakeauthservice、fakestorage |
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、打破导入循环或验证外部可用性时。
package parser_test // 黑盒:仅测试导出的 API
import "mymodule/parser"
func TestParse(t *testing.T) {
got, err := parser.Parse("input")
// ...
}
如果黑盒测试需要非导出符号,在 package foo(非 foo_test)中创建 export_test.go 来暴露它。谨慎使用。
设置作用域
建议:保持设置仅限于需要它的测试。
每个测试中的显式设置更清晰,避免惩罚不相关的测试:
// 好:在需要它的测试中显式设置
func TestParseData(t *testing.T) {
data := mustLoadDataset(t)
// ...
}
func TestUnrelated(t *testing.T) {
// 不需要为数据集加载付出代价
}
避免使用全局 init 进行测试设置 — 它会对文件中的每个测试运行,即使是不相关的测试。
子测试设置:当一组子测试共享设置时,使用带 t.Run 的父测试:
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)。