golang-stretchr-testify

golang-stretchr-testify

熱門

stretchr/testify 的完整指南,適用於 Golang 測試。深入涵蓋 assert、require、mock 和 suite 套件。在撰寫 testify 測試、建立 mock、設定測試套件或選擇 assert 與 require 時使用。涵蓋 testify 斷言、mock 預期、參數匹配器、呼叫驗證、套件生命週期以及進階模式,例如 Eventually、JSONEq 和自訂匹配器。當程式碼庫匯入 github.com/stretchr/testify 時適用。

2261星標
150分支
更新於 2026/6/6
SKILL.md
readonlyread-only
name
golang-stretchr-testify
description

Comprehensive guide to stretchr/testify for Golang testing. Covers assert, require, mock, and suite packages in depth. Use when writing tests with testify, creating mocks, setting up test suites, or choosing between assert and require. Covers testify assertions, mock expectations, argument matchers, call verification, suite lifecycle, and advanced patterns like Eventually, JSONEq, and custom matchers. Apply when the codebase imports github.com/stretchr/testify.

角色定位: 你是一位將測試視為可執行規格的 Go 工程師。你撰寫測試是為了約束行為並讓失敗一目了然,而不是為了達成覆蓋率目標。

模式:

  • 撰寫模式 — 為程式碼庫新增測試或 mock。
  • 審查模式 — 稽核現有測試程式碼中 testify 的誤用。

stretchr/testify

testify 以可讀的斷言、mock 和套件補充 Go 的 testing 套件。它不取代 testing — 始終使用 *testing.T 作為進入點。

此技能並非詳盡無遺。請參考函式庫文件和程式碼範例以取得更多資訊。Context7 可作為探索平台提供協助。如需 Go 套件文件、版本、符號和已知漏洞,→ 請參閱 samber/cc-skills-golang@golang-pkg-go-dev 技能。

assert vs require

兩者提供相同的斷言。差異在於失敗行為:

  • assert:記錄失敗,繼續執行 — 一次看到所有失敗
  • require:呼叫 t.FailNow() — 用於前提條件,若繼續執行會導致 panic 或誤導

使用 assert.New(t) / require.New(t) 以提升可讀性。將它們命名為 ismust

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

    cfg, err := ParseConfig("testdata/valid.yaml")
    must.NoError(err)    // 若解析失敗則停止 — cfg 會是 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)

// 相等性
is.Equal(expected, actual)              // DeepEqual + 精確型別
is.NotEqual(unexpected, actual)
is.EqualValues(expected, actual)        // 先轉換為通用型別
is.EqualExportedValues(expected, actual)

// Nil / 布林 / 空值
is.Nil(obj)                  is.NotNil(obj)
is.True(cond)                is.False(cond)
is.Empty(collection)         is.NotEmpty(collection)
is.Len(collection, n)

// 包含(字串、切片、map 鍵)
is.Contains("hello world", "world")
is.Contains([]int{1, 2, 3}, 2)
is.Contains(map[string]int{"a": 1}, "a")

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

// 錯誤
is.Error(err)                     is.NoError(err)
is.ErrorIs(err, ErrNotFound)      // 遍歷錯誤鏈
is.ErrorAs(err, &target)
is.ErrorContains(err, "not found")

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

參數順序:始終為 (expected, actual) — 交換會產生令人困惑的 diff 輸出。

進階斷言

is.ElementsMatch([]string{"b", "a", "c"}, result)             // 無序比較
is.InDelta(3.14, computedPi, 0.01)                            // 浮點數容差
is.JSONEq(`{"name":"alice"}`, `{"name": "alice"}`)             // 忽略空白/鍵順序
is.WithinDuration(expected, actual, 5*time.Second)
is.Regexp(`^user-[a-f0-9]+$`, userID)

// 非同步輪詢
is.Eventually(func() bool {
    status, _ := client.GetJobStatus(jobID)
    return status == "completed"
}, 5*time.Second, 100*time.Millisecond)

// 非同步輪詢搭配豐富斷言
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,使用 m.Called() 實作方法,務必以 AssertExpectations(t) 驗證。

關鍵匹配器:mock.Anythingmock.AnythingOfType("T")mock.MatchedBy(func)。呼叫修飾詞:.Once().Times(n).Maybe().Run(func)

關於定義 mock、參數匹配器、呼叫修飾詞、回傳序列和驗證,請參閱 Mock 參考

testify/suite

套件將相關測試分組,並共用設定/拆卸。

生命週期

SetupSuite()    → 所有測試前執行一次
  SetupTest()   → 每個測試前執行
    TestXxx()
  TearDownTest() → 每個測試後執行
TearDownSuite() → 所有測試後執行一次

範例

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())
}

// 必要的啟動函式
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)。交換會產生反向的 diff
  • 使用 assert 進行防衛 — 測試在失敗後繼續執行,並因 nil 解參考而 panic。應使用 require
  • 缺少 suite.Run() — 沒有啟動函式,零個測試會默默執行
  • 比較指標is.Equal(ptr1, ptr2) 比較位址。請解參考或使用 EqualExportedValues

Linter

使用 testifylint 來捕捉錯誤的參數順序、assert/require 誤用等。請參閱 samber/cc-skills-golang@golang-lint 技能。

交叉參考

  • → 請參閱 samber/cc-skills-golang@golang-testing 技能以了解一般測試模式、表格驅動測試和 CI
  • → 請參閱 samber/cc-skills-golang@golang-lint 技能以了解 testifylint 設定