Claude Code Plugins

Community-maintained marketplace

Feedback

test-driven-bug-fix

@cowwoc/styler
2
0

Systematic bug fix workflow with unit test first, then fix, prevents regression

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name test-driven-bug-fix
description Systematic bug fix workflow with unit test first, then fix, prevents regression
allowed-tools Bash, Read, Write, Edit

Test-Driven Bug Fix Skill

Purpose: Systematically fix bugs by creating unit test first, then implementing fix, ensuring regression prevention.

Performance: Prevents bug recurrence, builds regression test suite, improves code quality

When to Use This Skill

✅ Use test-driven-bug-fix When:

  • Bug discovered during development or testing
  • Unexpected behavior found
  • Edge case identified
  • Need to ensure bug doesn't recur
  • Want systematic bug fix approach

❌ Do NOT Use When:

  • Not a bug (feature request)
  • Issue is compilation error (not behavioral bug)
  • Test already exists for the bug
  • Bug fix is trivial (typo, missing semicolon)

What This Skill Does

1. Analyzes Bug

# Identifies:
- Exact failing behavior
- Minimal input that triggers bug
- Expected vs actual behavior
- Root cause (if known)

2. Creates Minimal Reproducing Test

@Test
public void testBugScenario() {
    // Minimal input triggering bug
    String input = "edge case";

    // Execute code
    Result result = methodUnderTest(input);

    // This assertion FAILS initially (reproduces bug)
    assertEquals(expectedBehavior, result);
}

3. Verifies Test Fails

# Run test to confirm it reproduces bug
mvn test -Dtest=BugTest#testBugScenario

# Expected: Test FAILS (proves bug exists)

4. Implements Fix

# Fix the code causing the bug
Edit: src/main/java/BuggyClass.java
  - Fix root cause
  - Handle edge case
  - Add validation if needed

5. Verifies Test Passes

# Run test again after fix
mvn test -Dtest=BugTest#testBugScenario

# Expected: Test PASSES (proves bug fixed)

6. Adds to Permanent Suite

# Commit test with fix
git add src/test/java/BugTest.java
git add src/main/java/BuggyClass.java
git commit -m "Fix: Handle edge case in validation

Added regression test: testBugScenario()

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>"

Usage

Basic Bug Fix

# After discovering bug
BUG_DESCRIPTION="Parser fails on scientific notation '1.5e10'"
TEST_CLASS="LexerTest"
BUGGY_CLASS="Lexer"

/workspace/main/.claude/scripts/test-driven-bug-fix.sh \
  --bug-description "$BUG_DESCRIPTION" \
  --test-class "$TEST_CLASS" \
  --buggy-class "$BUGGY_CLASS"

With Minimal Input

# Provide minimal reproducing input
MINIMAL_INPUT="double x = 1.5e10;"
EXPECTED="TokenType.DOUBLE_LITERAL"
ACTUAL="ParseException"

/workspace/main/.claude/scripts/test-driven-bug-fix.sh \
  --input "$MINIMAL_INPUT" \
  --expected "$EXPECTED" \
  --actual "$ACTUAL"

Bug Discovery Protocol

Step 1: Immediate Unit Test

DO NOT fix bug before creating test

// ❌ WRONG: Fix immediately
if (input.contains("e")) {
    // Handle scientific notation
}

// ✅ CORRECT: Test first
@Test
public void testScientificNotationLexing() {
    // Reproduces bug, will fail initially
}
// THEN fix after test fails

Step 2: Isolation

Extract smallest possible test case

// ❌ TOO BROAD: Full program
@Test
public void testComplexProgram() {
    String program = """
        public class Test {
            double x = 1.5e10;
            int y = 42;
            String z = "hello";
        }
        """;
    parser.parse(program);
}

// ✅ MINIMAL: Just the failing part
@Test
public void testScientificNotationLexing() {
    String input = "1.5e10";
    Token token = lexer.nextToken(input);
    assertEquals(TokenType.DOUBLE_LITERAL, token.type());
}

Step 3: Documentation

Descriptive test name

// ❌ VAGUE
@Test public void test1() { }
@Test public void testLexer() { }
@Test public void testBug() { }

