--- name: atdd description: Implement user stories using the ATDD workflow — Red (failing acceptance test) → Green (minimal implementation) → Refactor. One story at a time. --- # Acceptance Test-Driven Development (ATDD) ## Overview ATDD extends TDD to the acceptance level. You write tests that capture the behavior described in a user story's acceptance criteria — then implement just enough to make them pass. **Input required:** User stories with clear acceptance criteria. Load `user-stories` skill first. ## Core Methodology: Red-Green-Refactor at Acceptance Level ``` RED → Write failing acceptance test (one story at a time) GREEN → Write minimal code to make it pass REFACTOR → Clean up code (load clean-code skill) COMMIT → Commit with reference to the story ``` **Key principle:** Never mix phases. RED is only writing tests. GREEN is only making tests pass. REFACTOR is only cleaning up. **Stop and confirm** before advancing from RED to GREEN, and from GREEN to REFACTOR. ## Working on One Story at a Time Pick the highest-priority story from the backlog. Do not start a second story until the first is done (committed, clean, all tests green). ## Phase 1: RED — Write Failing Acceptance Test Translate the story's acceptance criteria into executable tests. **For each acceptance criterion:** ``` Given [context] When [action] Then [observable outcome] ``` Becomes a test case. ### Go ATDD Example **Story:** "Users can register with email and password" **Acceptance criteria:** - Given valid email and password, registration succeeds and returns the new user - Given an email already in use, registration returns ErrDuplicateEmail - Given an invalid email format, registration returns ErrInvalidEmail - Given a password shorter than 8 chars, registration returns ErrWeakPassword **Acceptance tests (write these BEFORE implementation):** ```go func TestUserRegistration_Acceptance(t *testing.T) { tests := []struct { name string email string pass string wantErr error }{ { name: "valid registration creates user", email: "alice@example.com", pass: "securePass1!", wantErr: nil, }, { name: "duplicate email returns ErrDuplicateEmail", email: "existing@example.com", pass: "securePass1!", wantErr: ErrDuplicateEmail, }, { name: "invalid email format returns ErrInvalidEmail", email: "not-an-email", pass: "securePass1!", wantErr: ErrInvalidEmail, }, { name: "short password returns ErrWeakPassword", email: "alice@example.com", pass: "short", wantErr: ErrWeakPassword, }, } store := NewInMemoryUserStore() // Pre-seed duplicate email store.Seed(User{Email: "existing@example.com"}) svc := NewUserService(store) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := svc.Register(context.Background(), tt.email, tt.pass) if tt.wantErr != nil { assert.ErrorIs(t, err, tt.wantErr) } else { assert.NoError(t, err) } }) } } ``` **Run and verify they fail:** ```bash go test -run TestUserRegistration_Acceptance ./... # FAIL: UserService.Register undefined ``` The test must fail because the implementation doesn't exist yet. If it passes, you're testing existing behavior. Check your test. **STOP here. Confirm before moving to GREEN.** ## Phase 2: GREEN — Minimal Implementation Implement the absolute minimum code necessary to make the acceptance tests pass. Ugly code is acceptable at this stage. The goal is green, not clean. ```go // Minimal implementation — prioritize passing tests over elegance func (s *UserService) Register(ctx context.Context, email, password string) (User, error) { if !strings.Contains(email, "@") { return User{}, ErrInvalidEmail } if len(password) < 8 { return User{}, ErrWeakPassword } if s.store.Exists(ctx, email) { return User{}, ErrDuplicateEmail } u := User{ID: newID(), Email: email} if err := s.store.Save(ctx, u); err != nil { return User{}, fmt.Errorf("save user: %w", err) } return u, nil } ``` **Verify GREEN:** ```bash go test -run TestUserRegistration_Acceptance ./... # ok go test ./... # All other tests still pass go test -race ./... ``` **STOP here. Confirm before moving to REFACTOR.** ## Phase 3: REFACTOR — Clean Up With tests green, clean the code. Do not change behavior. **During REFACTOR, load and follow the `clean-code` skill.** Apply: - Extract functions for complex logic - Better naming - Remove duplication - Apply SOLID principles - For design decisions, load `solid` skill After every change: `go test ./...` must stay green. **Do NOT refactor the acceptance tests themselves** unless they have a bug. The tests are your specification — changing them changes what you've committed to. **STOP here. Confirm before committing.** ## Phase 4: COMMIT Commit the completed story. Reference the story in the commit message. ```bash git commit -m "feat: user registration with email validation Implements acceptance criteria for US-001 (User Registration). - Valid email/password creates a user - Duplicate email returns ErrDuplicateEmail - Invalid email format returns ErrInvalidEmail - Short password returns ErrWeakPassword" ``` ## For Test Design Questions When writing acceptance tests, load the `test-design` skill to ensure: - Tests are understandable (Farley: Understandable) - Tests don't couple to implementation details (Farley: Maintainable) - Tests are repeatable across environments (Farley: Repeatable) ## For Code Review Before Commit Load the `code-review` skill before committing to check: - Error handling follows `fmt.Errorf("op: %w", err)` pattern - Context propagation is correct - No race conditions in concurrent code - Exported API is minimal ## For Refactoring Guidance Load the `refactoring` skill for systematic techniques to apply during REFACTOR phase. ## For Clean Code Principles Load the `clean-code` skill during REFACTOR phase to apply: - Naming conventions - Function size and responsibility - SOLID principles ## Go-Specific ATDD Notes ### Acceptance Test Location Place acceptance tests in `_test.go` files with the `_acceptance_test.go` suffix or `//go:build acceptance` build tag to separate from fast unit tests: ```go //go:build acceptance package service_test ``` ```bash go test ./... # Unit tests only (fast) go test -tags=acceptance ./... # Include acceptance tests ``` ### In-Memory Implementations for Acceptance Tests Use in-memory implementations of repositories for acceptance tests: ```go type InMemoryUserStore struct { mu sync.Mutex users map[string]User } func (s *InMemoryUserStore) Save(ctx context.Context, u User) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.users[u.Email]; exists { return ErrDuplicateEmail } s.users[u.Email] = u return nil } func (s *InMemoryUserStore) Exists(ctx context.Context, email string) bool { s.mu.Lock() defer s.mu.Unlock() _, exists := s.users[email] return exists } ``` This keeps acceptance tests fast and deterministic. ### Given-When-Then in Go The `t.Run` structure maps naturally to Given-When-Then: ```go t.Run("given valid credentials, when logging in, then returns token", func(t *testing.T) { // Given: valid user exists store := InMemoryUserStore{...} // When: login is called token, err := svc.Login(ctx, email, pass) // Then: token is returned require.NoError(t, err) assert.NotEmpty(t, token) }) ``` ## Verification Checklist Before committing: - [ ] All acceptance tests pass - [ ] All existing tests still pass - [ ] Race detector clean: `go test -race ./...` - [ ] REFACTOR phase complete — code is clean - [ ] Commit message references the user story - [ ] No behavior added beyond the acceptance criteria ## Cross-References - Requires: `user-stories` skill output (stories with acceptance criteria) - During REFACTOR: load `clean-code` skill - For test quality: load `test-design` skill - For code review: load `code-review` skill - For refactoring guidance: load `refactoring` skill - Foundation: load `tdd` skill for core TDD methodology