reproduce-before-fix

verified

ff63832b-3ffc-4fd6-96db-73fb57eeb44d

Use when a bug report arrives — turn it into a minimal failing test, confirm it fails for the right reason (RED), then fix (GREEN), then keep the test as a permanent regression guard.

Metadata

Skill ID
ff63832b-3ffc-4fd6-96db-73fb57eeb44d
Version
1
Owner
387274b7-2891-478b-81b8-e11d5adb9319
Tags
testingdebuggingregressionquality
Signature
verified
Integrity
OK
Content hash
48d245f08d273f0487752ade42a421b8dc05aaed03b3ff13b08422fa945316ee
Created
2026-08-15T05:27:12Z

Skill file

Raw skill file (markdown source)
# Reproduce Before Fix

**Use when** a bug report arrives. Never start fixing until you have a failing test that reproduces it. This is the "RED" half of RED-GREEN-REFACTOR.

## From Bug Report to Failing Test

Bug reports are messy. Your first job is to extract the kernel:

```
Bug report: "The dashboard crashes when I upload a CSV with
empty rows and click 'Analyze' — the spinner spins forever."

Extract:
  - Action: upload CSV + click Analyze
  - Input: CSV with empty rows
  - Symptom: infinite spinner (likely an unhandled exception)
```

### The extraction checklist
1. What ACTION triggers it? (click, API call, CLI command)
2. What INPUT? (file content, JSON body, CLI args, env vars)
3. What is the OBSERVED behavior? (crash, hang, wrong output)
4. What is the EXPECTED behavior? (graceful error, correct output)

## Building the Minimal Failing Test

Start with a copy of the buggy input, then strip EVERYTHING unnecessary:

```python
# Version 1: Full bug report input (works but 500 lines)
def test_dashboard_csv_empty_rows():
    csv_content = """name,age,city
Alice,30,NYC
,,
Bob,25,LA
...
"""  # 200 rows
    result = analyze_csv(csv_content)
    assert result is not None  # hangs here

# Version 2: Strip to minimum
def test_dashboard_csv_empty_rows():
    """Reproduces hang on CSV with empty rows"""
    csv_content = "name,age,city\\nAlice,30,NYC\\n,,\\nBob,25,LA"
    with pytest.raises(ValueError, match="empty row"):
        analyze_csv(csv_content)

# Version 3: Even smaller — a SINGLE empty row
def test_dashboard_csv_empty_rows():
    csv_content = "name,age\\n,"
    with pytest.raises(ValueError, match="empty row"):
        analyze_csv(csv_content)
```

## Confirm RED (Fail for the Right Reason)

```bash
# 1. Run the test — it MUST fail
pytest tests/test_dashboard.py::test_dashboard_csv_empty_rows -x -v
# Result: FAILED — test_dashboard_csv_empty_rows — TimeoutError

# 2. Confirm it's the RIGHT failure, not a typo in your test
# A test that fails because you misspelled 'analyze_csv' doesn't count
```

## Common RED-Confirmation Gotchas

| Symptom | Diagnosis |
|---------|-----------|
| Test passes immediately | Your test doesn't trigger the bug. Add the exact input from the report. |
| Test fails with `ImportError` | Your test setup is broken, not the code. Fix the test first. |
| Test fails with a different error | Good! You've found a different manifestation. Note both. |
| Test hangs (no assertion) | Set a timeout: `pytest --timeout=5` |

## Then Fix (GREEN)

Now make the minimal fix:

```python
# In analyze_csv:
def analyze_csv(content: str):
    rows = content.strip().split("\\n")
    for i, row in enumerate(rows):
        cells = row.split(",")
        if all(c == "" for c in cells):  # <-- THE FIX
            raise ValueError(f"Empty row at line {i+1}")
        # ... rest of processing
```

```bash
# Run the test — now it MUST pass
pytest tests/test_dashboard.py::test_dashboard_csv_empty_rows -x -v
# Result: PASSED
```

## Keep the Test as a Regression Guard

This is the most important step. The test you just wrote is permanent — it proves this specific bug never comes back:

```python
def test_csv_empty_rows_raises():
    """
    Regression test for ISSUE-1234: empty CSV rows hang the dashboard.
    Fixed in commit a1b2c3d.
    """
    csv_content = "name,age\\n,"
    with pytest.raises(ValueError, match="Empty row at line 1"):
        analyze_csv(csv_content)
```

Always link the issue/commit so future readers know WHY this test exists.

## Guardrails

- NEVER fix a bug without a failing test first. If you can't reproduce, you can't fix.
- The test must fail for the RIGHT reason — not a typo, not a wrong assertion
- If the repro is too large to read (~50+ lines), you haven't stripped enough
- Don't weaken the test to make it pass — fix the code, not the test

## Pitfalls

| Pitfall | Fix |
|---------|-----|
| Repro that's too large (full CSV, full HTTP response) | Strip to the minimum: one row, one field, one condition |
| Test passes immediately (not a real repro) | Compare your input to the exact bug report input |
| "Fixing" by weakening the test | If the test should raise, don't change it to a no-op |
| Not keeping the test after the fix | The test IS the deliverable — commit it with the fix |
| Fixing a different bug than reported | Read the expected behavior from the report |

## Verify / Checklist

- [ ] Failing test reproduces the EXACT bug from the report
- [ ] Test is minimal — under 20 lines, single assertion
- [ ] Test fails for the right reason (not setup error)
- [ ] Test passes after the fix
- [ ] Test includes issue/commit reference as a comment
- [ ] Full suite still passes (`pytest tests/ -x`)

Attached files

No attached files.