Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for writing effective tests using best practices: test pyramid, Arrange-Act-Assert pattern, mocking strategies, and coverage guidelines. Use when the user asks to write tests, improve test coverage, refactor tests, or understand testing best practices. Do NOT use when the user wants to run existing tests without modification, or when the focus is on test infrastructure setup (CI/CD, test frameworks installation).
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 147% | 0% |
| case-12 | ✓→✓ | = Same ✓ | 167% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 136% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 140% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 173% | 0% |
Write reliable, maintainable tests that give confidence in code correctness.
Structure your test suite using the test pyramid model:
/\
/ \ Unit Tests (Fast, many)
/ \
/------\
/ \ Integration Tests (Medium, fewer)
/----------\
/ \ E2E Tests (Slow, few)
/--------------\go// Good: Pure unit test, no dependencies func TestCalculateTotal(t *testing.T) { items := []Item{ {Price: 10.00, Qty: 2}, {Price: 5.00, Qty: 1}, } got := CalculateTotal(items) want := 25.00 if got != want { t.Errorf("CalculateTotal() = %v, want %v", got, want) } }
go// Integration test with database func TestUserRepository_Create(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewUserRepository(db) user := &User{Name: "Alice", Email: "alice@example.com"} err := repo.Create(user) if err != nil { t.Fatalf("Create() error = %v", err) } if user.ID == 0 { t.Error("expected user ID to be set") } }
go// E2E test hitting real HTTP endpoint func TestLoginEndpoint(t *testing.T) { server := startTestServer(t) defer server.Close() resp, err := http.Post( server.URL+"/api/login", "application/json", strings.NewReader(`{"email":"alice@example.com","password":"secret"}`), ) if err != nil { t.Fatalf("request failed: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("status = %d, want %d", resp.StatusCode, http.StatusOK) } }
Structure every test with three clear phases:
gofunc TestDiscountCalculation(t *testing.T) { // ARRANGE: Set up test data and dependencies product := &Product{Price: 100.00} discount := 0.20 // 20% off // ACT: Execute the function under test result := CalculateDiscount(product.Price, discount) // ASSERT: Verify the expected outcome want := 80.00 if result != want { t.Errorf("CalculateDiscount() = %v, want %v", result, want) } }
go// Given-When-Then (BDD style) func TestLoginValidation(t *testing.T) { // Given: valid credentials credentials := Credentials{Email: "alice@example.com", Password: "valid123"} // When: user attempts login result, err := AuthService.Login(credentials) // Then: login succeeds if err != nil { t.Errorf("unexpected error: %v", err) } if result.Token == "" { t.Error("expected token to be set") } } // Arrange-Act-Assert-Cleanup (for resources) func TestFileProcessing(t *testing.T) { // Arrange tmpFile := createTempFile(t, "test data") defer os.Remove(tmpFile) // Act result, err := ProcessFile(tmpFile) // Assert if err != nil { t.Fatalf("ProcessFile() error = %v", err) } if result.Processed != true { t.Error("expected file to be marked as processed") } }
Define interfaces for dependencies:
go// Production code depends on interface, not concrete type type UserStore interface { GetByID(id int64) (*User, error) Save(user *User) error } // Mock implementation type MockUserStore struct { Users map[int64]*User SaveFn func(user *User) error } func (m *MockUserStore) GetByID(id int64) (*User, error) { user, ok := m.Users[id] if !ok { return nil, ErrNotFound } return user, nil } func (m *MockUserStore) Save(user *User) error { if m.SaveFn != nil { return m.SaveFn(user) } m.Users[user.ID] = user return nil }
gofunc TestGetUserProfile(t *testing.T) { // Arrange mockStore := &MockUserStore{ Users: map[int64]*User{ 1: {ID: 1, Name: "Alice", Email: "alice@example.com"}, }, } service := NewUserService(mockStore) // Act profile, err := service.GetProfile(1) // Assert if err != nil { t.Fatalf("GetProfile() error = %v", err) } if profile.Name != "Alice" { t.Errorf("Name = %q, want %q", profile.Name, "Alice") } } func TestSaveUser_RejectsInvalidEmail(t *testing.T) { // Arrange mockStore := &MockUserStore{ Users: make(map[int64]*User), } service := NewUserService(mockStore) user := &User{Name: "Bob", Email: "invalid"} // Act err := service.Save(user) // Assert if err == nil { t.Error("expected error for invalid email, got nil") } }
For testing multiple cases efficiently:
gofunc TestValidateEmail(t *testing.T) { tests := []struct { name string email string wantErr bool }{ {name: "valid email", email: "alice@example.com", wantErr: false}, {name: "missing @", email: "aliceexample.com", wantErr: true}, {name: "missing domain", email: "alice@", wantErr: true}, {name: "empty string", email: "", wantErr: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidateEmail(tt.email) if (err != nil) != tt.wantErr { t.Errorf("ValidateEmail(%q) error = %v, wantErr %v", tt.email, err, tt.wantErr) } }) } }
| Component Type | Minimum Coverage | Rationale | |----------------|------------------|-----------| | Business logic | 80-90% | Core correctness | | Utilities/helpers | 90%+ | Reused everywhere | | API handlers | 70-80% | HTTP concerns | | UI components | 50-70% | Harder to test, visual | | Configuration | 30-50% | Often trivial |
Always test:
Don't obsess over:
bash# Generate coverage report go test -coverprofile=coverage.out ./... # View coverage in terminal go tool cover -func=coverage.out # Generate HTML report for detailed view go tool cover -html=coverage.out -o coverage.html
ok github.com/example/pkg 0.5s coverage: 85.2% of statementsFocus on increasing coverage for files with lowest percentages:
bash# Show coverage per function go tool cover -func=coverage.out | sort -k3 -n
src/
├── user.go # Production code
├── user_test.go # Unit tests (same package)
├── user_integration_test.go # Integration tests
└── testdata/
└── fixtures/ # Test data filesgo// Function under test: CalculateDiscount // Standard test (success case) func TestCalculateDiscount(t *testing.T) { ... } // Failure/edge case func TestCalculateDiscount_ZeroDiscount(t *testing.T) { ... } func TestCalculateDiscount_NegativeAmount(t *testing.T) { ... } // Benchmark func BenchmarkCalculateDiscount(b *testing.B) { ... } // Example (documentation) func ExampleCalculateDiscount() { ... }
gofunc TestProcessItems_EmptySlice(t *testing.T) { result := ProcessItems([]Item{}) if len(result) != 0 { t.Error("expected empty result for empty input") } } func TestProcessItems_NilSlice(t *testing.T) { result := ProcessItems(nil) if result != nil { t.Error("expected nil result for nil input") } }
gofunc TestAgeValidation(t *testing.T) { tests := []struct { age int wantErr bool }{ {age: 0, wantErr: true}, // Minimum boundary {age: 1, wantErr: false}, // Just above minimum {age: 17, wantErr: false}, // Below adult age {age: 18, wantErr: false}, // Adult threshold {age: 150, wantErr: false}, // Reasonable maximum {age: 151, wantErr: true}, // Above reasonable max } // ... test loop }
gofunc TestConcurrentAccess(t *testing.T) { cache := NewCache() var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func(n int) { defer wg.Done() cache.Set(fmt.Sprintf("key%d", n), n) _ = cache.Get(fmt.Sprintf("key%d", n)) }(i) } wg.Wait() // Cache should not panic or corrupt data }
go// BAD: Tests implementation detail, brittle func TestUserRepository_InternalCache(t *testing.T) { repo := NewUserRepository(db) repo.cache = map[int64]*User{} // Testing internal state user := repo.GetUser(1) if _, exists := repo.cache[1]; !exists { t.Error("expected cache to be populated") } } // GOOD: Tests observable behavior func TestUserRepository_GetUser_CachesResult(t *testing.T) { db := &MockDB{QueryCount: 0} repo := NewUserRepository(db) // First call hits database _, _ = repo.GetUser(1) if db.QueryCount != 1 { t.Errorf("expected 1 query, got %d", db.QueryCount) } // Second call uses cache (no additional query) _, _ = repo.GetUser(1) if db.QueryCount != 1 { t.Errorf("expected still 1 query, got %d", db.QueryCount) } }
bashgo test ./... # Run all tests go test -v ./... # Verbose output go test -run TestFunctionName # Run specific test go test -count=3 ./... # Run 3 times (find flaky tests) go test -race ./... # Detect race conditions
bashgo test -cover # Show coverage percentage go test -coverprofile=out.out # Save coverage data go test -bench=. # Run benchmarks go test -short # Skip long-running tests go test -timeout 30s # Fail if test exceeds timeout
Other measured skills in the registry, with their headline benchmark lift.