BMAD-METHOD/bmad-core/tasks/write-failing-tests.md

259 lines
6.7 KiB
Markdown

<!-- Powered by BMAD™ Core -->
# write-failing-tests
Write failing tests first to drive development using Test-Driven Development (TDD) Red phase.
## Purpose
Generate failing unit tests that describe expected behavior before implementation. This is the "Red" phase of TDD where we define what success looks like through tests that initially fail.
## Prerequisites
- Story status must be "InProgress" or "Ready"
- TDD must be enabled in core-config.yaml (`tdd.enabled: true`)
- Acceptance criteria are clearly defined
- Test runner is configured or auto-detected
## Inputs
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "1.3"
- story_path: '{devStoryLocation}/{epic}.{story}.*.md' # Path from core-config.yaml
- story_title: '{title}' # If missing, derive from story file H1
- story_slug: '{slug}' # If missing, derive from title (lowercase, hyphenated)
```
## Process
### 1. Analyze Story Requirements
Read the story file and extract:
- Acceptance criteria (AC) that define success
- Business rules and constraints
- Edge cases and error conditions
- Data inputs and expected outputs
### 2. Design Test Strategy
For each acceptance criterion:
- Identify the smallest testable unit
- Choose appropriate test type (unit/integration/e2e)
- Plan test data and scenarios
- Consider mocking strategy for external dependencies
### 3. Detect/Configure Test Runner
```yaml
detection_order:
- Check project files for known patterns
- JavaScript: package.json dependencies (jest, vitest, mocha)
- Python: requirements files (pytest, unittest)
- Java: pom.xml, build.gradle (junit, testng)
- Go: go.mod (built-in testing)
- .NET: *.csproj (xunit, nunit, mstest)
- Fallback: tdd.test_runner.custom_command from config
```
### 4. Write Failing Tests
**Test Quality Guidelines:**
- **Deterministic**: No random values, dates, or network calls
- **Isolated**: Each test is independent and can run alone
- **Fast**: Unit tests should run in milliseconds
- **Readable**: Test names describe the behavior being tested
- **Focused**: One assertion per test when possible
**Mocking Strategy:**
```yaml
mock_vs_fake_vs_stub:
mock: 'Verify interactions (calls, parameters)'
fake: 'Simplified working implementation'
stub: 'Predefined responses to calls'
use_mocks_for:
- External APIs and web services
- Database connections
- File system operations
- Time-dependent operations
- Random number generation
```
**Test Structure (Given-When-Then):**
```typescript
// Example structure
describe('UserService', () => {
it('should create user with valid email', async () => {
// Given (Arrange)
const userData = { email: 'test@example.com', name: 'Test User' };
const mockDb = jest.fn().mockResolvedValue({ id: 1, ...userData });
// When (Act)
const result = await userService.create(userData);
// Then (Assert)
expect(result).toEqual({ id: 1, ...userData });
expect(mockDb).toHaveBeenCalledWith(userData);
});
});
```
### 5. Create Test Files
**Naming Conventions:**
```yaml
patterns:
javascript: '{module}.test.js' or '{module}.spec.js'
python: 'test_{module}.py' or '{module}_test.py'
java: '{Module}Test.java'
go: '{module}_test.go'
csharp: '{Module}Tests.cs'
```
**File Organization:**
```
tests/
├── unit/ # Fast, isolated tests
├── integration/ # Component interaction tests
└── e2e/ # End-to-end user journey tests
```
### 6. Verify Tests Fail
**Critical Step:** Run tests to ensure they fail for the RIGHT reason:
- ✅ Fail because functionality is not implemented
- ❌ Fail because of syntax errors, import issues, or test bugs
**Test Run Command:** Use auto-detected or configured test runner
### 7. Update Story Metadata
Update story file frontmatter:
```yaml
tdd:
status: red
cycle: 1
tests:
- id: 'UC-001'
name: 'should create user with valid email'
type: unit
status: failing
file_path: 'tests/unit/user-service.test.js'
- id: 'UC-002'
name: 'should reject user with invalid email'
type: unit
status: failing
file_path: 'tests/unit/user-service.test.js'
```
## Output Requirements
### 1. Test Files Created
Generate test files with:
- Clear, descriptive test names
- Proper setup/teardown
- Mock configurations
- Expected assertions
### 2. Test Execution Report
```bash
Running tests...
❌ UserService > should create user with valid email
❌ UserService > should reject user with invalid email
2 failing, 0 passing
```
### 3. Story File Updates
Append to TDD section:
```markdown
## TDD Progress
### Red Phase - Cycle 1
**Date:** {current_date}
**Agent:** Quinn (QA Agent)
**Tests Written:**
- UC-001: should create user with valid email (FAILING ✅)
- UC-002: should reject user with invalid email (FAILING ✅)
**Test Files:**
- tests/unit/user-service.test.js
**Next Step:** Dev Agent to implement minimal code to make tests pass
```
## Constraints & Best Practices
### Constraints
- **Minimal Scope:** Write tests for the smallest possible feature slice
- **No Implementation:** Do not implement the actual functionality
- **External Dependencies:** Always mock external services, databases, APIs
- **Deterministic Data:** Use fixed test data, mock time/random functions
- **Fast Execution:** Unit tests must complete quickly (< 100ms each)
### Anti-Patterns to Avoid
- Testing implementation details instead of behavior
- Writing tests after the code is written
- Complex test setup that obscures intent
- Tests that depend on external systems
- Overly broad tests covering multiple behaviors
## Error Handling
**If tests pass unexpectedly:**
- Implementation may already exist
- Test may be testing wrong behavior
- HALT and clarify requirements
**If tests fail for wrong reasons:**
- Fix syntax/import errors
- Verify mocks are properly configured
- Check test runner configuration
**If no test runner detected:**
- Fallback to tdd.test_runner.custom_command
- If not configured, prompt user for test command
- Document setup in story notes
## Completion Criteria
- [ ] All planned tests are written and failing
- [ ] Tests fail for correct reasons (missing implementation)
- [ ] Story TDD metadata updated with test list
- [ ] Test files follow project conventions
- [ ] All external dependencies are properly mocked
- [ ] Tests run deterministically and quickly
- [ ] Ready to hand off to Dev Agent for implementation
## Key Principles
- **Fail First:** Tests must fail before any implementation
- **Describe Behavior:** Tests define what "done" looks like
- **Start Small:** Begin with simplest happy path scenario
- **Isolate Dependencies:** External systems should be mocked
- **Fast Feedback:** Tests should run quickly to enable rapid iteration