Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:50:59 +08:00
commit cee24bf043
13 changed files with 3252 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
{
"name": "tdd-methodology-expert",
"description": "Expertise in Test-Driven Development (TDD) methodology.",
"version": "0.0.0-2025.11.28",
"author": {
"name": "Tim Green",
"email": "rawveg@gmail.com"
},
"skills": [
"./skills/tdd-methodology-expert"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# tdd-methodology-expert
Expertise in Test-Driven Development (TDD) methodology.

80
plugin.lock.json Normal file
View File

@@ -0,0 +1,80 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:rawveg/skillsforge-marketplace:tdd-methodology-expert",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "5a52968801ef6f67fa7f60a832a03e6a0cb6fc10",
"treeHash": "6c19bef81538ec852423a0a81b62980d53ec46902db6fe5d3ed0a03d30ae6ce8",
"generatedAt": "2025-11-28T10:27:54.021480Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "tdd-methodology-expert",
"description": "Expertise in Test-Driven Development (TDD) methodology."
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "677a00493327372480673719b0fcde810a801f4a9659c97b3ec6130cc5a0da89"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "0b87debb6e81f308b064334a8091d9fb3050677603f137435c5e1d5ed6837749"
},
{
"path": "skills/tdd-methodology-expert/plugin.json",
"sha256": "b07121a823cd01ab889c3481aea578214e0b74d160ede6098e3299d64170879a"
},
{
"path": "skills/tdd-methodology-expert/SKILL.md",
"sha256": "b68ce2e134064a47cf1204867e230a98884261e7469eb6cb76c0e4458dc5a41b"
},
{
"path": "skills/tdd-methodology-expert/references/code-smells.md",
"sha256": "336cbe45d1af325e0215b9719f3f8802ca1c9c1182fb72fb0c50cabb86951910"
},
{
"path": "skills/tdd-methodology-expert/references/testing-patterns.md",
"sha256": "934464f20b033d3a8c1e31bf78c36a91395989d38eec71a1d84bb9f604541873"
},
{
"path": "skills/tdd-methodology-expert/references/tdd-principles.md",
"sha256": "c4f62cc765bed8ffcab3c6955633e2c93efb01b26ab7185bcf08834afb56ccde"
},
{
"path": "skills/tdd-methodology-expert/scripts/check_tdd_compliance.py",
"sha256": "e07c8d44aeae0ab21926dbb169846ce92b44aeee3f8e213d473b6874185736a6"
},
{
"path": "skills/tdd-methodology-expert/scripts/setup_hooks.sh",
"sha256": "b1a06fd74b4fb489143113b8e6ec03f79ac0c3622510c6d0c5e1f3b0d726076a"
},
{
"path": "skills/tdd-methodology-expert/scripts/validate_tests.py",
"sha256": "4537e2e35d50a06d9c4920740c6e67ed656d6069eb8bc8e961257e661fd405f6"
},
{
"path": "skills/tdd-methodology-expert/assets/hook-templates/user-prompt-submit.sh",
"sha256": "1c0771eeaea8bee103459a1b328c5137fd72523547ef98757b278bcd3b484e3b"
},
{
"path": "skills/tdd-methodology-expert/assets/hook-templates/pre-commit.sh",
"sha256": "ff24e43515d626aa65f1c87c327f45ec524a5ba9638083f1937e69fcd2eac159"
}
],
"dirSha256": "6c19bef81538ec852423a0a81b62980d53ec46902db6fe5d3ed0a03d30ae6ce8"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}

View File

