測試隔離#
// 不好:測試間有依賴
var globalCounter int
func TestA(t *testing.T) {
globalCounter = 10
// ...
}
func TestB(t *testing.T) {
// 依賴 TestA 設定的值
if globalCounter != 10 { ... } // 可能失敗
}
// 好:每個測試獨立
func TestA(t *testing.T) {
counter := 10
// ...
}
func TestB(t *testing.T) {
counter := 10 // 獨立設定
// ...
}Mock 與 Stub#
// 定義介面
type EmailSender interface {
Send(to, subject, body string) error
}
// 實際實作
type SMTPSender struct { ... }
// 測試用 Mock
type MockSender struct {
SentEmails []Email
}
func (m *MockSender) Send(to, subject, body string) error {
m.SentEmails = append(m.SentEmails, Email{to, subject, body})
return nil
}
// 測試
func TestNotification(t *testing.T) {
mock := &MockSender{}
service := NewNotificationService(mock)
service.NotifyUser("user@example.com", "Hello")
if len(mock.SentEmails) != 1 {
t.Error("expected 1 email sent")
}
}並行測試#
// 使用 t.Parallel() 標記可並行的測試
func TestParallel1(t *testing.T) {
t.Parallel() // 可以與其他 Parallel 測試同時執行
// ...
}
func TestParallel2(t *testing.T) {
t.Parallel()
// ...
}
// 注意:並行測試不能共用可變狀態測試命名建議:
- 使用描述性名稱,讓測試失敗時能立即理解問題
- 遵循
Test<Function>_<Scenario>_<Expected>模式- 例如:
TestDivide_ByZero_ReturnsError
測試錯誤路徑#
func TestErrorHandling(t *testing.T) {
tests := []struct {
name string
input string
wantErr bool
errContains string
}{
{"empty input", "", true, "cannot be empty"},
{"invalid format", "abc", true, "invalid format"},
{"valid input", "123", false, ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := Parse(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("error %q should contain %q",
err.Error(), tt.errContains)
}
} else if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}