// ✅ DESCRIPTIVE
@Test public void testScientificNotationLexing() { }
@Test public void testMethodReferenceInAssignment() { }
@Test public void testEnumConstantWithArguments() { }
@Test public void testGenericTypeVariableDeclaration() { }

Step 4: Fix Validation

Ensure test passes after fix

# Before fix: Test FAILS
mvn test -Dtest=LexerTest#testScientificNotationLexing
# [ERROR] Tests run: 1, Failures: 1

# After fix: Test PASSES
mvn test -Dtest=LexerTest#testScientificNotationLexing
# [INFO] Tests run: 1, Failures: 0

Step 5: Regression Prevention

Keep test permanently

# ❌ WRONG: Delete test after bug fixed
git add src/main/java/Lexer.java
git commit -m "Fix scientific notation bug"
# Test not committed - regression possible

# ✅ CORRECT: Commit test with fix
git add src/test/java/LexerTest.java
git add src/main/java/Lexer.java
git commit -m "Fix scientific notation bug

Added regression test: testScientificNotationLexing()

🤖 Generated with Claude Code"

Unit Test Requirements

Specific

Target exact failing behavior

// ❌ GENERAL: Tests overall functionality
@Test
public void testLexer() {
    // Tests many things, not specific bug
}

// ✅ SPECIFIC: Tests only the bug
@Test
public void testScientificNotationLexing() {
    // Tests ONLY scientific notation handling
}

Minimal

Smallest input triggering bug

// ❌ LARGE INPUT
String input = "public class Foo { double x = 1.5e10; int y = 42; }";

// ✅ MINIMAL INPUT
String input = "1.5e10";

Isolated

Independent of other tests

// ❌ DEPENDENT
@Test
public void test1() { setupDatabase(); }

@Test
public void test2() {
    // Depends on test1 running first
    queryDatabase();
}

// ✅ ISOLATED
@Test
public void testScientificNotation() {
    // Self-contained, no external dependencies
    String input = "1.5e10";
    Token token = Lexer.tokenize(input);
    assertEquals(TokenType.DOUBLE_LITERAL, token.type());
}

Fast

Execute quickly

// ❌ SLOW: External dependencies
@Test
public void testParser() {
    server.start();
    client.connect();
    // ... slow setup
}

// ✅ FAST: Direct testing
@Test
public void testScientificNotation() {
    assertEquals(TokenType.DOUBLE_LITERAL,
                 Lexer.tokenize("1.5e10").type());
}

Example Workflow

Example 1: Parser Bug

// 1. Bug discovered: Parser fails on scientific notation "1.5e10"

// 2. Create test that reproduces bug
@Test
public void testScientificNotationLexing() {
    String input = "double x = 1.5e10;";
    List<Token> tokens = lexer.tokenize(input);

    // This assertion FAILS initially (reproducing the bug)
    assertEquals(TokenType.DOUBLE_LITERAL, tokens.get(3).type());
    assertEquals("1.5e10", tokens.get(3).text());
}

// 3. Run test - FAILS (confirms bug exists)
// mvn test -Dtest=LexerTest#testScientificNotationLexing
// Expected: DOUBLE_LITERAL, Actual: ParseException

// 4. Fix the bug in Lexer.java
private Token lexNumber() {
    StringBuilder sb = new StringBuilder();

    // Add existing decimal logic...

    // NEW: Handle scientific notation
    if (peek() == 'e' || peek() == 'E') {
        sb.append(consume());
        if (peek() == '+' || peek() == '-') {
            sb.append(consume());
        }
        while (Character.isDigit(peek())) {
            sb.append(consume());
        }
    }

    return new Token(TokenType.DOUBLE_LITERAL, sb.toString());
}

// 5. Run test - PASSES (confirms bug fixed)
// mvn test -Dtest=LexerTest#testScientificNotationLexing
// Tests run: 1, Failures: 0

// 6. Commit test + fix together
// git add src/test/java/LexerTest.java src/main/java/Lexer.java
// git commit -m "Fix: Add scientific notation support to lexer"

Example 2: Validation Bug

// 1. Bug: Validator incorrectly rejects valid input

