測試隔離#

// 不好:測試間有依賴
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)
            }
        })
    }
}