@@ -0,0 +1,737 @@
---
name: tdd-methodology-expert
description: Use proactively when you need to implement features or fix bugs using strict Test-Driven Development (TDD) methodology. This agent should be activated for any coding task that requires writing new functionality, refactoring existing code, or ensuring comprehensive test coverage, but should not be used for any design-related tasks. The agent excels at breaking down complex requirements into testable increments and maintaining high code quality through disciplined TDD cycles. Use this agent proactively or if the user mentions 'TDD', 'tdd' or 'Test Driven Development'.
---
# TDD Methodology Expert
Enforce and reinforce Test-Driven Development (TDD) methodology throughout the software development process. This skill provides comprehensive guidance, automated validation, and continuous reinforcement of the Red-Green-Refactor cycle.
## When to Use This Skill
This skill automatically activates when:
- TDD is mentioned in `CLAUDE.md` or `CLAUDE.local.md`
- TDD is referenced in project memory
- The user explicitly requests TDD methodology
- The user asks to write tests or implement features
Use this skill for:
- Implementing new features using TDD
- Refactoring existing code with test protection
- Fixing bugs with test-first approach
- Ensuring code quality through TDD discipline
- Validating that TDD principles are being followed
Do NOT use this skill for:
- Pure design discussions without implementation
- Documentation-only tasks
- Research or exploration tasks
## Core TDD Methodology
Test-Driven Development follows a strict three-phase cycle that must be repeated for every increment of functionality:
### 🔴 Red Phase: Write a Failing Test
**Always write the test before any production code.**
1. **Write a test** that expresses the desired behavior
2. **Run the test** and verify it fails (for the right reason)
3. **Confirm** the failure message indicates what's missing
**Red Phase Principles**:
- Test must be simple and focused on one behavior
- Test name should clearly describe expected behavior
- Test should be readable without looking at implementation
- Failure should be meaningful and guide implementation
**Example Flow**:
```
1. Write: test_should_calculate_total_with_tax()
2. Run: Test fails - "ShoppingCart has no attribute 'calculate_total'"
3. ✅ Ready for Green phase
```
### 🟢 Green Phase: Make the Test Pass
**Write minimal code to make the failing test pass.**
1. **Implement** the simplest code that makes the test pass
2. **Run the test** and verify it passes
3. **Run all tests** to ensure nothing broke
**Green Phase Principles**:
- Write only enough code to pass the current test
- Don't add features not required by tests
- It's okay to use shortcuts (you'll refactor later)
- Focus on making it work, not making it perfect
**Example Flow**:
```
1. Implement: def calculate_total(self, tax_rate): ...
2. Run: Test passes ✅
3. Run all: All tests pass ✅
4. ✅ Ready for Refactor phase
```
### 🔵 Refactor Phase: Improve the Code
**Clean up code while maintaining passing tests.**
1. **Identify** duplication, poor names, or structural issues
2. **Refactor** incrementally, running tests after each change
3. **Verify** all tests still pass
4. **Repeat** until code is clean
**Refactor Phase Principles**:
- Never refactor with failing tests
- Make small, safe changes
- Run tests after each refactoring step
- Improve both production code and test code
- Apply design patterns and best practices
**Example Flow**:
```
1. Identify: Duplicated tax calculation logic
2. Extract: Move to _calculate_tax() method
3. Run tests: All pass ✅
4. Improve: Better variable names
5. Run tests: All pass ✅
6. ✅ Commit and move to next feature
```
## TDD Workflow Integration
### Before Starting Any Code Task
**Step 1: Understand the Requirement**
- Break down the task into small, testable behaviors
- Identify the simplest test case to start with
- State which TDD phase you're entering (Red)
**Step 2: Plan the Test**
- Describe what test you're about to write
- Explain what behavior it will verify
- Confirm test will fail before implementation
### During Implementation
**Always follow this sequence**:
1. **🔴 Red**: Write failing test → Run → Verify failure
2. **🟢 Green**: Write minimal code → Run → Verify pass
3. **🔵 Refactor**: Improve code → Run → Verify still passes
4. **Commit**: Save working, tested, clean code
5. **Repeat**: Next test for next behavior
**Never skip phases or reverse the order.**
### Communicating TDD Progress
In every response involving code changes, explicitly state:
- **Current phase**: Which phase you're in (Red/Green/Refactor)
- **Test status**: Whether tests are passing or failing
- **Next steps**: What comes next in the cycle
**Example Communication**:
```
🔴 RED PHASE: Writing a test for calculating order total with discounts.
Test: test_should_apply_percentage_discount_to_order_total()
Expected to fail because Order.apply_discount() doesn't exist yet.
[Test code here]
Running test... ❌ Fails as expected: "Order has no attribute 'apply_discount'"
🟢 GREEN PHASE: Implementing minimal code to pass the test...
[Implementation code here]
Running test... ✅ Passes!
Running all tests... ✅ All pass!
🔵 REFACTOR PHASE: Improving the discount calculation structure...
[Refactored code here]
Running all tests... ✅ All pass!
Ready to commit this increment.
```
## Bundled Tools and Resources
### Scripts
#### check_tdd_compliance.py
Analyzes code to detect TDD compliance issues and code smells that indicate test-after development.
**Usage**:
```bash
python scripts/check_tdd_compliance.py <path-to-code>
```
**What it checks**:
- Nested conditionals (sign of poor TDD structure)
- Long methods (TDD produces small, focused methods)
- Complex boolean conditions (TDD encourages extraction)
- Missing abstractions (type checking vs polymorphism)
- Test coverage (presence of corresponding test files)
**When to use**:
- After completing a feature or module
- Before committing code
- When reviewing code quality
- During refactoring sessions
#### validate_tests.py
Validates that tests exist, are properly structured, and follow TDD patterns.
**Usage**:
```bash
python scripts/validate_tests.py <path-to-tests>
```
**What it checks**:
- Test file existence and structure
- Test case count and naming
- Arrange-Act-Assert pattern adherence
- Test size and complexity
- Descriptive test names
**When to use**:
- Before committing new tests
- When validating test quality
- During code review
- After writing a batch of tests
#### setup_hooks.sh
Installs git hooks and Claude Code hooks to enforce TDD methodology automatically.
**Usage**:
```bash
bash scripts/setup_hooks.sh <project-directory>
```
**What it installs**:
- Git pre-commit hook: Validates TDD compliance before commits
- Claude user-prompt-submit hook: Injects TDD reminders into every interaction
- Updates CLAUDE.md to document TDD requirement
**When to use**:
- Once at project initialization
- When onboarding new team members to TDD
- When setting up TDD enforcement for the first time
### References
Load these references when deeper understanding is needed:
#### tdd-principles.md
Comprehensive guide to TDD methodology including:
- The Red-Green-Refactor cycle in detail
- TDD philosophy and benefits
- Best practices and common mistakes
- TDD in different contexts (unit, integration, acceptance)
- Measuring TDD effectiveness
**When to reference**: When explaining TDD concepts or resolving questions about methodology.
#### code-smells.md
Catalog of code smells that indicate test-after development:
- High-severity smells (nested conditionals, long methods, god objects)
- Medium-severity smells (type checking, duplication, primitive obsession)
- Low-severity smells (magic numbers, long parameter lists)
- Detection strategies and refactoring guidance
**When to reference**: When analyzing code quality or identifying non-TDD patterns.
**Grep patterns for searching**:
- Nested conditionals: `if.*:\s*\n\s+if`
- Long methods: Count lines between function definitions
- Type checking: `isinstance\(|typeof `
- God classes: Count methods per class
#### testing-patterns.md
Language-agnostic testing patterns and best practices:
- Test structure patterns (AAA, Given-When-Then)
- Test organization (fixtures, builders, object mothers)
- Assertion patterns
- Test doubles (stubs, mocks, fakes)
- Parameterized testing
- Exception testing
- Test naming conventions
**When to reference**: When writing tests or improving test structure.
### Assets
#### Hook Templates
Located in `assets/hook-templates/`:
- **pre-commit.sh**: Git hook that runs TDD compliance checks before allowing commits
- **user-prompt-submit.sh**: Claude Code hook that injects TDD reminders before every user prompt
These templates are used by `setup_hooks.sh` and can be customized for specific project needs.
## Prompt-Based Validation
Users can invoke TDD validation scripts through natural language prompts instead of relying on git hooks. This provides flexibility for developers who prefer manual commits and on-demand validation.
### Recognizing Validation Requests
When the user requests TDD validation using phrases like:
- "Check the code for TDD compliance"
- "Validate TDD adherence"
- "Run TDD compliance check"
- "Check if this code follows TDD"
- "Analyze code for TDD smells"
- "Check code quality"
- "Run code smell detection"
**Immediately respond by**:
1. **Identify target code**: Determine which files or directories to analyze
- If user specifies files/directories, use those
- If not specified, ask for clarification or analyze recently modified files
- Use `git diff --name-only` to find recently changed files if appropriate
2. **Run compliance check**: Execute `check_tdd_compliance.py` on the target code
```bash
python scripts/check_tdd_compliance.py <path-to-code>
```
3. **Parse and present results**:
- Report the TDD compliance score (0-100)
- List any code smells found with severity levels
- Highlight specific issues in the code
- Provide actionable recommendations for improvement
4. **Validate tests** (if applicable): Run `validate_tests.py` on corresponding test files
```bash
python scripts/validate_tests.py <path-to-tests>
```
5. **Provide context-aware guidance**:
- Reference specific code smells from `references/code-smells.md`
- Suggest refactoring patterns from `references/testing-patterns.md`
- Offer to refactor problematic code sections
### Example Response Flow
**User**: "Check the code for TDD compliance"
**Claude Response**:
```
I'll analyze the code for TDD compliance. Let me identify the recently changed files and run the compliance check.
[Runs check_tdd_compliance.py on relevant files]
📊 TDD COMPLIANCE REPORT
File: src/order_processor.py
TDD Score: 65/100 (Needs Improvement)
Issues Found:
❌ High Severity (3 issues):
- Line 45-68: Deeply nested conditionals (4 levels deep)
- Line 112-145: Long method 'process_order' (34 lines)
- Line 78-82: Complex boolean condition (5 logical operators)
⚠️ Medium Severity (2 issues):
- Line 156: Type checking instead of polymorphism (isinstance)
- Line 203-215: Duplicate code block
Recommendations:
1. Extract nested conditionals into separate validation methods
2. Break down 'process_order' into smaller, focused methods
3. Extract complex boolean into named predicate method
4. Use polymorphism instead of type checking
5. Extract duplicate logic into reusable function
Would you like me to refactor any of these issues?
```
### When NOT to Run Validation
Do not automatically run validation scripts unless:
- User explicitly requests validation/compliance check
- User is about to commit and asks for pre-commit checks
- User asks about code quality or TDD adherence
Avoid running validation:
- During normal TDD cycles (Red-Green-Refactor)
- When writing initial test code
- During exploratory coding or prototyping
- When user hasn't requested it
### Manual vs. Automated Validation
**Manual (Prompt-Based)**:
- User has full control over when checks run
- Can validate specific files or directories
- Immediate feedback and context-aware guidance
- No interruption to commit workflow
- Preferred for developers who commit manually
**Automated (Git Hooks)**:
- Runs automatically before commits (if hooks installed)
- Ensures validation happens consistently
- Blocks commits with poor TDD compliance
- Can be bypassed with `--no-verify` flag
- Optional setup via `setup_hooks.sh`
Both approaches are valid. Users can choose the workflow that fits their preferences.
## Prompt-Based Setup
Users can request TDD setup for their project through natural language prompts. This provides a guided setup experience that respects user preferences.
### Recognizing Setup Requests
When the user requests TDD setup using phrases like:
- "Setup this project for TDD"
- "Configure TDD for this project"
- "Initialize TDD development"
- "Set up TDD methodology"
- "Enable TDD for this codebase"
- "Install TDD tools"
**Immediately respond by**:
1. **Confirm the request**: Acknowledge that you'll set up TDD for the project
2. **Update CLAUDE.md**: Always add or update the TDD requirement
```markdown
# Development Guidelines
## Test-Driven Development (TDD)
This project follows strict Test-Driven Development methodology:
- Write tests before production code (Red-Green-Refactor cycle)
- All features must have corresponding unit tests
- Code quality is validated through TDD compliance checks
Use the `tdd-methodology-expert` skill for TDD guidance and validation.
```
3. **Install Claude Code hook**: Always install the user-prompt-submit hook
- Copy `assets/hook-templates/user-prompt-submit.sh` to `.claude/hooks/user-prompt-submit`
- Make executable: `chmod +x .claude/hooks/user-prompt-submit`
- This injects TDD reminders into every conversation
4. **Offer pre-commit hook (optional)**: Proactively ask about git hook installation
**Prompt the user**:
```
✅ TDD Setup Progress:
- ✓ Updated CLAUDE.md with TDD requirements
- ✓ Installed Claude Code hook for TDD reinforcement
📋 Optional Enhancement:
Would you like to install the git pre-commit hook for automated TDD compliance checking?
Benefits:
✅ Automatically validates TDD compliance before each commit
✅ Prevents commits with poor code quality (blocking enforcement)
✅ Provides immediate feedback on code smells
Trade-offs:
⚠️ Adds ~2-5 seconds to commit time
⚠️ Blocks commits that fail TDD compliance (can bypass with --no-verify)
⚠️ Some developers prefer manual validation
You can always validate manually by saying "Check the code for TDD compliance"
Install pre-commit hook? (yes/no)
```
5. **Install pre-commit hook (if user agrees)**:
- Copy `assets/hook-templates/pre-commit.sh` to `.git/hooks/pre-commit`
- Make executable: `chmod +x .git/hooks/pre-commit`
- Confirm installation
6. **Provide completion summary**:
```
✅ TDD Setup Complete!
What was installed:
- ✓ CLAUDE.md updated with TDD requirements
- ✓ Claude Code hook (user-prompt-submit)
- ✓ Git pre-commit hook (if user opted in)
Available validation scripts:
- check_tdd_compliance.py - Analyze code for TDD compliance
- validate_tests.py - Validate test quality and structure
How to use:
- Say "Check the code for TDD compliance" anytime
- All code development will follow Red-Green-Refactor cycle
- I'll provide TDD guidance and validation throughout
Ready to start TDD development!
```
### Example Setup Flow
**User**: "Please setup this project for TDD development"
**Claude**:
```
I'll set up TDD for your project. Let me configure the necessary files and hooks.
[Updates CLAUDE.md with TDD requirements]
[Installs Claude Code hook]
✅ TDD Setup Progress:
- ✓ Updated CLAUDE.md with TDD requirements
- ✓ Installed Claude Code hook for TDD reinforcement
📋 Optional Enhancement:
Would you like to install the git pre-commit hook for automated TDD compliance checking?
Benefits:
✅ Automatically validates TDD compliance before each commit
✅ Prevents commits with poor code quality
✅ Provides immediate feedback on code smells
Trade-offs:
⚠️ Adds ~2-5 seconds to commit time
⚠️ Blocks commits that fail TDD compliance (bypass with --no-verify)
⚠️ Some developers prefer manual validation
You can always validate manually by saying "Check the code for TDD compliance"
Install pre-commit hook? (yes/no)
```
**User**: "no thanks, I prefer manual validation"
**Claude**:
```
✅ TDD Setup Complete!
What was installed:
- ✓ CLAUDE.md updated with TDD requirements
- ✓ Claude Code hook (user-prompt-submit)
- ✗ Git pre-commit hook (skipped per user preference)
Available validation scripts:
- check_tdd_compliance.py - Analyze code for TDD compliance
- validate_tests.py - Validate test quality and structure
How to use:
- Say "Check the code for TDD compliance" anytime for manual validation
- All code development will follow Red-Green-Refactor cycle
- I'll provide TDD guidance throughout
Ready to start TDD development!
```
### Setup Components
**Always Install**:
- CLAUDE.md update (TDD documentation)
- Claude Code user-prompt-submit hook (TDD reminders)
**Optional Install** (ask user):
- Git pre-commit hook (automated validation)
**Never Install Without Asking**:
- Git hooks that modify user's commit workflow
### Handling Existing Setup
If TDD is already configured:
- Check if CLAUDE.md mentions TDD
- Check if `.claude/hooks/user-prompt-submit` exists
- Check if `.git/hooks/pre-commit` exists
- Report current status and offer to update or reinstall
## TDD Enforcement Rules
### Mandatory Requirements
1. **Tests must be written before production code**
- Never implement features without failing tests first
- Never fix bugs without reproducing them in a test first
2. **All tests must pass before committing**
- Red phase: Don't commit
- Green phase: Can commit, but prefer to refactor first
- Refactor phase: Commit here with clean, tested code
3. **Refactoring is not optional**
- Every cycle must include the refactor phase
- Code quality must continuously improve
- Technical debt must be addressed immediately
4. **Tests must follow TDD patterns**
- Descriptive, behavior-focused names
- Arrange-Act-Assert structure
- Single responsibility per test
- Independent and isolated
### Validation and Feedback
**Continuously validate TDD adherence by**:
1. **Checking code structure**:
- Run `check_tdd_compliance.py` on changed files
- Report TDD score and any code smells found
- Provide specific guidance on improvements
2. **Verifying test quality**:
- Run `validate_tests.py` on test files
- Ensure tests follow patterns from `testing-patterns.md`
- Check for descriptive names and proper structure
3. **Monitoring the cycle**:
- Confirm each phase is completed in order
- Verify tests fail before implementation
- Ensure tests pass after implementation
- Validate refactoring maintains passing tests
### User Reassurance
Throughout the TDD process, continuously reassure the user that:
- ✅ TDD methodology is being strictly followed
- ✅ Red-Green-Refactor cycle is being respected
- ✅ Tests are written before code
- ✅ Code quality is being maintained
- ✅ Each phase is completed properly
**Example Reassurance**:
```
✅ TDD COMPLIANCE: Following strict Red-Green-Refactor methodology.
Current status:
- 🔴 Red: Test written and confirmed failing
- 🟢 Green: Implementation passes test
- 🔵 Refactor: Code cleaned up, all tests pass
- TDD Score: 95/100 (Excellent)
The code exhibits TDD characteristics:
✓ Small, focused functions (8-12 lines)
✓ Flat control flow (no deep nesting)
✓ Clear separation of concerns
✓ High testability
Ready to proceed with next feature.
```
## Common TDD Scenarios
### Implementing a New Feature
**Process**:
1. Break feature into small, testable behaviors
2. For each behavior:
- 🔴 Write failing test
- 🟢 Implement minimal code
- 🔵 Refactor for quality
3. Validate with `check_tdd_compliance.py`
4. Commit clean, tested code
### Fixing a Bug
**Process**:
1. 🔴 Write a test that reproduces the bug (should fail)
2. Confirm test fails with current code
3. 🟢 Fix the bug (test should now pass)
4. 🔵 Refactor if needed
5. Validate no new code smells introduced
6. Commit fix with test
### Refactoring Existing Code
**Process**:
1. Ensure existing tests pass (or add characterization tests)
2. For each refactoring increment:
- 🔵 Make small structural improvement
- Run tests to verify behavior unchanged
- Repeat until code is clean
3. Validate improvement with compliance check
4. Commit refactored code
### Adding Tests to Legacy Code
**Process**:
1. Identify functionality to test
2. Write characterization tests (document current behavior)
3. Use tests to enable safe refactoring
4. Gradually improve structure following TDD
5. Future changes follow strict TDD
## Integration with Development Workflow
### IDE Integration
Tests should be:
- Easy to run (one command or hotkey)
- Fast to execute (milliseconds for unit tests)
- Clearly reported (pass/fail with details)
### Continuous Integration
Every commit should:
- Run all tests automatically
- Report TDD compliance metrics
- Block merge if tests fail
- Track code quality trends
### Code Review
Reviewers should verify:
- Tests exist for all changes
- Tests follow TDD patterns
- Code exhibits TDD structure (small, flat, clean)
- Red-Green-Refactor was followed
## Troubleshooting TDD Issues
### "Tests are taking too long to write"
**Solution**: Tests are probably too big. Break into smaller behaviors.
### "Can't figure out what to test first"
**Solution**: Test the simplest case. Start with happy path, then edge cases.
### "Tests keep breaking during refactoring"
**Solution**: Tests are testing implementation, not behavior. Refactor tests too.
### "Code is getting messy"
**Solution**: Not refactoring enough. Spend more time in refactor phase.
### "Tests are hard to write"
**Solution**: Code design is poor. Let test difficulty guide design improvements.
## Summary: TDD Mindset
When using this skill, always remember:
1. **Tests First**: Never write production code without a failing test
2. **Small Steps**: Keep Red-Green-Refactor cycles very short (2-10 minutes)
3. **Refactor Always**: Code quality is not optional
4. **Trust the Process**: TDD produces better design through discipline
5. **Verify Compliance**: Use bundled tools to validate adherence
**The cycle is sacred: 🔴 Red → 🟢 Green → 🔵 Refactor**
Never skip phases. Never reverse the order. Always complete the cycle.
TDD is not just about testing - it's a design methodology that produces higher-quality, more maintainable code through disciplined practice.

