test-first-bugs
Enforces a test-driven bug-fixing workflow. Use when a user reports a bug, failing code, an error, or asks to fix something.
Install
npx skills add https://github.com/jamditis/claude-skills-journalism/tree/master/dev-toolkit/skills/test-first-bugs
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install jamditis-claude-skills-journalism@llmmart
git clone https://github.com/jamditis/claude-skills-journalism.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole jamditis/claude-skills-journalism collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Test-first bug fixing
Enforce a disciplined bug-fixing workflow that prevents regression and parallelizes fix attempts.
Core workflow
When a bug is reported, follow these steps in order:
Phase 1: Reproduce and document
- Understand the bug, Gather details about expected vs actual behavior
- Identify the test location, Determine where tests live in the project (check for
tests/,__tests__/,spec/,*.test.*,*.spec.*patterns) - Write a failing test, Create a test that demonstrates the bug
Phase 2: Fix with subagents
- Launch fix subagents, Use the Task tool with
subagent_type=general-purposeto attempt fixes - Run the test, Verify the fix by running the specific test
- Iterate if needed, If test still fails, launch additional subagents with new approaches
Phase 3: Verify and complete
- Run full test suite, Ensure no regressions were introduced
- Report success, Confirm the bug is fixed with passing test as proof
Writing the failing test
Test naming convention
Name the test to describe the bug:
# Python (pytest)
def test_user_login_fails_when_email_has_uppercase():
...
# Python (unittest)
def test_should_handle_empty_input_without_crashing(self):
...
// JavaScript (Jest/Vitest)
it('should not crash when input array is empty', () => { ... });
test('handles special characters in username', () => { ... });
// TypeScript
describe('UserService', () => {
it('returns null when user not found instead of throwing', () => { ... });
});
Test structure
Every bug reproduction test follows this pattern:
def test_bug_description():
# 1. ARRANGE - Set up the conditions that trigger the bug
input_data = create_problematic_input()
# 2. ACT - Perform the action that causes the bug
result = function_under_test(input_data)
# 3. ASSERT - Verify the expected (correct) behavior
assert result == expected_value # This should FAIL initially
Finding the right test file
Check the project structure for existing test patterns:
# Find test files
find . -name "*.test.*" -o -name "*.spec.*" -o -name "test_*.py" | head -20
# Find test directories
ls -la tests/ __tests__/ spec/ test/ 2>/dev/null
# Check package.json for test command
grep -A5 '"test"' package.json
Launching fix subagents
Use the Task tool to parallelize fix attempts:
Task tool parameters:
- subagent_type: "general-purpose"
- description: "Fix [bug description]"
- prompt: Include:
1. The bug description
2. The failing test location and contents
3. Suspected cause (if known)
4. Constraint: "Run the test to verify your fix works"
Parallel fix strategies
Launch multiple subagents with different approaches:
- Direct fix agent, Focus on the immediate code causing the bug
- Root cause agent, Investigate deeper architectural issues
- Edge case agent, Look for similar bugs in related code
When projects lack tests
If the project has no test infrastructure:
- Set up minimal test framework first
- Create the test file in a sensible location
- Document the test setup for future use
Quick test setup commands
# Python
pip install pytest
mkdir -p tests && touch tests/__init__.py
# JavaScript/TypeScript
npm install --save-dev jest
# or
npm install --save-dev vitest
# Go
# Tests are built-in, create *_test.go files
Verifying the fix
After subagent reports completion:
# Run the specific test
pytest tests/test_module.py::test_bug_description -v
npm test -- --grep "bug description"
go test -run TestBugDescription -v
# Run full suite to check for regressions
pytest
npm test
go test ./...
Example workflow
User reports: "The login function crashes when email has spaces"
Phase 1, Write failing test:
# tests/test_auth.py
def test_login_handles_email_with_spaces():
"""Bug: Login crashes when email contains spaces"""
auth = AuthService()
# This should return an error, not crash
result = auth.login("user @example.com", "password")
assert result.success == False
assert "invalid email" in result.error.lower()
Run test to confirm it fails:
pytest tests/test_auth.py::test_login_handles_email_with_spaces -v
# Expected: FAILED (demonstrates the bug)
Phase 2, Launch subagent:
Task tool:
- subagent_type: "general-purpose"
- description: "Fix email space crash"
- prompt: "Fix the login crash when email contains spaces.
Bug: AuthService.login() crashes instead of returning error when email has spaces.
Failing test: tests/test_auth.py::test_login_handles_email_with_spaces
After fixing, run: pytest tests/test_auth.py::test_login_handles_email_with_spaces -v
The test must pass to confirm the fix."
Phase 3, Verify:
# Specific test passes
pytest tests/test_auth.py::test_login_handles_email_with_spaces -v
# PASSED
# No regressions
pytest tests/test_auth.py -v
# All tests pass
Integration with hooks
The bug-report-detector hook in this plugin automatically:
- Detects when a user reports a bug
- Reminds Claude to follow the test-first workflow
- Blocks Edit/Write tools until a test file has been created or modified
Additional resources
Reference files
references/test-frameworks.md, Framework-specific test patternsreferences/common-bugs.md, Common bug patterns and test strategies
Example files
examples/python-bug-test.py, Python pytest exampleexamples/js-bug-test.js, JavaScript Jest example
Scripts
scripts/find-tests.sh, Locate test infrastructure in a project
Files (claude-skills-journalism)
-
agents
-
openai.yaml 111 B
interface: display_name: "Test first bugs" short_description: "Enforces a test-driven bug-fixing workflow"
-
-
examples
-
js-bug-test.js 3.3 KB
/** * Example: Bug reproduction test in JavaScript (Jest) * * Bug reported: "Array filter crashes when items have null properties" * * Expected: Filter should skip items with null properties gracefully * Actual: TypeError: Cannot read property 'name' of null */ // Assume this is the buggy code // const { filterByName } = require('../src/utils'); // Mock the buggy function for demonstration const filterByName = (items, searchTerm) => { // BUGGY: doesn't handle null items return items.filter((item) => item.name.toLowerCase().includes(searchTerm.toLowerCase())); }; describe('filterByName - Bug reproduction', () => { /** * Bug: Function crashes when array contains null items * Issue: #456 * Reported: 2026-02-01 * * These tests should FAIL before the fix and PASS after. */ describe('null handling bugs', () => { it('should not crash when array contains null items', () => { // Bug: TypeError on null item const items = [{ name: 'Alice' }, null, { name: 'Bob' }]; // Should not throw expect(() => { filterByName(items, 'alice'); }).not.toThrow(); }); it('should not crash when item.name is null', () => { // Bug: TypeError on null property const items = [{ name: 'Alice' }, { name: null }, { name: 'Bob' }]; expect(() => { filterByName(items, 'alice'); }).not.toThrow(); }); it('should not crash when item.name is undefined', () => { const items = [{ name: 'Alice' }, { id: 123 }, { name: 'Bob' }]; expect(() => { filterByName(items, 'alice'); }).not.toThrow(); }); it('should skip null items and return valid matches', () => { const items = [{ name: 'Alice' }, null, { name: 'Alicia' }, { name: 'Bob' }]; const result = filterByName(items, 'ali'); // Should find Alice and Alicia, skipping null expect(result).toHaveLength(2); expect(result.map((r) => r.name)).toEqual(['Alice', 'Alicia']); }); }); describe('empty input handling', () => { it('should handle empty array', () => { const result = filterByName([], 'test'); expect(result).toEqual([]); }); it('should handle empty search term', () => { const items = [{ name: 'Alice' }, { name: 'Bob' }]; // Empty string should match all (or none, depending on intended behavior) const result = filterByName(items, ''); expect(Array.isArray(result)).toBe(true); }); }); }); describe('filterByName - Regression tests', () => { /** * Ensure the fix doesn't break normal functionality */ it('should find items by partial name match', () => { const items = [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Alicia' }]; const result = filterByName(items, 'ali'); expect(result).toHaveLength(2); }); it('should be case-insensitive', () => { const items = [{ name: 'ALICE' }, { name: 'bob' }]; const result = filterByName(items, 'alice'); expect(result).toHaveLength(1); expect(result[0].name).toBe('ALICE'); }); it('should return empty array when no matches', () => { const items = [{ name: 'Alice' }, { name: 'Bob' }]; const result = filterByName(items, 'Charlie'); expect(result).toEqual([]); }); }); // Run with: npm test -- --grep "filterByName" // Or: npx jest examples/js-bug-test.js -
python-bug-test.py 3.5 KB
""" Example: Bug reproduction test in Python (pytest) Bug reported: "User login fails silently when email contains leading/trailing spaces" Expected: Login should work after trimming whitespace, or return clear error Actual: Login returns success=False with no error message """ import pytest from unittest.mock import Mock, patch # Assume this is the buggy code location # from myapp.auth import AuthService class TestLoginWhitespaceBug: """ Bug reproduction tests for email whitespace handling. Issue: https://github.com/org/repo/issues/123 Reported: 2026-02-01 These tests should FAIL before the fix and PASS after. """ def test_login_with_leading_space_in_email(self): """Bug: leading space causes silent failure""" auth = AuthService() # Email with leading space - user copy/pasted from somewhere result = auth.login(" user@example.com", "correct_password") # Should either succeed (after trimming) or give clear error assert result.success is True or result.error is not None if not result.success: assert "whitespace" in result.error.lower() or "trim" in result.error.lower() def test_login_with_trailing_space_in_email(self): """Bug: trailing space causes silent failure""" auth = AuthService() result = auth.login("user@example.com ", "correct_password") assert result.success is True or result.error is not None def test_login_with_spaces_around_email(self): """Bug: spaces on both sides cause silent failure""" auth = AuthService() result = auth.login(" user@example.com ", "correct_password") # Most permissive fix: trim and succeed assert result.success is True def test_login_error_message_is_helpful(self): """Even if we reject spaced emails, error should be clear""" auth = AuthService() result = auth.login(" bad@email.com", "password") if not result.success: # Error message should explain the problem assert result.error is not None assert len(result.error) > 10 # Not just "error" or "failed" class TestLoginNormalCases: """ Regression tests - ensure fix doesn't break normal login. """ def test_login_with_valid_credentials(self): """Normal login should still work""" auth = AuthService() result = auth.login("user@example.com", "correct_password") assert result.success is True assert result.user is not None def test_login_with_wrong_password(self): """Wrong password should fail with clear error""" auth = AuthService() result = auth.login("user@example.com", "wrong_password") assert result.success is False assert "password" in result.error.lower() or "credentials" in result.error.lower() # Minimal mock for demonstration - replace with actual import class AuthService: """Mock - replace with actual import""" def login(self, email: str, password: str): # This simulates the BUGGY behavior # The fix would add: email = email.strip() if email != "user@example.com": # Bug: doesn't strip spaces return Mock(success=False, error=None, user=None) # Silent failure! if password != "correct_password": return Mock(success=False, error="Invalid credentials", user=None) return Mock(success=True, error=None, user={"email": email}) # Run with: pytest examples/python-bug-test.py -v if __name__ == "__main__": pytest.main([__file__, "-v"])
-
-
references
-
common-bugs.md 6.4 KB
# Common bug patterns and test strategies ## Null / None / Undefined handling **Symptoms:** TypeError, NullPointerException, "undefined is not a function" **Test strategy:** ```python def test_handles_none_input(): result = function(None) assert result is not None # or appropriate default def test_handles_missing_key(): data = {} # Missing expected key result = function(data) assert result == default_value ``` **Common fixes:** - Add null checks at function entry - Use optional chaining (`?.` in JS/TS) - Provide default values - Use `get()` with defaults for dict/object access ## Off-by-one errors **Symptoms:** IndexError, missing first/last item, extra iteration **Test strategy:** ```python def test_first_element(): result = function([1, 2, 3]) assert result[0] == 1 # Verify first element handled def test_last_element(): result = function([1, 2, 3]) assert result[-1] == 3 # Verify last element handled def test_single_element(): result = function([1]) assert len(result) == 1 def test_empty_collection(): result = function([]) assert result == [] ``` ## String encoding issues **Symptoms:** UnicodeDecodeError, garbled text, "?" characters **Test strategy:** ```python def test_handles_unicode(): result = function("café ñ 日本語") assert "café" in result def test_handles_emoji(): result = function("Hello 👋 World") assert "👋" in result def test_handles_special_chars(): result = function("test@#$%^&*()") assert result is not None ``` ## Race conditions **Symptoms:** Intermittent failures, data corruption, deadlocks **Test strategy:** ```python import threading import concurrent.futures def test_concurrent_access(): results = [] def worker(): results.append(function()) threads = [threading.Thread(target=worker) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() assert len(results) == 10 assert all(r is not None for r in results) ``` ## Date/time bugs **Symptoms:** Wrong timezone, off-by-one day, DST issues **Test strategy:** ```python from datetime import datetime, timezone import freezegun # or time-machine @freezegun.freeze_time("2024-03-10 02:30:00") # DST transition def test_handles_dst_transition(): result = function() assert result.hour in (2, 3) # Depends on expected behavior def test_handles_timezone(): utc_time = datetime.now(timezone.utc) result = function(utc_time) # Verify timezone preserved or converted correctly def test_handles_leap_year(): date = datetime(2024, 2, 29) # Leap year result = function(date) assert result is not None ``` ## Floating point precision **Symptoms:** 0.1 + 0.2 != 0.3, comparison failures **Test strategy:** ```python import math def test_float_calculation(): result = function(0.1, 0.2) assert math.isclose(result, 0.3, rel_tol=1e-9) def test_currency_calculation(): # Use Decimal for money from decimal import Decimal result = function(Decimal("10.99"), Decimal("5.01")) assert result == Decimal("16.00") ``` ## Memory leaks / resource exhaustion **Symptoms:** OOM errors, file handle exhaustion, slow degradation **Test strategy:** ```python import tracemalloc def test_no_memory_leak(): tracemalloc.start() for _ in range(1000): function() current, peak = tracemalloc.get_traced_memory() tracemalloc.stop() assert peak < 100_000_000 # 100MB threshold def test_file_handles_closed(): import resource soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) for _ in range(100): function() # Should not leak file descriptors ``` ## SQL injection / input validation **Symptoms:** Security vulnerability, unexpected query results **Test strategy:** ```python def test_sql_injection_attempt(): malicious_input = "'; DROP TABLE users; --" result = function(malicious_input) # Should sanitize or reject, not execute assert "DROP" not in str(result) def test_xss_attempt(): malicious_input = "<script>alert('xss')</script>" result = function(malicious_input) assert "<script>" not in result ``` ## Async / Promise handling **Symptoms:** Unhandled promise rejection, callback not called **Test strategy:** ```javascript it('handles async error correctly', async () => { // Bug: unhandled rejection await expect(asyncFunction()).rejects.toThrow('Expected error'); }); it('resolves within timeout', async () => { const result = await Promise.race([ asyncFunction(), new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000) ) ]); expect(result).toBeDefined(); }); ``` ## State mutation bugs **Symptoms:** Unexpected side effects, stale data, wrong order **Test strategy:** ```python def test_does_not_mutate_input(): original = [1, 2, 3] input_copy = original.copy() function(original) assert original == input_copy # Input unchanged def test_returns_new_object(): obj = {"key": "value"} result = function(obj) assert result is not obj # Different object assert result == expected ``` ## Configuration / environment bugs **Symptoms:** Works locally, fails in production **Test strategy:** ```python import os from unittest.mock import patch def test_handles_missing_env_var(): with patch.dict(os.environ, {}, clear=True): result = function() assert result == default_value def test_handles_different_env(): with patch.dict(os.environ, {"ENV": "production"}): result = function() # Verify production behavior ``` ## Error message preservation **Symptoms:** Generic error, lost context, unhelpful message **Test strategy:** ```python def test_error_includes_context(): try: function(bad_input) assert False, "Should have raised" except CustomError as e: assert "bad_input" in str(e) assert e.original_error is not None ``` ## Pagination / limit bugs **Symptoms:** Missing items, duplicates, infinite loop **Test strategy:** ```python def test_pagination_no_duplicates(): all_results = [] page = 1 while True: results = function(page=page, limit=10) if not results: break all_results.extend(results) page += 1 # No duplicates assert len(all_results) == len(set(r.id for r in all_results)) def test_large_offset(): result = function(page=10000, limit=10) assert isinstance(result, list) # Should not error ``` -
test-frameworks.md 6.8 KB
# Test framework patterns Quick reference for writing bug reproduction tests in common frameworks. ## Python ### pytest (recommended) ```python # tests/test_module.py import pytest from myapp.module import function_under_test def test_bug_description(): """ Bug: [describe the bug] Expected: [expected behavior] Actual: [actual buggy behavior] """ # Arrange input_data = "problematic input" # Act result = function_under_test(input_data) # Assert assert result == expected_value # For exceptions def test_should_not_crash_on_bad_input(): with pytest.raises(ValueError, match="expected error"): function_under_test(bad_input) # For async @pytest.mark.asyncio async def test_async_bug(): result = await async_function() assert result is not None # Parametrized for multiple cases @pytest.mark.parametrize("input,expected", [ ("case1", "result1"), ("case2", "result2"), ]) def test_multiple_cases(input, expected): assert function_under_test(input) == expected ``` **Run commands:** ```bash pytest tests/test_module.py::test_bug_description -v pytest tests/test_module.py -v # All tests in file pytest -x # Stop on first failure pytest --tb=short # Shorter tracebacks # Watch-mode loop (re-runs on file change). Install with: pip install pytest-watcher ptw -- tests/test_module.py -v ``` For async tests, install `pytest-asyncio` and either decorate each test with `@pytest.mark.asyncio` (above) or set `asyncio_mode = auto` in `pytest.ini` / `pyproject.toml` to skip the per-test decorator. ### unittest ```python import unittest from myapp.module import function_under_test class TestBugFix(unittest.TestCase): def test_bug_description(self): """Bug: [description]""" result = function_under_test("input") self.assertEqual(result, expected) def test_should_raise_on_invalid(self): with self.assertRaises(ValueError): function_under_test(None) ``` **Run commands:** ```bash python -m unittest tests.test_module.TestBugFix.test_bug_description python -m unittest discover tests/ ``` ## JavaScript / TypeScript ### Jest ```javascript // __tests__/module.test.js const { functionUnderTest } = require('../src/module'); describe('Module', () => { describe('functionUnderTest', () => { it('should handle edge case without crashing', () => { // Bug: crashes on empty input const result = functionUnderTest(''); expect(result).toBeDefined(); expect(result.error).toBeNull(); }); it('should throw on invalid input', () => { expect(() => { functionUnderTest(null); }).toThrow('Invalid input'); }); }); }); // Async it('should fetch data correctly', async () => { const result = await asyncFunction(); expect(result.data).toHaveLength(3); }); // With mocks jest.mock('../src/api'); it('should handle API error', async () => { api.fetch.mockRejectedValue(new Error('Network error')); const result = await functionUnderTest(); expect(result.error).toBe('Network error'); }); ``` **Run commands:** ```bash # Jest uses -t / --testNamePattern (NOT --grep, which is Mocha) npm test -- -t "should handle edge case" npm test -- __tests__/module.test.js npm test -- --watch # Watch mode ``` ### Vitest ```typescript // src/module.test.ts import { describe, it, expect, vi } from 'vitest'; import { functionUnderTest } from './module'; describe('functionUnderTest', () => { it('handles empty array without crashing', () => { // Bug: TypeError when array is empty const result = functionUnderTest([]); expect(result).toEqual([]); }); }); ``` **Run commands:** ```bash npx vitest run src/module.test.ts npx vitest --reporter=verbose ``` ### Mocha + Chai ```javascript const { expect } = require('chai'); const { functionUnderTest } = require('../src/module'); describe('Module', function() { it('should handle special characters', function() { const result = functionUnderTest('test@#$%'); expect(result).to.be.a('string'); expect(result).to.not.include('undefined'); }); }); ``` ## Go ```go // module_test.go package mypackage import ( "testing" ) func TestBugDescription(t *testing.T) { // Bug: function panics on nil input result, err := FunctionUnderTest(nil) if err == nil { t.Error("expected error for nil input") } if result != nil { t.Errorf("expected nil result, got %v", result) } } // Table-driven tests func TestMultipleCases(t *testing.T) { tests := []struct { name string input string expected string }{ {"empty string", "", "default"}, {"special chars", "@#$", "sanitized"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := FunctionUnderTest(tt.input) if result != tt.expected { t.Errorf("got %s, want %s", result, tt.expected) } }) } } ``` **Run commands:** ```bash go test -v -run TestBugDescription go test ./... -v go test -race ./... # Check for race conditions ``` ## Rust ```rust // src/module.rs or tests/module_test.rs #[cfg(test)] mod tests { use super::*; #[test] fn test_bug_description() { // Bug: panics on empty vec let result = function_under_test(vec![]); assert!(result.is_ok()); assert_eq!(result.unwrap(), expected); } #[test] #[should_panic(expected = "invalid input")] fn test_panics_on_invalid() { function_under_test(invalid_input); } } ``` **Run commands:** ```bash cargo test test_bug_description -- --nocapture cargo test -- --test-threads=1 ``` ## Ruby (RSpec) ```ruby # spec/module_spec.rb require 'module' RSpec.describe Module do describe '#function_under_test' do it 'handles nil input without crashing' do # Bug: NoMethodError on nil result = described_class.function_under_test(nil) expect(result).to be_nil end it 'raises ArgumentError for invalid type' do expect { described_class.function_under_test(123) }.to raise_error(ArgumentError, /expected string/) end end end ``` **Run commands:** ```bash rspec spec/module_spec.rb:10 # Line number rspec --example "handles nil" ``` ## PHP (PHPUnit) ```php <?php // tests/ModuleTest.php use PHPUnit\Framework\TestCase; class ModuleTest extends TestCase { public function testBugDescription(): void { // Bug: returns null instead of empty array $result = functionUnderTest([]); $this->assertIsArray($result); $this->assertEmpty($result); } public function testThrowsOnInvalid(): void { $this->expectException(InvalidArgumentException::class); functionUnderTest(null); } } ``` **Run commands:** ```bash ./vendor/bin/phpunit --filter testBugDescription ./vendor/bin/phpunit tests/ModuleTest.php ```
-
-
scripts
-
find-tests.sh 2.2 KB
#!/bin/bash # Find test infrastructure in a project # Usage: ./find-tests.sh [directory] DIR="${1:-.}" echo "=== Test Infrastructure Discovery ===" echo "Scanning: $DIR" echo "" # Find test directories echo "📁 Test directories:" find "$DIR" -type d \( -name "tests" -o -name "test" -o -name "__tests__" -o -name "spec" \) 2>/dev/null | grep -v node_modules | grep -v venv | head -10 echo "" # Find test files by pattern echo "📄 Test files (sample):" find "$DIR" \( \ -name "test_*.py" -o \ -name "*_test.py" -o \ -name "*.test.js" -o \ -name "*.test.ts" -o \ -name "*.test.jsx" -o \ -name "*.test.tsx" -o \ -name "*.spec.js" -o \ -name "*.spec.ts" -o \ -name "*_test.go" \ \) 2>/dev/null | grep -v node_modules | grep -v venv | head -20 echo "" # Check for test config files echo "⚙️ Test configuration:" for config in pytest.ini pyproject.toml setup.cfg jest.config.js jest.config.ts vitest.config.js vitest.config.ts .mocharc.js .mocharc.json karma.conf.js; do if [ -f "$DIR/$config" ]; then echo " ✓ $config" fi done echo "" # Check package.json for test scripts if [ -f "$DIR/package.json" ]; then echo "📦 npm test scripts:" grep -A5 '"scripts"' "$DIR/package.json" | grep -E '"test|"jest|"vitest|"mocha' | head -5 echo "" fi # Check for test dependencies if [ -f "$DIR/package.json" ]; then echo "📦 Test dependencies:" grep -E '"jest"|"vitest"|"mocha"|"chai"|"@testing-library"' "$DIR/package.json" | head -5 fi if [ -f "$DIR/requirements.txt" ]; then echo "🐍 Python test dependencies:" grep -E "^pytest|^unittest|^nose" "$DIR/requirements.txt" fi if [ -f "$DIR/pyproject.toml" ]; then echo "🐍 Python test dependencies (pyproject.toml):" grep -E "pytest|unittest" "$DIR/pyproject.toml" | head -5 fi echo "" echo "=== Suggested test command ===" # Suggest test command based on what was found if [ -f "$DIR/package.json" ] && grep -q '"test"' "$DIR/package.json"; then echo "npm test" elif [ -f "$DIR/pytest.ini" ] || [ -f "$DIR/pyproject.toml" ]; then echo "pytest" elif find "$DIR" -name "*_test.go" 2>/dev/null | grep -q .; then echo "go test ./..." else echo "Could not determine test command. Check project documentation." fi
-
-
SKILL.md 5.8 KB
--- name: test-first-bugs description: Enforces a test-driven bug-fixing workflow. Use when a user reports a bug, failing code, an error, or asks to fix something. --- # Test-first bug fixing Enforce a disciplined bug-fixing workflow that prevents regression and parallelizes fix attempts. ## Core workflow When a bug is reported, follow these steps in order: ### Phase 1: Reproduce and document 1. **Understand the bug**, Gather details about expected vs actual behavior 2. **Identify the test location**, Determine where tests live in the project (check for `tests/`, `__tests__/`, `spec/`, `*.test.*`, `*.spec.*` patterns) 3. **Write a failing test**, Create a test that demonstrates the bug ### Phase 2: Fix with subagents 4. **Launch fix subagents**, Use the Task tool with `subagent_type=general-purpose` to attempt fixes 5. **Run the test**, Verify the fix by running the specific test 6. **Iterate if needed**, If test still fails, launch additional subagents with new approaches ### Phase 3: Verify and complete 7. **Run full test suite**, Ensure no regressions were introduced 8. **Report success**, Confirm the bug is fixed with passing test as proof ## Writing the failing test ### Test naming convention Name the test to describe the bug: ```python # Python (pytest) def test_user_login_fails_when_email_has_uppercase(): ... # Python (unittest) def test_should_handle_empty_input_without_crashing(self): ... ``` ```javascript // JavaScript (Jest/Vitest) it('should not crash when input array is empty', () => { ... }); test('handles special characters in username', () => { ... }); ``` ```typescript // TypeScript describe('UserService', () => { it('returns null when user not found instead of throwing', () => { ... }); }); ``` ### Test structure Every bug reproduction test follows this pattern: ```python def test_bug_description(): # 1. ARRANGE - Set up the conditions that trigger the bug input_data = create_problematic_input() # 2. ACT - Perform the action that causes the bug result = function_under_test(input_data) # 3. ASSERT - Verify the expected (correct) behavior assert result == expected_value # This should FAIL initially ``` ### Finding the right test file Check the project structure for existing test patterns: ```bash # Find test files find . -name "*.test.*" -o -name "*.spec.*" -o -name "test_*.py" | head -20 # Find test directories ls -la tests/ __tests__/ spec/ test/ 2>/dev/null # Check package.json for test command grep -A5 '"test"' package.json ``` ## Launching fix subagents Use the Task tool to parallelize fix attempts: ``` Task tool parameters: - subagent_type: "general-purpose" - description: "Fix [bug description]" - prompt: Include: 1. The bug description 2. The failing test location and contents 3. Suspected cause (if known) 4. Constraint: "Run the test to verify your fix works" ``` ### Parallel fix strategies Launch multiple subagents with different approaches: 1. **Direct fix agent**, Focus on the immediate code causing the bug 2. **Root cause agent**, Investigate deeper architectural issues 3. **Edge case agent**, Look for similar bugs in related code ## When projects lack tests If the project has no test infrastructure: 1. **Set up minimal test framework** first 2. **Create the test file** in a sensible location 3. **Document the test setup** for future use ### Quick test setup commands ```bash # Python pip install pytest mkdir -p tests && touch tests/__init__.py # JavaScript/TypeScript npm install --save-dev jest # or npm install --save-dev vitest # Go # Tests are built-in, create *_test.go files ``` ## Verifying the fix After subagent reports completion: ```bash # Run the specific test pytest tests/test_module.py::test_bug_description -v npm test -- --grep "bug description" go test -run TestBugDescription -v # Run full suite to check for regressions pytest npm test go test ./... ``` ## Example workflow **User reports:** "The login function crashes when email has spaces" **Phase 1, Write failing test:** ```python # tests/test_auth.py def test_login_handles_email_with_spaces(): """Bug: Login crashes when email contains spaces""" auth = AuthService() # This should return an error, not crash result = auth.login("user @example.com", "password") assert result.success == False assert "invalid email" in result.error.lower() ``` **Run test to confirm it fails:** ```bash pytest tests/test_auth.py::test_login_handles_email_with_spaces -v # Expected: FAILED (demonstrates the bug) ``` **Phase 2, Launch subagent:** ``` Task tool: - subagent_type: "general-purpose" - description: "Fix email space crash" - prompt: "Fix the login crash when email contains spaces. Bug: AuthService.login() crashes instead of returning error when email has spaces. Failing test: tests/test_auth.py::test_login_handles_email_with_spaces After fixing, run: pytest tests/test_auth.py::test_login_handles_email_with_spaces -v The test must pass to confirm the fix." ``` **Phase 3, Verify:** ```bash # Specific test passes pytest tests/test_auth.py::test_login_handles_email_with_spaces -v # PASSED # No regressions pytest tests/test_auth.py -v # All tests pass ``` ## Integration with hooks The `bug-report-detector` hook in this plugin automatically: 1. Detects when a user reports a bug 2. Reminds Claude to follow the test-first workflow 3. Blocks Edit/Write tools until a test file has been created or modified ## Additional resources ### Reference files - **`references/test-frameworks.md`**, Framework-specific test patterns - **`references/common-bugs.md`**, Common bug patterns and test strategies ### Example files - **`examples/python-bug-test.py`**, Python pytest example - **`examples/js-bug-test.js`**, JavaScript Jest example ### Scripts - **`scripts/find-tests.sh`**, Locate test infrastructure in a project
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.