Initial commit
This commit is contained in:
425
skills/testing-strategy/reference/patterns.md
Normal file
425
skills/testing-strategy/reference/patterns.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user