426 lines
9.6 KiB
Markdown
426 lines
9.6 KiB
Markdown
# Test Pattern Library
|
|
|
|
**Version**: 2.0
|
|
**Source**: Bootstrap-002 Test Strategy Development
|
|
**Last Updated**: 2025-10-18
|
|
|
|
This document provides 8 proven test patterns for Go testing with practical examples and usage guidance.
|
|
|
|
---
|
|
|
|
## Pattern 1: Unit Test Pattern
|
|
|
|
**Purpose**: Test a single function or method in isolation
|
|
|
|
**Structure**:
|
|
```go
|
|
func TestFunctionName_Scenario(t *testing.T) {
|
|
// Setup
|
|
input := createTestInput()
|
|
|
|
// Execute
|
|
result, err := FunctionUnderTest(input)
|
|
|
|
// Assert
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if result != expected {
|
|
t.Errorf("expected %v, got %v", expected, result)
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing pure functions (no side effects)
|
|
- Simple input/output validation
|
|
- Single test scenario
|
|
|
|
**Time**: ~8-10 minutes per test
|
|
|
|
---
|
|
|
|
## Pattern 2: Table-Driven Test Pattern
|
|
|
|
**Purpose**: Test multiple scenarios with the same test logic
|
|
|
|
**Structure**:
|
|
```go
|
|
func TestFunction(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input InputType
|
|
expected OutputType
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid input",
|
|
input: validInput,
|
|
expected: validOutput,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid input",
|
|
input: invalidInput,
|
|
expected: zeroValue,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := Function(tt.input)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Function() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if !tt.wantErr && result != tt.expected {
|
|
t.Errorf("Function() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing boundary conditions
|
|
- Multiple input variations
|
|
- Comprehensive coverage
|
|
|
|
**Time**: ~10-15 minutes for 3-5 scenarios
|
|
|
|
---
|
|
|
|
## Pattern 3: Integration Test Pattern
|
|
|
|
**Purpose**: Test complete request/response flow through handlers
|
|
|
|
**Structure**:
|
|
```go
|
|
func TestHandler(t *testing.T) {
|
|
// Setup: Create request
|
|
req := createTestRequest()
|
|
|
|
// Setup: Capture output
|
|
var buf bytes.Buffer
|
|
outputWriter = &buf
|
|
defer func() { outputWriter = originalWriter }()
|
|
|
|
// Execute
|
|
handleRequest(req)
|
|
|
|
// Assert: Parse response
|
|
var resp Response
|
|
if err := json.Unmarshal(buf.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to parse response: %v", err)
|
|
}
|
|
|
|
// Assert: Validate response
|
|
if resp.Error != nil {
|
|
t.Errorf("unexpected error: %v", resp.Error)
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing MCP server handlers
|
|
- HTTP endpoint testing
|
|
- End-to-end flows
|
|
|
|
**Time**: ~15-20 minutes per test
|
|
|
|
---
|
|
|
|
## Pattern 4: Error Path Test Pattern
|
|
|
|
**Purpose**: Systematically test error handling and edge cases
|
|
|
|
**Structure**:
|
|
```go
|
|
func TestFunction_ErrorCases(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input InputType
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "nil input",
|
|
input: nil,
|
|
wantErr: true,
|
|
errMsg: "input cannot be nil",
|
|
},
|
|
{
|
|
name: "empty input",
|
|
input: InputType{},
|
|
wantErr: true,
|
|
errMsg: "input cannot be empty",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := Function(tt.input)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Function() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
|
|
if tt.wantErr && !strings.Contains(err.Error(), tt.errMsg) {
|
|
t.Errorf("expected error containing '%s', got '%s'", tt.errMsg, err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing validation logic
|
|
- Boundary condition testing
|
|
- Error recovery
|
|
|
|
**Time**: ~12-15 minutes for 3-4 error cases
|
|
|
|
---
|
|
|
|
## Pattern 5: Test Helper Pattern
|
|
|
|
**Purpose**: Reduce duplication and improve maintainability
|
|
|
|
**Structure**:
|
|
```go
|
|
// Test helper function
|
|
func createTestInput(t *testing.T, options ...Option) *InputType {
|
|
t.Helper() // Mark as helper for better error reporting
|
|
|
|
input := &InputType{
|
|
Field1: "default",
|
|
Field2: 42,
|
|
}
|
|
|
|
for _, opt := range options {
|
|
opt(input)
|
|
}
|
|
|
|
return input
|
|
}
|
|
|
|
// Usage
|
|
func TestFunction(t *testing.T) {
|
|
input := createTestInput(t, WithField1("custom"))
|
|
result, err := Function(input)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Complex test setup
|
|
- Repeated fixture creation
|
|
- Test data builders
|
|
|
|
**Time**: ~5 minutes to create, saves 2-3 min per test using it
|
|
|
|
---
|
|
|
|
## Pattern 6: Dependency Injection Pattern
|
|
|
|
**Purpose**: Test components that depend on external systems
|
|
|
|
**Structure**:
|
|
```go
|
|
// 1. Define interface
|
|
type Executor interface {
|
|
Execute(args Args) (Result, error)
|
|
}
|
|
|
|
// 2. Production implementation
|
|
type RealExecutor struct{}
|
|
func (e *RealExecutor) Execute(args Args) (Result, error) {
|
|
// Real implementation
|
|
}
|
|
|
|
// 3. Mock implementation
|
|
type MockExecutor struct {
|
|
Results map[string]Result
|
|
Errors map[string]error
|
|
}
|
|
|
|
func (m *MockExecutor) Execute(args Args) (Result, error) {
|
|
if err, ok := m.Errors[args.Key]; ok {
|
|
return Result{}, err
|
|
}
|
|
return m.Results[args.Key], nil
|
|
}
|
|
|
|
// 4. Tests use mock
|
|
func TestProcess(t *testing.T) {
|
|
mock := &MockExecutor{
|
|
Results: map[string]Result{"key": {Value: "expected"}},
|
|
}
|
|
err := ProcessData(mock, testData)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing components that execute commands
|
|
- Testing HTTP clients
|
|
- Testing database operations
|
|
|
|
**Time**: ~20-25 minutes (includes refactoring)
|
|
|
|
---
|
|
|
|
## Pattern 7: CLI Command Test Pattern
|
|
|
|
**Purpose**: Test Cobra command execution with flags
|
|
|
|
**Structure**:
|
|
```go
|
|
func TestCommand(t *testing.T) {
|
|
// Setup: Create command
|
|
cmd := &cobra.Command{
|
|
Use: "command",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
// Command logic
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// Setup: Add flags
|
|
cmd.Flags().StringP("flag", "f", "default", "description")
|
|
|
|
// Setup: Set arguments
|
|
cmd.SetArgs([]string{"--flag", "value"})
|
|
|
|
// Setup: Capture output
|
|
var buf bytes.Buffer
|
|
cmd.SetOut(&buf)
|
|
|
|
// Execute
|
|
err := cmd.Execute()
|
|
|
|
// Assert
|
|
if err != nil {
|
|
t.Fatalf("command failed: %v", err)
|
|
}
|
|
|
|
// Verify output
|
|
if !strings.Contains(buf.String(), "expected") {
|
|
t.Errorf("unexpected output: %s", buf.String())
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing CLI command handlers
|
|
- Flag parsing verification
|
|
- Command composition testing
|
|
|
|
**Time**: ~12-15 minutes per test
|
|
|
|
---
|
|
|
|
## Pattern 8: Global Flag Test Pattern
|
|
|
|
**Purpose**: Test global flag parsing and propagation
|
|
|
|
**Structure**:
|
|
```go
|
|
func TestGlobalFlags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
expected GlobalOptions
|
|
}{
|
|
{
|
|
name: "default",
|
|
args: []string{},
|
|
expected: GlobalOptions{ProjectPath: getCwd()},
|
|
},
|
|
{
|
|
name: "with flag",
|
|
args: []string{"--session", "abc"},
|
|
expected: GlobalOptions{SessionID: "abc"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
resetGlobalFlags() // Important: reset state
|
|
rootCmd.SetArgs(tt.args)
|
|
rootCmd.ParseFlags(tt.args)
|
|
opts := getGlobalOptions()
|
|
|
|
if opts.SessionID != tt.expected.SessionID {
|
|
t.Errorf("SessionID = %v, expected %v", opts.SessionID, tt.expected.SessionID)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to Use**:
|
|
- Testing global flag parsing
|
|
- Flag interaction testing
|
|
- Option struct population
|
|
|
|
**Time**: ~10-12 minutes (table-driven, high efficiency)
|
|
|
|
---
|
|
|
|
## Pattern Selection Decision Tree
|
|
|
|
```
|
|
What are you testing?
|
|
├─ CLI command with flags?
|
|
│ ├─ Multiple flag combinations? → Pattern 8 (Global Flag)
|
|
│ ├─ Integration test needed? → Pattern 7 (CLI Command)
|
|
│ └─ Command execution? → Pattern 7 (CLI Command)
|
|
├─ Error paths?
|
|
│ ├─ Multiple error scenarios? → Pattern 4 (Error Path) + Pattern 2 (Table-Driven)
|
|
│ └─ Single error case? → Pattern 4 (Error Path)
|
|
├─ Unit function?
|
|
│ ├─ Multiple inputs? → Pattern 2 (Table-Driven)
|
|
│ └─ Single input? → Pattern 1 (Unit Test)
|
|
├─ External dependency?
|
|
│ └─ → Pattern 6 (Dependency Injection)
|
|
└─ Integration flow?
|
|
└─ → Pattern 3 (Integration Test)
|
|
```
|
|
|
|
---
|
|
|
|
## Pattern Efficiency Metrics
|
|
|
|
**Time per Test** (measured):
|
|
- Unit Test (Pattern 1): ~8 min
|
|
- Table-Driven (Pattern 2): ~12 min (3-4 scenarios)
|
|
- Integration Test (Pattern 3): ~18 min
|
|
- Error Path (Pattern 4): ~14 min (4 scenarios)
|
|
- Test Helper (Pattern 5): ~5 min to create
|
|
- Dependency Injection (Pattern 6): ~22 min (includes refactoring)
|
|
- CLI Command (Pattern 7): ~13 min
|
|
- Global Flag (Pattern 8): ~11 min
|
|
|
|
**Coverage Impact per Test**:
|
|
- Table-Driven: 0.20-0.30% total coverage (high impact)
|
|
- Error Path: 0.10-0.15% total coverage
|
|
- CLI Command: 0.15-0.25% total coverage
|
|
- Unit Test: 0.10-0.20% total coverage
|
|
|
|
**Best ROI Patterns**:
|
|
1. Global Flag Tests (Pattern 8): High coverage, fast execution
|
|
2. Table-Driven Tests (Pattern 2): Multiple scenarios, efficient
|
|
3. Error Path Tests (Pattern 4): Critical coverage, systematic
|
|
|
|
---
|
|
|
|
**Source**: Bootstrap-002 Test Strategy Development
|
|
**Framework**: BAIME (Bootstrapped AI Methodology Engineering)
|
|
**Status**: Production-ready, validated through 4 iterations
|