返回 Skill 列表
extension
分类: 开发与工程无需 API Key

testing

测试标准和最佳实践

person作者: jakexiaohubgithub

Testing Standards

Philosophy

Tests are verification infrastructure, not checkboxes. They protect against regressions, document expected behavior, and surface real issues for human evaluation.

Comprehensive, not exhaustive. Cover meaningful scenarios without combinatorial explosion.


Pre-Flight Checklist

Before writing any test, answer:

  1. What is the contract? (inputs → outputs, side effects, errors)
  2. What are the boundaries? (empty, nil, zero, max, negative)
  3. What can go wrong? (every error return path)
  4. What state does it depend on? (filesystem, env vars, time)
  5. Is there concurrency? (goroutines, shared state)

Write tests for #2 and #3 FIRST. Edge cases and errors catch real bugs.


Go Test Essentials

func TestFoo(t *testing.T) {
    t.Parallel()           // Unless shared state
    dir := t.TempDir()     // Auto-cleanup, not os.MkdirTemp
    t.Setenv("KEY", "val") // Auto-restore, prevents t.Parallel() misuse
}

func helper(t *testing.T) {
    t.Helper()  // REQUIRED - fixes line numbers in failures
}

Error assertions:

if !errors.Is(err, ErrNotFound) { ... }      // Sentinel errors
if !errors.As(err, &pathErr) { ... }         // Wrapped errors
if !strings.Contains(err.Error(), "x") {...} // Message matching

Table-Driven Tests

func TestCharge(t *testing.T) {
    tests := []struct {
        name    string
        card    Card   // GIVEN
        amount  int64  // GIVEN
        wantErr error  // THEN
    }{
        {"expired card", Card{Expiry: past}, 100, ErrExpired},
        {"valid card", Card{Expiry: future}, 100, nil},
        {"negative amount", Card{}, -1, ErrInvalidAmount},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            _, err := svc.Charge(tt.card, tt.amount)  // WHEN
            if !errors.Is(err, tt.wantErr) {
                t.Errorf("got %v, want %v", err, tt.wantErr)
            }
        })
    }
}

Coverage Requirements

| Path Type | Required | Purpose | |-----------|----------|---------| | Happy path | Always | Confirms expected behavior | | Error paths | Always | Every error return tested | | Edge cases | When meaningful | nil, empty, zero, boundaries |

If a function returns an error, test that it returns the right error.


Real-World Test Data

Tests must reflect production reality:

| Aspect | Bad | Good | |--------|-----|------| | Data | User{Name: "test"} | User{Name: "María García-López"} | | Scale | Single item | Batch of 100+ | | State | Empty/zero only | Realistic pre-existing data |

Mocks are a last resort. Prefer interfaces with test implementations or recorded fixtures.


Test Integrity Rules

❌ FORBIDDEN: Changing assertions to match broken behavior
❌ FORBIDDEN: Deleting tests that "don't work anymore"
❌ FORBIDDEN: Adding t.Skip() without justification
✅ REQUIRED: Escalate unexpected failures for evaluation

Tests may fix implementation only when:

  1. Test reveals a legitimate bug (doesn't match documented contract)
  2. Fix is isolated to the defect (no scope creep)

Escalate when: Multiple tests fail, architectural issue suspected, or fix requires API changes.


Anti-Patterns

| Pattern | Problem | Fix | |---------|---------|-----| | Testing unexported details | Brittle | Test exported behavior | | One giant test function | Hard to diagnose | Table-driven subtests | | Shared mutable state | Race conditions | Fresh setup per test | | time.Sleep | Slow, flaky | Channels, contexts, polling | | os.Chdir in tests | Breaks parallel | Pass paths explicitly | | Missing t.Helper() | Wrong line numbers | Add to all helpers | | os.MkdirTemp without cleanup | Leaks | Use t.TempDir() |


Organization

pkg/service/
├── service.go
├── service_test.go       # Unit tests
└── service_int_test.go   # //go:build integration
internal/testutil/        # Shared helpers

Always run: go test -race ./...