| name | spec-process-dev |
| description | TDD (Test-Driven Development) workflow for implementation. Use when writing code, implementing features, or fixing bugs. Covers RED-GREEN-REFACTOR cycle, test patterns, and coverage requirements. (project) |
Development Process (TDD Workflow)
The TDD Cycle
RED Phase: Write Failing Test
Before writing any implementation code:
- Identify acceptance criterion from user story
- Translate to test case:
- Given → Arrange (setup)
- When → Act (execute)
- Then → Assert (verify)
- Run test - confirm it fails
- Verify failure reason - should fail because code doesn't exist
Example:
func TestAcceptanceRate_CalculatesCorrectly(t *testing.T) {
dev := NewDeveloper("dev-1")
dev.SuggestionsShown = 100
dev.SuggestionsAccepted = 75
rate := dev.AcceptanceRate()
assert.Equal(t, 75.0, rate)
}
Run: go test ./... -run TestAcceptanceRate → FAIL (method doesn't exist)
GREEN Phase: Minimal Implementation
Write just enough code to pass the test:
- Implement minimally - no extra features
- Run test - confirm it passes
- No refactoring yet - just make it work
func (d *Developer) AcceptanceRate() float64 {
if d.SuggestionsShown == 0 {
return 0
}
return float64(d.SuggestionsAccepted) / float64(d.SuggestionsShown) * 100
}
Run: go test ./... -run TestAcceptanceRate → PASS
REFACTOR Phase: Clean Up
Improve code while keeping tests green:
- Extract duplication
- Improve naming
- Simplify logic
- Run tests after each change
DO NOT:
- Add new functionality
- Change behavior
- Skip running tests
Test Patterns by Type
Unit Tests
Test individual functions/methods in isolation:
func TestCalculator_Add(t *testing.T) {
calc := NewCalculator()
result := calc.Add(2, 3)
assert.Equal(t, 5, result)
}
Table-Driven Tests
Test multiple scenarios efficiently:
func TestAcceptanceRate(t *testing.T) {
tests := []struct {
name string
shown int
accepted int
expected float64
}{
{"all accepted", 100, 100, 100.0},
{"half accepted", 100, 50, 50.0},
{"none shown", 0, 0, 0.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dev := NewDeveloper("test")
dev.SuggestionsShown = tt.shown
dev.SuggestionsAccepted = tt.accepted
assert.Equal(t, tt.expected, dev.AcceptanceRate())
})
}
}
Integration Tests
Test component interactions:
func TestHandler_ReturnsCorrectFormat(t *testing.T) {
store := storage.NewMemory()
store.AddCommit(testCommit)
handler := NewHandler(store)
req := httptest.NewRequest("GET", "/commits", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
assert.Equal(t, 200, rec.Code)
assert.Contains(t, rec.Body.String(), "data")
}
Running Tests
Go (cursor-sim)
go test ./...
go test ./internal/models
go test ./... -run TestAcceptanceRate
go test ./... -cover
go test ./... -v
Coverage Requirements
| Category | Minimum | Target |
|---|
| Core logic | 90% | 95% |
| API handlers | 80% | 90% |
| Utilities | 70% | 80% |
| Overall | 80% | 85% |
Check coverage:
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out
When Tests Fail
- Read the error message carefully
- Understand expected vs actual
- Fix the issue (code or test?)
- Run tests again
- Don't proceed until green
Common issues:
- Test has wrong expectation → Fix test
- Implementation has bug → Fix code
- Missing setup/teardown → Add fixtures
- Flaky test → Add synchronization
After TDD Cycle
Every completed task requires:
- All tests pass
- Coverage meets threshold
- Git commit
- task.md updated
- DEVELOPMENT.md updated
See sdd-checklist skill for enforcement.