View File

@@ -0,0 +1,107 @@
#!/bin/bash
#
# Git Pre-Commit Hook for TDD Enforcement
#
# This hook runs before each git commit to ensure TDD principles are followed.
# It validates that tests exist and checks for code smells that indicate
# tests-after-code development.
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${BLUE}🔍 Running TDD compliance check...${NC}"
# Find the skill directory (assuming it's in a standard location)
# You may need to adjust this path based on where the skill is installed
SKILL_SCRIPT=""
if [ -f "$HOME/.claude/skills/tdd-methodology-expert/scripts/check_tdd_compliance.py" ]; then
SKILL_SCRIPT="$HOME/.claude/skills/tdd-methodology-expert/scripts/check_tdd_compliance.py"
elif [ -f "./.claude/skills/tdd-methodology-expert/scripts/check_tdd_compliance.py" ]; then
SKILL_SCRIPT="./.claude/skills/tdd-methodology-expert/scripts/check_tdd_compliance.py"
fi
if [ -z "$SKILL_SCRIPT" ]; then
echo -e "${YELLOW}⚠️ TDD compliance script not found. Skipping check.${NC}"
exit 0
fi
# Get list of staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
if [ -z "$STAGED_FILES" ]; then
echo -e "${GREEN}✅ No files to check${NC}"
exit 0
fi
# Create temporary directory for checking
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# Copy staged files to temp directory
for FILE in $STAGED_FILES; do
if [ -f "$FILE" ]; then
mkdir -p "$TEMP_DIR/$(dirname "$FILE")"
git show ":$FILE" > "$TEMP_DIR/$FILE"
fi
done
# Run TDD compliance check on staged files
echo -e "${BLUE}Analyzing staged files for TDD compliance...${NC}"
OUTPUT=$(python3 "$SKILL_SCRIPT" "$TEMP_DIR" 2>&1)
EXIT_CODE=$?
# Parse results
COMPLIANCE_LEVEL=$(echo "$OUTPUT" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['compliance'])" 2>/dev/null || echo "unknown")
TDD_SCORE=$(echo "$OUTPUT" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['metrics']['tdd_score'])" 2>/dev/null || echo "0")
CODE_SMELLS=$(echo "$OUTPUT" | python3 -c "import sys, json; data=json.load(sys.stdin); print(data['metrics']['code_smells'])" 2>/dev/null || echo "0")
echo ""
echo "📊 TDD Compliance Report:"
echo " Score: $TDD_SCORE/100"
echo " Level: $COMPLIANCE_LEVEL"
echo " Code Smells: $CODE_SMELLS"
echo ""
if [ "$EXIT_CODE" -ne 0 ]; then
echo -e "${RED}❌ TDD compliance check failed!${NC}"
echo ""
echo "Issues found:"
echo "$OUTPUT" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
for issue in data['issues'][:5]: # Show first 5 issues
severity = issue.get('severity', 'unknown')
msg = issue.get('message', 'Unknown issue')
file = issue.get('file', '')
line = issue.get('line', '')
location = f'{file}:{line}' if line else file
print(f' • [{severity.upper()}] {location}')
print(f' {msg}')
print()
except:
pass
" 2>/dev/null
echo ""
echo -e "${YELLOW}💡 TDD Reminder:${NC}"
echo " 1. 🔴 Write a failing test first"
echo " 2. 🟢 Write minimal code to pass the test"
echo " 3. 🔵 Refactor while keeping tests green"
echo ""
echo "To commit anyway, use: git commit --no-verify"
echo ""
exit 1
fi
echo -e "${GREEN}✅ TDD compliance check passed!${NC}"
echo ""
exit 0

View File

@@ -0,0 +1,72 @@
#!/bin/bash
#
# Claude Code User-Prompt-Submit Hook for TDD Reinforcement
#
# This hook runs before every user prompt is submitted to Claude,
# injecting TDD methodology reminders and context into the conversation.
# Colors for output (for debugging)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Get the user's original prompt from stdin
USER_PROMPT=$(cat)
# Build TDD reinforcement message
TDD_REMINDER="
🔴🟢🔵 TDD Methodology Reminder:
This project follows Test-Driven Development (TDD). Every code change must follow the Red-Green-Refactor cycle:
1. 🔴 RED Phase: Write a failing test first
- Write the test before any production code
- Test should fail because the feature doesn't exist yet
- Test should clearly express the desired behavior
2. 🟢 GREEN Phase: Make the test pass
- Write the minimal code needed to pass the test
- Don't add extra features or abstractions yet
- Focus on making the test pass as quickly as possible
3. 🔵 REFACTOR Phase: Improve the code
- Clean up the code while keeping tests green
- Remove duplication
- Improve names and structure
- Ensure code is maintainable and readable
Code developed with TDD exhibits these characteristics:
✅ Small, focused functions/methods (typically <20 lines)
✅ Flat control flow (minimal nesting, no deep if/elif/else chains)
✅ Clear separation of concerns
✅ High testability and mockability
✅ Simple, straightforward logic
Code developed test-after (NOT TDD) often shows:
❌ Long, complex methods
❌ Deeply nested conditionals
❌ Complex boolean expressions
❌ Type checking instead of polymorphism
❌ Tight coupling and poor abstractions
When responding to this request:
• Explicitly state which TDD phase you're in (Red, Green, or Refactor)
• Write tests BEFORE any production code
• Verify tests fail before implementing features (Red phase)
• Implement minimal code to pass tests (Green phase)
• Refactor only after tests are passing (Refactor phase)
• Validate TDD compliance using the check_tdd_compliance.py script when appropriate
• Reassure the user that TDD principles are being followed
User's original request:
$USER_PROMPT
"
# Output the augmented prompt
echo "$TDD_REMINDER"
# Exit successfully
exit 0

View File

@@ -0,0 +1,15 @@
{
"name": "tdd-methodology-expert",
"description": "Provides TDD methodology expert integration for Claude Code.",
"version": "1.0.0",
"author": {
"name": "Tim Green",
"email": "rawveg@gmail.com"
},
"homepage": "https://github.com/rawveg/claude-skills-marketplace",
"repository": "https://github.com/rawveg/claude-skills-marketplace",
"license": "MIT",
"keywords": ["tdd", "tdd-methodology-expert", "coding standards", "code quality", "Claude Code"],
"category": "productivity",
"strict": false
}

View File