// 2. Create test
@Test
public void testNullInputValidation() {
    // Currently throws NPE, should return validation error
    ValidationResult result = validator.validate(null);

    assertFalse(result.isValid());
    assertEquals("Input cannot be null", result.getError());
}

// 3. Test FAILS with NPE (reproduces bug)

// 4. Fix validator
public ValidationResult validate(String input) {
    // NEW: Null check
    if (input == null) {
        return ValidationResult.error("Input cannot be null");
    }

    // Existing validation logic...
}

// 5. Test PASSES (bug fixed)

// 6. Commit both files

Workflow Integration

Bug Discovery Flow

Development or Testing
  ↓
Bug discovered
  ↓
[test-driven-bug-fix skill] ← THIS SKILL
  ↓
Create minimal reproducing test
  ↓
Verify test FAILS (reproduces bug)
  ↓
Implement fix
  ↓
Verify test PASSES (bug fixed)
  ↓
Commit test + fix together
  ↓
Bug fixed with regression prevention

Output Format

Script returns JSON:

{
  "status": "success",
  "message": "Bug fixed with regression test",
  "bug_description": "Parser fails on scientific notation",
  "test_class": "LexerTest",
  "test_method": "testScientificNotationLexing",
  "buggy_class": "Lexer",
  "test_failed_initially": true,
  "test_passed_after_fix": true,
  "files_modified": [
    "src/test/java/LexerTest.java",
    "src/main/java/Lexer.java"
  ],
  "commit_sha": "abc123",
  "timestamp": "2025-11-11T12:34:56-05:00"
}

Related Skills

  • reinvoke-agent-fixes: May use test-driven approach for fixes
  • archive-task: Commits test + fix together

Troubleshooting

Error: "Test passes before fix"

# Test should FAIL initially to prove it reproduces bug
# If test passes before fix:
1. Bug may already be fixed
2. Test doesn't actually reproduce bug
3. Test logic incorrect

# Fix: Verify test actually triggers bug condition

Error: "Test still fails after fix"

# Fix didn't resolve the bug
# Options:
1. Fix incorrect (try different approach)
2. Multiple bugs (fix addressed one, another remains)
3. Test expectations wrong (update test)

# Debug: Add logging to understand actual behavior

Test Too Broad

# Test covers too much, not minimal
# Refine by:
1. Remove unrelated setup
2. Use smallest possible input
3. Test one specific behavior
4. Extract to helper method if needed

Best Practices

Always Test First

# ✅ CORRECT SEQUENCE
1. Create test (fails)
2. Implement fix
3. Test passes
4. Commit both

# ❌ WRONG SEQUENCE
1. Implement fix
2. Create test (passes)
# Can't verify test reproduces bug

One Bug, One Test

# ✅ ONE TEST PER BUG
@Test testScientificNotation() { }
@Test testHexLiterals() { }
@Test testMethodReferences() { }

# ❌ MULTIPLE BUGS IN ONE TEST
@Test testAllBugs() {
    // Tests 5 different bugs
}

Keep Tests Simple

// ✅ SIMPLE
@Test
public void testEdgeCase() {
    assertEquals(expected, actual);
}

// ❌ COMPLEX
@Test
public void testEdgeCase() {
    // 50 lines of setup
    // Multiple assertions
    // Complex logic
}

Implementation Notes

The test-driven-bug-fix script performs:

  1. Analysis Phase

    • Parse bug description
    • Identify failing behavior
    • Determine minimal input
    • Extract expected vs actual
  2. Test Creation Phase

    • Generate test method name
    • Create minimal reproducing test
    • Write to appropriate test class
    • Format with proper annotations
  3. Verification Phase

    • Compile test
    • Run test
    • Verify test FAILS (proves reproduces bug)
    • Capture failure output
  4. Fix Implementation Phase

    • Identify buggy code location
    • Implement fix (Edit tool)
    • Preserve existing behavior
    • Handle edge case
  5. Validation Phase

    • Compile fixed code
    • Run test again
    • Verify test PASSES (proves fix works)
    • Run full test suite (no regressions)
  6. Commit Phase

    • Stage test file
    • Stage fixed code file
    • Commit both together atomically
    • Include test name in message