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

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)。