stretchr/testify 的完整指南,適用於 Golang 測試。深入涵蓋 assert、require、mock 和 suite 套件。在撰寫 testify 測試、建立 mock、設定測試套件或選擇 assert 與 require 時使用。涵蓋 testify 斷言、mock 預期、參數匹配器、呼叫驗證、套件生命週期以及進階模式,例如 Eventually、JSONEq 和自訂匹配器。當程式碼庫匯入 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) 以提升可讀性。將它們命名為 is 和 must:
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.Anything、mock.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 設定