@@ -0,0 +1,532 @@
# Code Smells That Indicate Test-After Development
This document catalogs code smells and anti-patterns that strongly suggest tests were written after implementation rather than following TDD methodology.
## Understanding Code Smells in TDD Context
When developers write tests after code (test-after), they tend to produce different code structures than when following TDD. This is because:
1. **TDD enforces small steps**: Each test drives minimal implementation
2. **TDD encourages refactoring**: The refactor phase continuously improves structure
3. **TDD requires testability**: Code must be designed for easy testing from the start
4. **TDD prevents over-engineering**: Only write code needed to pass tests
Test-after code often shows signs of:
- Solving problems that don't exist yet (premature optimization)
- Complex structures built all at once (big bang implementation)
- Difficult-to-test designs (retrofitted testability)
- Accumulated technical debt (skipped refactoring)
## High-Severity Code Smells
### 1. Deeply Nested Conditionals
**Description**: Multiple levels of if/elif/else statements nested within each other.
**Why it indicates test-after**:
- TDD would break this down into separate, testable functions
- Each branch would have its own test, encouraging extraction
- Refactor phase would identify and eliminate deep nesting
**Example (Bad)**:
```python
def process_order(order):
if order.customer:
if order.customer.is_active:
if order.items:
if order.total > 0:
if order.payment_method:
if order.payment_method == "credit_card":
if order.customer.credit_limit >= order.total:
# Process credit card payment
return "processed"
else:
return "insufficient_credit"
else:
# Process other payment
return "processed"
else:
return "no_payment_method"
else:
return "invalid_total"
else:
return "no_items"
else:
return "inactive_customer"
else:
return "no_customer"
```
**TDD Alternative**:
```python
def process_order(order):
_validate_order(order)
_validate_customer(order.customer)
_validate_payment(order)
return _execute_payment(order)
def _validate_order(order):
if not order.items:
raise OrderValidationError("Order must have items")
if order.total <= 0:
raise OrderValidationError("Order total must be positive")
def _validate_customer(customer):
if not customer:
raise OrderValidationError("Order must have a customer")
if not customer.is_active:
raise OrderValidationError("Customer is inactive")
def _validate_payment(order):
if not order.payment_method:
raise OrderValidationError("Payment method required")
def _execute_payment(order):
payment_processor = PaymentProcessorFactory.create(order.payment_method)
return payment_processor.process(order)
```
**How to detect**: Look for 3+ levels of nested if/else statements.
### 2. Long Methods/Functions
**Description**: Methods exceeding 20-30 lines of code.
**Why it indicates test-after**:
- TDD naturally produces small functions (5-15 lines)
- Each test typically drives one small piece of functionality
- Long methods suggest big-bang implementation
**Example (Bad)**:
```python
def generate_invoice(order_id):
# 80+ lines of mixed responsibilities:
# - Database queries
# - Business logic
# - Calculations
# - Formatting
# - File generation
# - Email sending
order = db.query(Order).filter_by(id=order_id).first()
if not order:
return None
total = 0
for item in order.items:
if item.discount:
price = item.price * (1 - item.discount)
else:
price = item.price
total += price * item.quantity
tax = total * 0.1
shipping = 10 if total < 50 else 0
grand_total = total + tax + shipping
# ... 50 more lines of formatting and sending
```
**TDD Alternative**:
```python
def generate_invoice(order_id):
order = _fetch_order(order_id)
invoice_data = _calculate_invoice_totals(order)
formatted_invoice = _format_invoice(order, invoice_data)
_send_invoice(order.customer.email, formatted_invoice)
return formatted_invoice
def _fetch_order(order_id):
order = db.query(Order).filter_by(id=order_id).first()
if not order:
raise OrderNotFoundError(f"Order {order_id} not found")
return order
def _calculate_invoice_totals(order):
subtotal = sum(_calculate_line_total(item) for item in order.items)
tax = _calculate_tax(subtotal)
shipping = _calculate_shipping(subtotal)
return InvoiceTotals(subtotal, tax, shipping)
def _calculate_line_total(item):
price = item.price * (1 - item.discount) if item.discount else item.price
return price * item.quantity
```
**How to detect**: Count lines in methods. Flag anything over 20 lines.
### 3. Complex Boolean Conditions
**Description**: Conditional expressions with multiple AND/OR operators.
**Why it indicates test-after**:
- TDD encourages extracting complex conditions into named methods
- Each condition part would have its own test
- Refactor phase would identify complexity and extract it
**Example (Bad)**:
```python
if (user.age >= 18 and user.has_license and
user.years_experience >= 2 and
(user.state == "CA" or user.state == "NY") and
not user.has_violations and user.insurance_valid):
# Allow to rent car
pass
```
**TDD Alternative**:
```python
def can_rent_car(user):
return (is_eligible_driver(user) and
is_in_service_area(user) and
has_clean_record(user))
def is_eligible_driver(user):
return user.age >= 18 and user.has_license and user.years_experience >= 2
def is_in_service_area(user):
return user.state in ["CA", "NY"]
def has_clean_record(user):
return not user.has_violations and user.insurance_valid
```
**How to detect**: Count AND/OR operators. Flag conditions with 3+ logical operators.
### 4. God Objects/Classes
**Description**: Classes with too many responsibilities and methods.
**Why it indicates test-after**:
- TDD enforces Single Responsibility Principle through testing
- Each test focuses on one behavior, encouraging focused classes
- Testing god objects is painful, encouraging decomposition
**Example (Bad)**:
```python
class UserManager:
def authenticate(self, username, password): pass
def create_user(self, user_data): pass
def update_user(self, user_id, data): pass
def delete_user(self, user_id): pass
def send_welcome_email(self, user): pass
def send_password_reset(self, user): pass
def validate_email(self, email): pass
def validate_password(self, password): pass
def log_user_activity(self, user, action): pass
def generate_report(self, user_id): pass
def export_user_data(self, user_id): pass
def import_users(self, file_path): pass
# ... 20 more methods
```
**TDD Alternative**:
```python
class AuthenticationService:
def authenticate(self, username, password): pass
class UserRepository:
def create(self, user): pass
def update(self, user_id, data): pass
def delete(self, user_id): pass
def find_by_id(self, user_id): pass
class EmailService:
def send_welcome_email(self, user): pass
def send_password_reset(self, user): pass
class UserValidator:
def validate_email(self, email): pass
def validate_password(self, password): pass
class UserReportGenerator:
def generate_report(self, user_id): pass
```
**How to detect**: Count methods in class. Flag classes with 10+ methods.
## Medium-Severity Code Smells
### 5. Type Checking Instead of Polymorphism
**Description**: Using `isinstance()`, `typeof`, or type switches instead of polymorphic design.
**Why it indicates test-after**:
- TDD encourages interface-based design through mocking
- Polymorphism emerges naturally when testing behaviors
- Type checking makes testing harder, encouraging better design
**Example (Bad)**:
```python
def calculate_area(shape):
if isinstance(shape, Circle):
return 3.14159 * shape.radius ** 2
elif isinstance(shape, Rectangle):
return shape.width * shape.height
elif isinstance(shape, Triangle):
return 0.5 * shape.base * shape.height
else:
raise ValueError("Unknown shape type")
```
**TDD Alternative**:
```python
class Shape(ABC):
@abstractmethod
def calculate_area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def calculate_area(self):
return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
# Usage:
def process_shape(shape: Shape):
return shape.calculate_area()
```
**How to detect**: Search for `isinstance()`, `typeof`, or type switch patterns.
### 6. Duplicate Code Blocks
**Description**: Same or similar code repeated in multiple places.
**Why it indicates test-after**:
- TDD's refactor phase explicitly targets duplication
- Each cycle includes time to eliminate redundancy
- Test-after often skips refactoring altogether
**Example (Bad)**:
```python
def calculate_discount_price_for_books(price, quantity):
if quantity >= 10:
discount = 0.2
elif quantity >= 5:
discount = 0.1
else:
discount = 0
return price * (1 - discount)
def calculate_discount_price_for_electronics(price, quantity):
if quantity >= 10:
discount = 0.15
elif quantity >= 5:
discount = 0.08
else:
discount = 0
return price * (1 - discount)
```
**TDD Alternative**:
```python
def calculate_discount_price(price, quantity, discount_tiers):
discount = _get_discount_for_quantity(quantity, discount_tiers)
return price * (1 - discount)
def _get_discount_for_quantity(quantity, tiers):
for min_qty, discount in sorted(tiers.items(), reverse=True):
if quantity >= min_qty:
return discount
return 0
# Usage:
BOOK_DISCOUNTS = {10: 0.2, 5: 0.1}
ELECTRONICS_DISCOUNTS = {10: 0.15, 5: 0.08}
book_price = calculate_discount_price(29.99, 12, BOOK_DISCOUNTS)
```
**How to detect**: Use code duplication analysis tools (>6 lines duplicated).
### 7. Primitive Obsession
**Description**: Using primitive types instead of small objects to represent concepts.
**Why it indicates test-after**:
- TDD encourages creating types that make tests clearer
- Value objects emerge naturally when expressing test intent
- Primitives make tests verbose and unclear
**Example (Bad)**:
```python
def create_appointment(patient_id, doctor_id, date_str, time_str, duration_mins):
# Working with primitives throughout
date = datetime.strptime(date_str, "%Y-%m-%d")
time = datetime.strptime(time_str, "%H:%M")
# ... complex validation and manipulation
```
**TDD Alternative**:
```python
@dataclass
class AppointmentTime:
date: datetime.date
time: datetime.time
duration: timedelta
def __post_init__(self):
if self.duration <= timedelta(0):
raise ValueError("Duration must be positive")
def end_time(self):
start = datetime.combine(self.date, self.time)
return start + self.duration
def create_appointment(patient_id, doctor_id, appointment_time: AppointmentTime):
# Working with rich domain objects
pass
```
**How to detect**: Look for functions with many primitive parameters (4+).
### 8. Comments Explaining What Code Does
**Description**: Comments that explain the mechanics of the code rather than the "why".
**Why it indicates test-after**:
- TDD produces self-documenting code through clear naming
- Tests serve as documentation for behavior
- Need for "what" comments suggests unclear code
**Example (Bad)**:
```python
def process(data):
# Loop through each item in data
for item in data:
# Check if item value is greater than 100
if item.value > 100:
# Multiply value by 1.5
item.value = item.value * 1.5
# Check if item is active
if item.is_active:
# Add item to results list
results.append(item)
```
**TDD Alternative**:
```python
def process_high_value_active_items(items):
return [apply_premium_pricing(item)
for item in items
if is_premium_eligible(item)]
def is_premium_eligible(item):
return item.value > 100 and item.is_active
def apply_premium_pricing(item):
item.value *= PREMIUM_MULTIPLIER
return item
```
**How to detect**: Look for comments explaining mechanics; good comments explain "why".
## Low-Severity Code Smells
### 9. Magic Numbers
**Description**: Unexplained numeric literals scattered throughout code.
**Example (Bad)**:
```python
def calculate_shipping(weight):
if weight < 5:
return 10
elif weight < 20:
return 25
else:
return 50
```
**TDD Alternative**:
```python
LIGHT_PACKAGE_THRESHOLD = 5
MEDIUM_PACKAGE_THRESHOLD = 20
LIGHT_PACKAGE_RATE = 10
MEDIUM_PACKAGE_RATE = 25
HEAVY_PACKAGE_RATE = 50
def calculate_shipping(weight):
if weight < LIGHT_PACKAGE_THRESHOLD:
return LIGHT_PACKAGE_RATE
elif weight < MEDIUM_PACKAGE_THRESHOLD:
return MEDIUM_PACKAGE_RATE
else:
return HEAVY_PACKAGE_RATE
```
### 10. Long Parameter Lists
**Description**: Methods accepting many parameters (4+).
**Example (Bad)**:
```python
def create_user(first_name, last_name, email, phone, address, city, state, zip, country):
pass
```
**TDD Alternative**:
```python
@dataclass
class UserProfile:
first_name: str
last_name: str
email: str
phone: str
@dataclass
class Address:
street: str
city: str
state: str
zip: str
country: str
def create_user(profile: UserProfile, address: Address):
pass
```
## Detection Strategy
### Automated Checks
Run these checks regularly to identify test-after patterns:
1. **Cyclomatic Complexity**: Flag methods with complexity > 10
2. **Method Length**: Flag methods > 20 lines
3. **Class Size**: Flag classes with > 10 methods
4. **Nesting Depth**: Flag code with > 3 levels of nesting
5. **Duplication**: Flag blocks of > 6 duplicated lines
6. **Parameter Count**: Flag methods with > 4 parameters
### Manual Review
Look for these patterns during code review:
1. Large commits with code and tests together
2. Tests that test implementation rather than behavior
3. Absence of refactoring commits
4. Complex code without corresponding complex tests
5. Tests that mock internal methods
## Refactoring from Test-After to TDD
If you inherit test-after code:
1. **Add characterization tests**: Cover existing behavior
2. **Identify smells**: Use automated and manual detection
3. **Extract methods**: Break down large methods
4. **Introduce types**: Replace primitives with value objects
5. **Apply patterns**: Use polymorphism, strategy, etc.
6. **Write tests first for new features**: Start TDD from now
Remember: The goal isn't perfect code, but continuously improving code quality through TDD discipline.

View File

