mcpField
G
golang-stretchr-testify

golang-stretchr-testify

github.com/samber测试与质量3.7k39.8k 次安装5 天前更新
代码仓库

关于 golang-stretchr-testify

面向 Go 工程师的 stretchr/testify 测试技能,适用于编写或审查测试。内容侧重断言失败行为、Mock 验证、测试套件及常见误用。

主要功能

  1. 01assert 与 require 的使用指南
  2. 02基础及高级断言
  3. 03Mock 设置与预期验证
  4. 04测试套件生命周期与执行
  5. 05常见错误及 Linter 指引

适用场景

  1. 01为 Go 项目编写行为测试
  2. 02检查测试中的断言误用
  3. 03Mock 接口并验证预期调用

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-stretchr-testify

安装后在 Claude Code 中输入/golang-stretchr-testify确认已加载。

附带文件evals/evals.jsonreferences/mock.md

详细介绍

角色: 你是一名 Go 工程师,将测试视为可执行规范。你编写测试来约束行为,并让失败原因一目了然,而不是为了达到覆盖率目标。

模式:

  • 编写模式 — 向代码库添加新测试或 Mock。
  • 审查模式 — 检查现有测试代码是否误用 testify。

stretchr/testify

testify 为 Go 的 testing 包提供可读的断言、Mock 和测试套件。它不会取代 testing —— 始终使用 *testing.T 作为入口。

本技能并非详尽无遗 —— 如需更多信息,请参阅库文档和代码示例:

  • 有关 Go 包文档、符号、版本、导入方和已知漏洞,→ 请参阅 samber/cc-skills-golang@golang-pkg-go-dev 技能(godig),查询 Go 包信息时优先于 Context7。
  • 如需在你自己的代码中浏览此库的用法(定义、调用位置、诊断信息),→ 请参阅 samber/cc-skills-golang@golang-gopls 技能(gopls)。
  • 对于未被 pkg.go.dev 收录的文档,Context7 仍可作为备用选择。

assert 与 require

两者提供相同的断言。区别在于失败时的行为:

  • assert:记录失败并继续执行 —— 一次查看所有失败
  • require:调用 t.FailNow() —— 用于继续执行会导致 Panic 或产生误导的前置条件

使用 assert.New(t) / require.New(t) 提高可读性。将它们命名为 is 和 must:

func TestParseConfig(t *testing.T) {
    is := assert.New(t)
    must := require.New(t)

    cfg, err := ParseConfig("testdata/valid.yaml")
    must.NoError(err)    // stop if parsing fails — cfg would be nil
    must.NotNil(cfg)

    is.Equal("production", cfg.Environment)
    is.Equal(8080, cfg.Port)
    is.True(cfg.TLS.Enabled)
}

规则:前置条件(设置、错误检查)使用 require,验证使用 assert。切勿随意混用。

核心断言

is := assert.New(t)

// Equality
is.Equal(expected, actual)              // DeepEqual + exact type
is.NotEqual(unexpected, actual)
is.EqualValues(expected, actual)        // converts to common type first
is.EqualExportedValues(expected, actual)

// Nil / Bool / Emptiness
is.Nil(obj)                  is.NotNil(obj)
is.True(cond)                is.False(cond)
is.Empty(collection)         is.NotEmpty(collection)
is.Len(collection, n)

// Contains (strings, slices, map keys)
is.Contains("hello world", "world")
is.Contains([]int{1, 2, 3}, 2)
is.Contains(map[string]int{"a": 1}, "a")

// Comparison
is.Greater(actual, threshold)     is.Less(actual, ceiling)
is.Positive(val)                  is.Negative(val)
is.Zero(val)

// Errors
is.Error(err)                     is.NoError(err)
is.ErrorIs(err, ErrNotFound)      // walks error chain
is.ErrorAs(err, &target)
is.ErrorContains(err, "not found")

// Type
is.IsType(&User{}, obj)
is.Implements((*io.Reader)(nil), obj)

参数顺序:始终使用 (expected, actual) —— 交换顺序会产生令人困惑的差异输出。

高级断言

is.ElementsMatch([]string{"b", "a", "c"}, result)             // unordered comparison
is.InDelta(3.14, computedPi, 0.01)                            // float tolerance
is.JSONEq(`{"name":"alice"}`, `{"name": "alice"}`)             // ignores whitespace/key order
is.WithinDuration(expected, actual, 5*time.Second)
is.Regexp(`^user-[a-f0-9]+$`, userID)

// Async polling
is.Eventually(func() bool {
    status, _ := client.GetJobStatus(jobID)
    return status == "completed"
}, 5*time.Second, 100*time.Millisecond)

// Async polling with rich assertions
is.EventuallyWithT(func(c *assert.CollectT) {
    resp, err := client.GetOrder(orderID)
    assert.NoError(c, err)
    assert.Equal(c, "shipped", resp.Status)
}, 10*time.Second, 500*time.Millisecond)

testify/mock

对接口进行 Mock,以隔离被测单元。嵌入 mock.Mock,使用 m.Called() 实现方法,并始终使用 AssertExpectations(t) 验证。

关键匹配器:mock.Anything、mock.AnythingOfType("T")、mock.MatchedBy(func)。调用修饰符:.Once()、.Times(n)、.Maybe()、.Run(func)。

有关定义 Mock、参数匹配器、调用修饰符、返回序列和验证的信息,请参阅 Mock 参考。

testify/suite

测试套件将相关测试分组,并共享设置和清理逻辑。

生命周期
SetupSuite()    → once before all tests
  SetupTest()   → before each test
    TestXxx()
  TearDownTest() → after each test
TearDownSuite() → once after all tests
示例
type TokenServiceSuite struct {
    suite.Suite
    store   *MockTokenStore
    service *TokenService
}

func (s *TokenServiceSuite) SetupTest() {
    s.store = new(MockTokenStore)
    s.service = NewTokenService(s.store)
}

func (s *TokenServiceSuite) TestGenerate_ReturnsValidToken() {
    s.store.On("Save", mock.Anything, mock.Anything).Return(nil)
    token, err := s.service.Generate("user-42")
    s.NoError(err)
    s.NotEmpty(token)
    s.store.AssertExpectations(s.T())
}

// Required launcher
func TestTokenServiceSuite(t *testing.T) {
    suite.Run(t, new(TokenServiceSuite))
}

像 s.Equal() 这样的测试套件方法与 assert 的行为相同。对于 require:s.Require().NotNil(obj)。

常见错误

  • 忘记调用 AssertExpectations(t) —— 未经验证的 Mock 预期会被静默通过
  • is.Equal(ErrNotFound, err) —— 对包装后的错误检查会失败。使用 is.ErrorIs 遍历错误链
  • 参数顺序颠倒 —— testify 假定顺序为 (expected, actual)。颠倒顺序会导致差异方向相反
  • 用 assert 检查守卫条件 —— 测试在失败后会继续执行,并因解引用 nil 而 Panic。应使用 require
  • 缺少 suite.Run() —— 没有启动函数时,不会执行任何测试,而且不会提示
  • 比较指针 —— is.Equal(ptr1, ptr2) 比较的是地址。请解引用,或使用 EqualExportedValues

Linter

使用 testifylint 检测参数顺序错误、误用 assert/require 等问题。请参阅 samber/cc-skills-golang@golang-lint 技能。

交叉引用

  • → 有关常见测试模式、表驱动测试和 CI,请参阅 samber/cc-skills-golang@golang-testing 技能
  • → 有关 testifylint 配置,请参阅 samber/cc-skills-golang@golang-lint 技能

评论

支持 Markdown