@@ -0,0 +1,391 @@
# Test-Driven Development (TDD) Principles
This document provides comprehensive guidance on TDD methodology, the Red-Green-Refactor cycle, and how to apply TDD effectively across different programming contexts.
## Core Philosophy
Test-Driven Development is a software development methodology where tests are written before the production code. The fundamental principle is: **Write failing tests first, then write code to make them pass.**
### Why TDD?
1. **Design Pressure**: Writing tests first forces you to think about the API design before implementation
2. **Regression Safety**: Every feature is protected by tests from the moment it's created
3. **Living Documentation**: Tests serve as executable examples of how the code should be used
4. **Confidence to Refactor**: Comprehensive test coverage enables safe code improvements
5. **Better Code Structure**: TDD naturally produces more modular, testable, and maintainable code
## The Red-Green-Refactor Cycle
TDD follows a strict three-phase cycle that must be repeated for every small piece of functionality:
### 🔴 Red Phase: Write a Failing Test
**Purpose**: Define the desired behavior before implementation exists.
**Process**:
1. Write a test that expresses the desired behavior
2. Run the test and verify it fails (it must fail for the right reason)
3. The failure confirms the test is actually testing something
**Key Principles**:
- Write the simplest test that expresses one behavior
- Test should be readable and clearly express intent
- Focus on behavior, not implementation
- Use descriptive test names (e.g., `test_should_return_empty_list_when_no_items_match`)
**Example (Python)**:
```python
# Red Phase: Test written first, will fail
def test_should_calculate_total_price_with_tax():
cart = ShoppingCart()
cart.add_item(Item("Book", price=10.00))
total = cart.calculate_total(tax_rate=0.1)
assert total == 11.00 # This will fail - method doesn't exist yet
```
**Red Phase Checklist**:
- [ ] Test clearly expresses desired behavior?
- [ ] Test name is descriptive and behavior-focused?
- [ ] Test actually fails when run?
- [ ] Failure message is clear about what's missing?
### 🟢 Green Phase: Make the Test Pass
**Purpose**: Write the minimal code necessary to make the failing test pass.
**Process**:
1. Write just enough production code to make the test pass
2. Take shortcuts if needed (you'll refactor later)
3. Run the test and verify it passes
4. Don't add extra features or abstractions yet
**Key Principles**:
- Write the simplest implementation that makes the test pass
- Don't worry about code quality yet (that's the refactor phase)
- Resist the urge to add features not tested
- It's okay to hard-code values initially (refactor will generalize)
**Example (Python)**:
```python
# Green Phase: Minimal implementation to pass the test
class ShoppingCart:
def __init__(self):
self.items = []
def add_item(self, item):
self.items.append(item)
def calculate_total(self, tax_rate):
# Minimal implementation - just make it work
subtotal = sum(item.price for item in self.items)
return subtotal * (1 + tax_rate)
class Item:
def __init__(self, name, price):
self.name = name
self.price = price
```
**Green Phase Checklist**:
- [ ] Test now passes?
- [ ] Only added code necessary to pass the test?
- [ ] No extra features or premature optimization?
- [ ] All previously passing tests still pass?
### 🔵 Refactor Phase: Improve the Code
**Purpose**: Improve code quality while maintaining passing tests.
**Process**:
1. Look for duplication, poor names, or structural issues
2. Refactor the code to improve quality
3. Run tests after each small change to ensure they still pass
4. Continue until code is clean and maintainable
**Key Principles**:
- Never refactor with failing tests
- Make small, incremental changes
- Run tests frequently during refactoring
- Improve both test code and production code
- Apply design patterns and best practices
**Example (Python)**:
```python
# Refactor Phase: Improve structure and readability
class ShoppingCart:
def __init__(self):
self._items = []
def add_item(self, item):
self._items.append(item)
def calculate_total(self, tax_rate=0.0):
subtotal = self._calculate_subtotal()
tax_amount = subtotal * tax_rate
return subtotal + tax_amount
def _calculate_subtotal(self):
return sum(item.price for item in self._items)
class Item:
def __init__(self, name, price):
if price < 0:
raise ValueError("Price cannot be negative")
self.name = name
self.price = price
```
**Refactor Phase Checklist**:
- [ ] Code is DRY (Don't Repeat Yourself)?
- [ ] Names are clear and expressive?
- [ ] Functions/methods are focused and small?
- [ ] Code follows SOLID principles?
- [ ] All tests still pass?
- [ ] Test code is also clean and maintainable?
## TDD Rhythm and Cadence
### The Cycle Duration
A complete Red-Green-Refactor cycle should be **very short** - typically 2-10 minutes:
- Red: 1-2 minutes to write a small test
- Green: 1-5 minutes to make it pass
- Refactor: 1-3 minutes to clean up
If cycles are taking longer, the tests are too large. Break them into smaller pieces.
### Commit Strategy
Commit at the end of each complete Red-Green-Refactor cycle:
- After Red: Don't commit failing tests
- After Green: Can commit if needed, but prefer to refactor first
- After Refactor: **Commit here** - you have working, clean code with tests
### Incremental Development
Build features incrementally, one test at a time:
**Bad (Big Bang)**:
```
Write comprehensive test suite → Implement entire feature → Debug for hours
```
**Good (Incremental)**:
```
Test 1 → Implement 1 → Refactor →
Test 2 → Implement 2 → Refactor →
Test 3 → Implement 3 → Refactor → ...
```
## TDD Best Practices
### 1. Test Behavior, Not Implementation
**Bad**:
```python
def test_uses_quicksort_algorithm():
sorter = Sorter()
# Testing internal implementation detail
assert sorter._partition_method == "quicksort"
```
**Good**:
```python
def test_should_return_sorted_list_in_ascending_order():
sorter = Sorter()
unsorted = [3, 1, 4, 1, 5]
result = sorter.sort(unsorted)
assert result == [1, 1, 3, 4, 5]
```
### 2. One Assertion Per Test (Usually)
**Bad**:
```python
def test_user_registration():
user = register_user("john@example.com", "password123")
assert user.email == "john@example.com"
assert user.is_active == True
assert user.created_at is not None
assert user.id is not None
# Too many concerns in one test
```
**Good**:
```python
def test_should_create_user_with_provided_email():
user = register_user("john@example.com", "password123")
assert user.email == "john@example.com"
def test_should_create_active_user_by_default():
user = register_user("john@example.com", "password123")
assert user.is_active == True
def test_should_assign_creation_timestamp_to_new_user():
user = register_user("john@example.com", "password123")
assert user.created_at is not None
```
### 3. Arrange-Act-Assert (AAA) Pattern
Structure every test with three clear sections:
```python
def test_should_withdraw_amount_from_account_balance():
# Arrange: Set up test data and preconditions
account = Account(initial_balance=100)
# Act: Execute the behavior being tested
account.withdraw(30)
# Assert: Verify the expected outcome
assert account.balance == 70
```
### 4. Test Names Should Be Descriptive
**Bad**: `test_user()`, `test_1()`, `test_validation()`
**Good**:
- `test_should_reject_user_registration_with_invalid_email()`
- `test_should_return_empty_list_when_database_has_no_records()`
- `test_should_throw_exception_when_withdrawal_exceeds_balance()`
### 5. Keep Tests Fast
- Unit tests should run in milliseconds
- Avoid file I/O, network calls, and databases in unit tests
- Use mocks/stubs for external dependencies
- Slow tests discourage running them frequently
### 6. Keep Tests Independent
Each test should be able to run in isolation:
- Don't rely on test execution order
- Don't share state between tests
- Use setup/teardown to create clean state for each test
## Common TDD Mistakes
### Mistake 1: Writing Tests After Code
**Problem**: Writing tests after implementation defeats the purpose of TDD.
**Why it matters**: You lose the design benefits of TDD and tests become implementation-focused rather than behavior-focused.
**Solution**: Discipline. Always write the test first, even if it feels slower initially.
### Mistake 2: Tests That Are Too Large
**Problem**: Writing comprehensive tests that cover too much functionality.
**Why it matters**: Large tests lead to large implementations, losing the incremental nature of TDD.
**Solution**: Break down tests into smallest possible behavioral units. If a test takes more than 5 minutes to implement, it's too big.
### Mistake 3: Skipping the Refactor Phase
**Problem**: Moving to the next test immediately after green without refactoring.
**Why it matters**: Technical debt accumulates quickly, leading to unmaintainable code.
**Solution**: Always spend time in the refactor phase. Code quality is not optional.
### Mistake 4: Testing Implementation Details
**Problem**: Tests that check how code works internally rather than what it produces.
**Why it matters**: Implementation-focused tests are fragile and prevent refactoring.
**Solution**: Focus tests on public interfaces and observable behavior.
### Mistake 5: Incomplete Red Phase
**Problem**: Not running the test to verify it actually fails.
**Why it matters**: You might write a test that always passes (false positive).
**Solution**: Always verify the test fails before implementing. Watch it turn from red to green.
## TDD in Different Contexts
### Unit Testing (Primary TDD Focus)
- Test individual functions/methods in isolation
- Fast execution (milliseconds)
- Mock external dependencies
- High coverage of edge cases
### Integration Testing (TDD Can Apply)
- Test interactions between components
- Slower execution (seconds)
- Use real dependencies where practical
- Focus on critical integration points
### Acceptance Testing (BDD/TDD Hybrid)
- Test complete user scenarios
- Written in behavior-focused language
- Slower execution (seconds to minutes)
- Validate business requirements
## Measuring TDD Effectiveness
### Good TDD Indicators
1. **Test Count**: Tests outnumber production code files
2. **Test Coverage**: High line/branch coverage (>80%)
3. **Commit Frequency**: Small, frequent commits
4. **Code Structure**: Small functions, low complexity
5. **Refactoring Confidence**: Easy to change code without fear
### Poor TDD Indicators
1. **Test-After Pattern**: Tests written in bulk after features
2. **Low Coverage**: Large portions of code untested
3. **Complex Code**: Long methods, deep nesting
4. **Fragile Tests**: Tests break with minor refactoring
5. **Fear of Change**: Reluctance to modify code
## TDD and Code Quality
### Code Characteristics of TDD
Well-executed TDD produces code with:
**Small Functions**: Typically 5-20 lines
**Flat Structure**: Minimal nesting (1-2 levels max)
**Single Responsibility**: Each function does one thing
**Clear Naming**: Descriptive names that explain purpose
**Low Coupling**: Components loosely connected
**High Cohesion**: Related functionality grouped together
**Testable Design**: Easy to test in isolation
### Code Smells Indicating Test-After Development
**Long Methods**: Functions over 30 lines
**Deep Nesting**: 3+ levels of if/else/for statements
**Complex Conditionals**: Multiple AND/OR in one condition
**Type Checking**: Using `isinstance()` or `typeof`
**God Objects**: Classes with too many responsibilities
**Tight Coupling**: Components highly dependent on each other
**Poor Naming**: Generic names like `data`, `process`, `manager`
## Summary: The TDD Mindset
TDD is not just about testing - it's a design methodology:
1. **Tests Drive Design**: Let test requirements shape your API
2. **Incremental Progress**: Build features one small test at a time
3. **Continuous Refinement**: Always improve code quality through refactoring
4. **Fast Feedback**: Run tests constantly to catch issues immediately
5. **Confidence**: Trust your tests to enable bold refactoring
Remember: **Red-Green-Refactor** is not optional. Follow the cycle religiously, and your code quality will improve dramatically.

View File

@@ -0,0 +1,637 @@
# Testing Patterns for TDD
This document provides language-agnostic testing patterns and best practices that support effective Test-Driven Development across different programming environments.
## Test Structure Patterns
### The AAA Pattern (Arrange-Act-Assert)
The most fundamental testing pattern. Every test should follow this three-part structure:
```
Arrange: Set up the test data and preconditions
Act: Execute the behavior being tested
Assert: Verify the expected outcome
```
**Example (Python)**:
```python
def test_should_add_item_to_shopping_cart():
# Arrange
cart = ShoppingCart()
item = Item("Book", price=29.99)
# Act
cart.add_item(item)
# Assert
assert len(cart.items) == 1
assert cart.items[0] == item
```
**Example (JavaScript)**:
```javascript
test('should add item to shopping cart', () => {
// Arrange
const cart = new ShoppingCart();
const item = new Item("Book", 29.99);
// Act
cart.addItem(item);
// Assert
expect(cart.items).toHaveLength(1);
expect(cart.items[0]).toBe(item);
});
```
### Given-When-Then (BDD Style)
An alternative to AAA, commonly used in Behavior-Driven Development:
```
Given: Preconditions and context
When: The action or event
Then: Expected outcomes
```
**Example (Ruby)**:
```ruby
describe 'Shopping Cart' do
it 'adds item to cart' do
# Given a cart and an item
cart = ShoppingCart.new
item = Item.new("Book", 29.99)
# When adding the item
cart.add_item(item)
# Then the cart should contain the item
expect(cart.items.length).to eq(1)
expect(cart.items[0]).to eq(item)
end
end
```
## Test Organization Patterns
### Test Fixture Pattern
Use setup/teardown to create consistent test preconditions:
**Example (Python with pytest)**:
```python
import pytest
@pytest.fixture
def cart():
"""Create a fresh shopping cart for each test"""
return ShoppingCart()
@pytest.fixture
def sample_items():
"""Create sample items for testing"""
return [
Item("Book", 29.99),
Item("Pen", 1.99),
Item("Notebook", 5.99)
]
def test_should_calculate_correct_total(cart, sample_items):
for item in sample_items:
cart.add_item(item)
total = cart.calculate_total()
assert total == 37.97
```
**Example (JavaScript with Jest)**:
```javascript
describe('ShoppingCart', () => {
let cart;
beforeEach(() => {
cart = new ShoppingCart();
});
test('should calculate correct total', () => {
cart.addItem(new Item("Book", 29.99));
cart.addItem(new Item("Pen", 1.99));
const total = cart.calculateTotal();
expect(total).toBe(31.98);
});
});
```
### Test Builder Pattern
Create fluent APIs for complex test data setup:
**Example (Java)**:
```java
public class OrderBuilder {
private Customer customer;
private List<Item> items = new ArrayList<>();
private PaymentMethod paymentMethod;
public OrderBuilder withCustomer(String name, String email) {
this.customer = new Customer(name, email);
return this;
}
public OrderBuilder withItem(String name, double price) {
this.items.add(new Item(name, price));
return this;
}
public OrderBuilder withPayment(PaymentMethod method) {
this.paymentMethod = method;
return this;
}
public Order build() {
Order order = new Order(customer);
items.forEach(order::addItem);
order.setPaymentMethod(paymentMethod);
return order;
}
}
// Usage in tests:
@Test
public void shouldProcessOrderSuccessfully() {
Order order = new OrderBuilder()
.withCustomer("John Doe", "john@example.com")
.withItem("Book", 29.99)
.withItem("Pen", 1.99)
.withPayment(PaymentMethod.CREDIT_CARD)
.build();
OrderResult result = processor.process(order);
assertEquals(OrderStatus.COMPLETED, result.getStatus());
}
```
### Object Mother Pattern
Centralized factory for creating common test objects:
**Example (Python)**:
```python
class CustomerMother:
"""Factory for creating test customers"""
@staticmethod
def create_standard_customer():
return Customer(
name="John Doe",
email="john@example.com",
is_active=True
)
@staticmethod
def create_vip_customer():
return Customer(
name="Jane Smith",
email="jane@example.com",
is_active=True,
membership_level="VIP"
)
@staticmethod
def create_inactive_customer():
return Customer(
name="Bob Wilson",
email="bob@example.com",
is_active=False
)
# Usage in tests:
def test_should_apply_vip_discount():
customer = CustomerMother.create_vip_customer()
cart = ShoppingCart(customer)
cart.add_item(Item("Book", 100))
total = cart.calculate_total()
assert total == 80 # 20% VIP discount
```
## Assertion Patterns
### Single Assertion Principle
**Guideline**: Each test should verify one logical concept (though may have multiple assertion statements for clarity).
**Good**:
```python
def test_should_create_user_with_correct_attributes():
user = create_user("john@example.com", "John Doe")
# Multiple assertions verifying one concept: user creation
assert user.email == "john@example.com"
assert user.name == "John Doe"
assert user.is_active is True
```
**Better (when concepts are truly separate)**:
```python
def test_should_create_user_with_provided_email():
user = create_user("john@example.com", "John Doe")
assert user.email == "john@example.com"
def test_should_create_user_with_provided_name():
user = create_user("john@example.com", "John Doe")
assert user.name == "John Doe"
def test_should_create_active_user_by_default():
user = create_user("john@example.com", "John Doe")
assert user.is_active is True
```
### Custom Assertion Methods
Create domain-specific assertions for clarity:
**Example (JavaScript)**:
```javascript
function assertValidOrder(order) {
expect(order.customer).toBeDefined();
expect(order.items).not.toHaveLength(0);
expect(order.total).toBeGreaterThan(0);
expect(order.status).toBe('pending');
}
test('should create valid order from cart', () => {
const cart = createSampleCart();
const order = cart.checkout();
assertValidOrder(order);
});
```
## Test Doubles (Mocking) Patterns
### Stub Pattern
Replace dependencies with simplified implementations:
**Example (Python)**:
```python
class StubEmailService:
"""Stub that tracks calls without sending real emails"""
def __init__(self):
self.sent_emails = []
def send(self, to, subject, body):
self.sent_emails.append({
'to': to,
'subject': subject,
'body': body
})
def test_should_send_welcome_email_on_registration():
email_service = StubEmailService()
user_service = UserService(email_service)
user = user_service.register("john@example.com", "password")
assert len(email_service.sent_emails) == 1
assert email_service.sent_emails[0]['to'] == "john@example.com"
assert "Welcome" in email_service.sent_emails[0]['subject']
```
### Mock Pattern
Verify interactions with dependencies:
**Example (JavaScript with Jest)**:
```javascript
test('should call payment gateway with correct amount', () => {
const mockGateway = {
charge: jest.fn().mockResolvedValue({ success: true })
};
const processor = new PaymentProcessor(mockGateway);
processor.processPayment(customer, 100.00);
expect(mockGateway.charge).toHaveBeenCalledWith(
customer.paymentToken,
100.00
);
});
```
### Fake Pattern
Provide working implementations with shortcuts:
**Example (Python)**:
```python
class FakeUserRepository:
"""In-memory repository for testing"""
def __init__(self):
self.users = {}
self.next_id = 1
def save(self, user):
if not user.id:
user.id = self.next_id
self.next_id += 1
self.users[user.id] = user
return user
def find_by_id(self, user_id):
return self.users.get(user_id)
def test_should_persist_user_with_generated_id():
repo = FakeUserRepository()
user = User(name="John")
saved_user = repo.save(user)
assert saved_user.id is not None
assert repo.find_by_id(saved_user.id) == saved_user
```
## Parameterized Testing Pattern
Test multiple cases with the same structure:
**Example (Python with pytest)**:
```python
import pytest
@pytest.mark.parametrize("input,expected", [
(0, 0),
(1, 1),
(2, 2),
(3, 6),
(4, 24),
(5, 120),
])
def test_factorial_calculation(input, expected):
result = factorial(input)
assert result == expected
```
**Example (JavaScript with Jest)**:
```javascript
test.each([
[0, 0],
[1, 1],
[2, 2],
[3, 6],
[4, 24],
[5, 120],
])('factorial(%i) should equal %i', (input, expected) => {
const result = factorial(input);
expect(result).toBe(expected);
});
```
## Exception Testing Pattern
Test error conditions explicitly:
**Example (Python)**:
```python
def test_should_raise_error_when_withdrawing_too_much():
account = Account(balance=100)
with pytest.raises(InsufficientFundsError) as exc_info:
account.withdraw(150)
assert "Insufficient funds" in str(exc_info.value)
assert exc_info.value.available == 100
assert exc_info.value.requested == 150
```
**Example (JavaScript)**:
```javascript
test('should throw error when withdrawing too much', () => {
const account = new Account(100);
expect(() => {
account.withdraw(150);
}).toThrow(InsufficientFundsError);
expect(() => {
account.withdraw(150);
}).toThrow('Insufficient funds');
});
```
## Property-Based Testing Pattern
Test properties that should always hold:
**Example (Python with hypothesis)**:
```python
from hypothesis import given
import hypothesis.strategies as st
@given(st.lists(st.integers()))
def test_reversing_twice_gives_original(lst):
result = reverse(reverse(lst))
assert result == lst
@given(st.integers(min_value=0))
def test_factorial_is_positive(n):
result = factorial(n)
assert result > 0
```
## State-Based vs. Interaction-Based Testing
### State-Based Testing (Preferred)
Verify final state rather than how it was achieved:
**Example**:
```python
def test_should_remove_item_from_cart():
cart = ShoppingCart()
item = Item("Book", 29.99)
cart.add_item(item)
cart.remove_item(item)
assert len(cart.items) == 0 # Verify state
```
### Interaction-Based Testing
Verify interactions when state is not observable:
**Example**:
```python
def test_should_log_failed_login_attempt():
logger = Mock()
auth = AuthService(logger)
auth.login("user", "wrong_password")
logger.warning.assert_called_once_with(
"Failed login attempt for user: user"
)
```
## Test Naming Patterns
### Should-Style Naming
```
test_should_<expected_behavior>_when_<condition>
```
**Examples**:
- `test_should_return_empty_list_when_no_matches_found`
- `test_should_throw_exception_when_amount_is_negative`
- `test_should_apply_discount_when_quantity_exceeds_ten`
### Behavior-Style Naming
```
test_<subject>_<scenario>_<expected_result>
```
**Examples**:
- `test_cart_with_multiple_items_calculates_correct_total`
- `test_user_registration_with_invalid_email_fails`
- `test_payment_processing_with_insufficient_funds_raises_error`
## Test Data Patterns
### Obvious Data
Use self-documenting test data:
**Bad**:
```python
def test_calculation():
result = calculate(10, 5, 3)
assert result == 8
```
**Good**:
```python
def test_should_calculate_average_correctly():
value1 = 10
value2 = 20
value3 = 30
expected_average = 20
result = calculate_average([value1, value2, value3])
assert result == expected_average
```
### Boundary Value Testing
Test edges of valid ranges:
```python
def test_age_validation():
validator = AgeValidator(min_age=18, max_age=100)
# Below minimum
assert not validator.is_valid(17)
# At minimum boundary
assert validator.is_valid(18)
# Normal value
assert validator.is_valid(50)
# At maximum boundary
assert validator.is_valid(100)
# Above maximum
assert not validator.is_valid(101)
```
## TDD-Specific Patterns
### Triangulation
Build generality through multiple test cases:
**Step 1 - Specific case**:
```python
def test_should_add_two_numbers():
assert add(2, 3) == 5
# Implementation:
def add(a, b):
return 5 # Hardcoded - simplest thing that works
```
**Step 2 - Add another case to force generalization**:
```python
def test_should_add_two_numbers():
assert add(2, 3) == 5
def test_should_add_different_numbers():
assert add(4, 7) == 11
# Implementation:
def add(a, b):
return a + b # Now forced to generalize
```
### Fake It Till You Make It
Start with simple/hardcoded implementations:
```python
# Test
def test_should_return_greeting():
assert greet("World") == "Hello, World!"
# First implementation (fake it)
def greet(name):
return "Hello, World!"
# Add another test to force real implementation
def test_should_return_greeting_with_different_name():
assert greet("Alice") == "Hello, Alice!"
# Real implementation
def greet(name):
return f"Hello, {name}!"
```
### Obvious Implementation
When the implementation is obvious, just write it:
```python
# Test
def test_should_calculate_rectangle_area():
assert calculate_area(width=5, height=3) == 15
# Implementation (obvious, no need to fake)
def calculate_area(width, height):
return width * height
```
## Summary: Testing Pattern Selection
- **Use AAA** for clarity in most tests
- **Use fixtures** to eliminate setup duplication
- **Use builders** for complex object creation
- **Use test doubles** when dependencies are expensive or unpredictable
- **Prefer state-based** testing over interaction-based
- **Use parameterized tests** for multiple similar cases
- **Test boundaries** explicitly
- **Name tests** to describe behavior
- **Keep tests independent** of each other
- **Make tests readable** - they're documentation
Remember: Tests are first-class citizens in TDD. Treat them with the same care as production code.

View File

@@ -0,0 +1,280 @@
#!/usr/bin/env python3
"""
TDD Compliance Checker
Analyzes code to detect if Test-Driven Development was followed.
Identifies code smells and patterns that indicate tests-after-code.
"""
import os
import re
import sys
import json
from pathlib import Path
from typing import Dict, List, Tuple
class TDDComplianceChecker:
"""Checks code for TDD compliance indicators."""
# Code smell patterns that suggest tests-after-code
CODE_SMELLS = {
'nested_conditionals': r'if\s+.*:\s*\n\s+if\s+.*:|if\s+.*:\s*\n\s+elif\s+',
'long_methods': None, # Checked by line count
'complex_conditions': r'if\s+.*\s+(and|or)\s+.*\s+(and|or)\s+',
'multiple_responsibilities': None, # Checked by method analysis
'missing_abstractions': r'if\s+isinstance\(',
'god_class': None, # Checked by class analysis
}
def __init__(self, path: str):
self.path = Path(path)
self.issues = []
self.metrics = {
'files_analyzed': 0,
'test_files_found': 0,
'code_smells': 0,
'tdd_score': 0.0
}
def analyze(self) -> Dict:
"""Run full TDD compliance analysis."""
if self.path.is_file():
self._analyze_file(self.path)
else:
self._analyze_directory(self.path)
self._calculate_tdd_score()
return {
'issues': self.issues,
'metrics': self.metrics,
'compliance': self._get_compliance_level()
}
def _analyze_directory(self, directory: Path):
"""Recursively analyze all source files in directory."""
# Common source file extensions
extensions = {'.py', '.js', '.ts', '.java', '.go', '.rb', '.php', '.c', '.cpp', '.cs'}
for file_path in directory.rglob('*'):
if file_path.suffix in extensions and file_path.is_file():
# Skip test files in analysis (we'll check they exist separately)
if not self._is_test_file(file_path):
self._analyze_file(file_path)
def _analyze_file(self, file_path: Path):
"""Analyze a single source file for TDD compliance."""
self.metrics['files_analyzed'] += 1
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
lines = content.split('\n')
# Check for code smells
self._check_nested_conditionals(file_path, content)
self._check_long_methods(file_path, lines)
self._check_complex_conditions(file_path, content)
self._check_missing_abstractions(file_path, content)
# Check if corresponding test file exists
self._check_test_coverage(file_path)
except Exception as e:
self.issues.append({
'file': str(file_path),
'type': 'error',
'message': f'Failed to analyze file: {str(e)}'
})
def _check_nested_conditionals(self, file_path: Path, content: str):
"""Detect deeply nested conditional statements."""
pattern = self.CODE_SMELLS['nested_conditionals']
matches = re.finditer(pattern, content)
for match in matches:
line_num = content[:match.start()].count('\n') + 1
self.issues.append({
'file': str(file_path),
'line': line_num,
'type': 'code_smell',
'severity': 'high',
'smell': 'nested_conditionals',
'message': 'Nested conditional statements detected. TDD typically produces flatter, more testable code structures.'
})
self.metrics['code_smells'] += 1
def _check_long_methods(self, file_path: Path, lines: List[str]):
"""Detect methods/functions that are too long."""
# Simple heuristic: methods longer than 20 lines
in_method = False
method_start = 0
method_name = ''
indent_level = 0
for i, line in enumerate(lines):
stripped = line.lstrip()
# Detect method/function definitions (language-agnostic patterns)
if any(keyword in stripped for keyword in ['def ', 'function ', 'func ', 'public ', 'private ', 'protected ']):
if '{' in stripped or ':' in stripped:
in_method = True
method_start = i + 1
method_name = stripped.split('(')[0].split()[-1]
indent_level = len(line) - len(stripped)
# Check if method ended
elif in_method:
current_indent = len(line) - len(line.lstrip())
if stripped and current_indent <= indent_level and stripped not in ['}', 'end']:
method_length = i - method_start
if method_length > 20:
self.issues.append({
'file': str(file_path),
'line': method_start,
'type': 'code_smell',
'severity': 'medium',
'smell': 'long_method',
'message': f'Method "{method_name}" is {method_length} lines long. TDD encourages smaller, focused methods.'
})
self.metrics['code_smells'] += 1
in_method = False
def _check_complex_conditions(self, file_path: Path, content: str):
"""Detect overly complex conditional expressions."""
pattern = self.CODE_SMELLS['complex_conditions']
matches = re.finditer(pattern, content)
for match in matches:
line_num = content[:match.start()].count('\n') + 1
self.issues.append({
'file': str(file_path),
'line': line_num,
'type': 'code_smell',
'severity': 'medium',
'smell': 'complex_conditions',
'message': 'Complex boolean conditions detected. TDD promotes simpler, more testable conditions.'
})
self.metrics['code_smells'] += 1
def _check_missing_abstractions(self, file_path: Path, content: str):
"""Detect type checking that suggests missing abstractions."""
pattern = self.CODE_SMELLS['missing_abstractions']
matches = re.finditer(pattern, content)
for match in matches:
line_num = content[:match.start()].count('\n') + 1
self.issues.append({
'file': str(file_path),
'line': line_num,
'type': 'code_smell',
'severity': 'medium',
'smell': 'missing_abstractions',
'message': 'Type checking detected. TDD encourages polymorphism over type checking.'
})
self.metrics['code_smells'] += 1
def _check_test_coverage(self, file_path: Path):
"""Check if a corresponding test file exists."""
test_file = self._find_test_file(file_path)
if test_file and test_file.exists():
self.metrics['test_files_found'] += 1
else:
self.issues.append({
'file': str(file_path),
'type': 'missing_test',
'severity': 'critical',
'message': f'No corresponding test file found. Expected: {test_file}'
})
def _find_test_file(self, source_file: Path) -> Path:
"""Find the expected test file location for a source file."""
# Common test file patterns
test_patterns = [
lambda p: p.parent / f'test_{p.name}',
lambda p: p.parent / f'{p.stem}_test{p.suffix}',
lambda p: p.parent / 'tests' / f'test_{p.name}',
lambda p: p.parent.parent / 'tests' / p.parent.name / f'test_{p.name}',
lambda p: p.parent.parent / 'test' / p.parent.name / f'test_{p.name}',
]
for pattern in test_patterns:
test_file = pattern(source_file)
if test_file.exists():
return test_file
# Return the most common pattern as expected location
return source_file.parent / f'test_{source_file.name}'
def _is_test_file(self, file_path: Path) -> bool:
"""Check if a file is a test file."""
name = file_path.name.lower()
return any([
name.startswith('test_'),
name.endswith('_test.py'),
name.endswith('_test.js'),
name.endswith('.test.js'),
name.endswith('.spec.js'),
'test' in file_path.parts,
'tests' in file_path.parts,
])
def _calculate_tdd_score(self):
"""Calculate an overall TDD compliance score (0-100)."""
if self.metrics['files_analyzed'] == 0:
self.metrics['tdd_score'] = 0.0
return
# Factors that contribute to score
test_coverage_ratio = self.metrics['test_files_found'] / self.metrics['files_analyzed']
smell_penalty = min(self.metrics['code_smells'] * 5, 50) # Max 50 point penalty
# Score calculation
score = (test_coverage_ratio * 100) - smell_penalty
self.metrics['tdd_score'] = max(0.0, min(100.0, score))
def _get_compliance_level(self) -> str:
"""Get human-readable compliance level."""
score = self.metrics['tdd_score']
if score >= 90:
return 'excellent'
elif score >= 75:
return 'good'
elif score >= 50:
return 'fair'
elif score >= 25:
return 'poor'
else:
return 'critical'
def main():
"""Main entry point for the TDD compliance checker."""
if len(sys.argv) < 2:
print("Usage: check_tdd_compliance.py <path>")
print(" path: File or directory to analyze")
sys.exit(1)
path = sys.argv[1]
if not os.path.exists(path):
print(f"Error: Path '{path}' does not exist")
sys.exit(1)
checker = TDDComplianceChecker(path)
results = checker.analyze()
# Output results as JSON
print(json.dumps(results, indent=2))
# Exit with appropriate code
if results['compliance'] in ['critical', 'poor']:
sys.exit(1)
else:
sys.exit(0)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,112 @@
#!/bin/bash
#
# Setup TDD Hooks
#
# Installs git hooks and Claude Code hooks for TDD enforcement.
# This script should be run once per project to enable TDD reinforcement.
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="${1:-.}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "🔧 Setting up TDD hooks for project: $PROJECT_ROOT"
# Check if git repository
if [ ! -d "$PROJECT_ROOT/.git" ]; then
echo -e "${YELLOW}Warning: Not a git repository. Skipping git hooks.${NC}"
GIT_HOOKS=false
else
GIT_HOOKS=true
fi
# Check if Claude Code project
if [ ! -d "$PROJECT_ROOT/.claude" ]; then
echo -e "${YELLOW}Warning: Not a Claude Code project. Creating .claude directory.${NC}"
mkdir -p "$PROJECT_ROOT/.claude"
fi
# Install git pre-commit hook
if [ "$GIT_HOOKS" = true ]; then
echo "📝 Installing git pre-commit hook..."
HOOKS_DIR="$PROJECT_ROOT/.git/hooks"
mkdir -p "$HOOKS_DIR"
# Copy pre-commit hook template
cp "$SKILL_DIR/assets/hook-templates/pre-commit.sh" "$HOOKS_DIR/pre-commit"
chmod +x "$HOOKS_DIR/pre-commit"
echo -e "${GREEN}✅ Git pre-commit hook installed${NC}"
fi
# Install Claude Code user-prompt-submit hook
echo "📝 Installing Claude Code user-prompt-submit hook..."
CLAUDE_HOOKS_DIR="$PROJECT_ROOT/.claude/hooks"
mkdir -p "$CLAUDE_HOOKS_DIR"
# Copy user-prompt-submit hook template
cp "$SKILL_DIR/assets/hook-templates/user-prompt-submit.sh" "$CLAUDE_HOOKS_DIR/user-prompt-submit"
chmod +x "$CLAUDE_HOOKS_DIR/user-prompt-submit"
echo -e "${GREEN}✅ Claude Code user-prompt-submit hook installed${NC}"
# Create or update CLAUDE.md to mention TDD
CLAUDE_MD="$PROJECT_ROOT/.claude/CLAUDE.md"
if [ ! -f "$CLAUDE_MD" ]; then
echo "📝 Creating CLAUDE.md with TDD requirement..."
cat > "$CLAUDE_MD" << 'EOF'
# Project Guidelines
## Development Methodology
**This project uses Test-Driven Development (TDD).**
All code must be developed following the Red-Green-Refactor cycle:
1. 🔴 Red: Write a failing test first
2. 🟢 Green: Write minimal code to make the test pass
3. 🔵 Refactor: Improve code while keeping tests green
The `tdd-methodology-expert` skill is automatically loaded for this project.
EOF
echo -e "${GREEN}✅ CLAUDE.md created with TDD requirement${NC}"
else
# Check if TDD is already mentioned
if ! grep -q "TDD\|Test-Driven Development" "$CLAUDE_MD"; then
echo "📝 Updating CLAUDE.md with TDD requirement..."
echo "" >> "$CLAUDE_MD"
echo "## Development Methodology" >> "$CLAUDE_MD"
echo "" >> "$CLAUDE_MD"
echo "**This project uses Test-Driven Development (TDD).**" >> "$CLAUDE_MD"
echo "" >> "$CLAUDE_MD"
echo "All code must be developed following the Red-Green-Refactor cycle." >> "$CLAUDE_MD"
echo "The \`tdd-methodology-expert\` skill is automatically loaded for this project." >> "$CLAUDE_MD"
echo -e "${GREEN}✅ CLAUDE.md updated with TDD requirement${NC}"
else
echo -e "${GREEN}✅ CLAUDE.md already mentions TDD${NC}"
fi
fi
# Make scripts executable
chmod +x "$SKILL_DIR/scripts/"*.py
echo ""
echo -e "${GREEN}✅ TDD hooks setup complete!${NC}"
echo ""
echo "The following hooks have been installed:"
if [ "$GIT_HOOKS" = true ]; then
echo " • Git pre-commit hook: Validates TDD compliance before commits"
fi
echo " • Claude Code user-prompt-submit hook: Reinforces TDD in every interaction"
echo ""
echo "To verify installation, run:"
if [ "$GIT_HOOKS" = true ]; then
echo " git hook run pre-commit"
fi
echo " cat $PROJECT_ROOT/.claude/hooks/user-prompt-submit"

View File

@@ -0,0 +1,274 @@
#!/usr/bin/env python3
"""
Test Validator
Validates that tests exist, are properly structured, and follow TDD principles.
"""
import os
import re
import sys
import json
from pathlib import Path
from typing import Dict, List, Set
class TestValidator:
"""Validates test files for TDD compliance."""
def __init__(self, path: str):
self.path = Path(path)
self.results = {
'valid': True,
'test_files': [],
'issues': [],
'stats': {
'total_tests': 0,
'test_files_found': 0,
'well_structured': 0
}
}
def validate(self) -> Dict:
"""Run full test validation."""
test_files = self._find_test_files()
if not test_files:
self.results['valid'] = False
self.results['issues'].append({
'type': 'no_tests',
'severity': 'critical',
'message': 'No test files found. TDD requires writing tests first.'
})
return self.results
for test_file in test_files:
self._validate_test_file(test_file)
return self.results
def _find_test_files(self) -> List[Path]:
"""Find all test files in the path."""
test_files = []
if self.path.is_file():
if self._is_test_file(self.path):
test_files.append(self.path)
else:
for file_path in self.path.rglob('*'):
if file_path.is_file() and self._is_test_file(file_path):
test_files.append(file_path)
return test_files
def _is_test_file(self, file_path: Path) -> bool:
"""Check if a file is a test file."""
name = file_path.name.lower()
return any([
name.startswith('test_'),
name.endswith('_test.py'),
name.endswith('_test.js'),
name.endswith('.test.js'),
name.endswith('.test.ts'),
name.endswith('.spec.js'),
name.endswith('.spec.ts'),
name.endswith('Test.java'),
name.endswith('_test.go'),
'test' in file_path.parts,
'tests' in file_path.parts,
])
def _validate_test_file(self, test_file: Path):
"""Validate a single test file."""
self.results['stats']['test_files_found'] += 1
file_result = {
'file': str(test_file),
'tests_found': 0,
'issues': []
}
try:
with open(test_file, 'r', encoding='utf-8') as f:
content = f.read()
# Count test cases
test_count = self._count_tests(content, test_file.suffix)
file_result['tests_found'] = test_count
self.results['stats']['total_tests'] += test_count
if test_count == 0:
file_result['issues'].append({
'type': 'empty_test_file',
'severity': 'high',
'message': 'Test file contains no test cases'
})
self.results['valid'] = False
# Check test structure
structure_issues = self._check_test_structure(content, test_file)
file_result['issues'].extend(structure_issues)
if not structure_issues:
self.results['stats']['well_structured'] += 1
# Check for TDD patterns
tdd_issues = self._check_tdd_patterns(content, test_file)
file_result['issues'].extend(tdd_issues)
except Exception as e:
file_result['issues'].append({
'type': 'error',
'severity': 'high',
'message': f'Failed to validate test file: {str(e)}'
})
self.results['valid'] = False
self.results['test_files'].append(file_result)
# Aggregate issues
for issue in file_result['issues']:
if issue['severity'] in ['critical', 'high']:
self.results['valid'] = False
self.results['issues'].append({
'file': str(test_file),
**issue
})
def _count_tests(self, content: str, extension: str) -> int:
"""Count the number of test cases in the file."""
count = 0
# Language-specific test detection patterns
patterns = {
'.py': [r'def test_\w+', r'@pytest\.mark\.'],
'.js': [r'(test|it)\s*\(', r'describe\s*\('],
'.ts': [r'(test|it)\s*\(', r'describe\s*\('],
'.java': [r'@Test', r'public\s+void\s+test\w+'],
'.go': [r'func\s+Test\w+'],
'.rb': [r'(it|test)\s+["\']', r'describe\s+["\']'],
'.php': [r'public\s+function\s+test\w+', r'@test'],
}
if extension in patterns:
for pattern in patterns[extension]:
count += len(re.findall(pattern, content))
return count
def _check_test_structure(self, content: str, test_file: Path) -> List[Dict]:
"""Check if tests follow good structure patterns."""
issues = []
# Check for Arrange-Act-Assert pattern (AAA)
lines = content.split('\n')
# Look for test functions
test_functions = self._extract_test_functions(content, test_file.suffix)
for func_name, func_body in test_functions:
# Check if test is too long (suggests poor structure)
func_lines = func_body.split('\n')
if len(func_lines) > 30:
issues.append({
'type': 'long_test',
'severity': 'medium',
'test': func_name,
'message': f'Test "{func_name}" is {len(func_lines)} lines long. Consider breaking it down.'
})
# Check for multiple assertions in one test (might indicate poor isolation)
assertion_count = len(re.findall(r'assert|expect|should', func_body, re.IGNORECASE))
if assertion_count > 5:
issues.append({
'type': 'multiple_assertions',
'severity': 'low',
'test': func_name,
'message': f'Test "{func_name}" has {assertion_count} assertions. Consider splitting into focused tests.'
})
return issues
def _extract_test_functions(self, content: str, extension: str) -> List[tuple]:
"""Extract test function names and bodies."""
functions = []
# Simple extraction for Python
if extension == '.py':
pattern = r'def (test_\w+)\s*\([^)]*\):\s*\n((?: .*\n)*)'
matches = re.finditer(pattern, content)
for match in matches:
functions.append((match.group(1), match.group(2)))
# Simple extraction for JavaScript/TypeScript
elif extension in ['.js', '.ts']:
pattern = r'(test|it)\s*\([\'"]([^\'"]+)[\'"].*?\{([^}]*)\}'
matches = re.finditer(pattern, content, re.DOTALL)
for match in matches:
functions.append((match.group(2), match.group(3)))
return functions
def _check_tdd_patterns(self, content: str, test_file: Path) -> List[Dict]:
"""Check for patterns that indicate TDD was followed."""
issues = []
# Check for test-first indicators
# Red-Green-Refactor should result in:
# 1. Tests that clearly express intent
# 2. Minimal production code to make tests pass
# 3. Clear test names that describe behavior
test_functions = self._extract_test_functions(content, test_file.suffix)
for func_name, func_body in test_functions:
# Check for descriptive test names
if len(func_name) < 10 or not any(word in func_name.lower() for word in ['should', 'when', 'given', 'test']):
issues.append({
'type': 'poor_test_name',
'severity': 'low',
'test': func_name,
'message': f'Test name "{func_name}" is not descriptive. TDD encourages behavior-focused names.'
})
# Check for setup/teardown patterns
if not any(keyword in content for keyword in ['setUp', 'beforeEach', 'before', 'setup', 'fixture']):
# Only flag if multiple tests exist
if len(test_functions) > 3:
issues.append({
'type': 'missing_setup',
'severity': 'low',
'message': 'No setup/fixture detected. Consider DRY principle in test arrangement.'
})
break # Only report once per file
return issues
def main():
"""Main entry point for the test validator."""
if len(sys.argv) < 2:
print("Usage: validate_tests.py <path>")
print(" path: File or directory containing tests to validate")
sys.exit(1)
path = sys.argv[1]
if not os.path.exists(path):
print(f"Error: Path '{path}' does not exist")
sys.exit(1)
validator = TestValidator(path)
results = validator.validate()
# Output results as JSON
print(json.dumps(results, indent=2))
# Exit with appropriate code
if not results['valid']:
sys.exit(1)
else:
sys.exit(0)
if __name__ == '__main__':
main()