test-fixer
Safe workflow for fixing bulk test assertion failures in existing test suites — collection errors mask deeper problems, replace_all corrupts, fix source first then tests
Best use case
test-fixer is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Safe workflow for fixing bulk test assertion failures in existing test suites — collection errors mask deeper problems, replace_all corrupts, fix source first then tests
Teams using test-fixer should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/test-fixer/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How test-fixer Compares
| Feature / Agent | test-fixer | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Safe workflow for fixing bulk test assertion failures in existing test suites — collection errors mask deeper problems, replace_all corrupts, fix source first then tests
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Test Fixer — Bulk Test Failure Resolution
## When to Use
- 5+ failing tests in an existing test suite
- Collection errors masking real test failures
- Assertion mismatches between tests and evolved source code
- Async test framework misconfiguration
## Critical: Fix Collection Errors FIRST
Collection errors (ImportError, ModuleNotFoundError) cause pytest to stop collecting and skip subsequent test files. The "2 errors" you see initially may mask 50+ real failures.
**Workflow:**
1. Run `pytest --collect-only -q` to see only collection errors
2. Fix import paths, missing dependencies, pytest.ini markers one at a time
3. Re-run `pytest -q --tb=line | grep FAILED` to reveal the FULL failure count
```bash
# Step 1: Check collection, then full run
uv run python -m pytest tests/ --collect-only -q 2>&1 | grep -E "^ERROR"
# Fix each one
uv run python -m pytest tests/ -q --tb=line 2>&1 | grep "^FAILED"
```
## Strategy: Categorize Before Fixing
Group failures by root cause, then fix each category:
1. **Async/framework config** — 1-2 file changes fix many tests (install pytest-asyncio, add markers)
2. **Import paths** — hardcoded /mnt/github/github/... to /mnt/local-analysis/... (1 fix per file)
3. **Assertion mismatches** — output text changed, method names changed (1+ fix per assertion)
4. **Missing methods** — tests call APIs that no longer exist (requires source change or test deletion)
5. **Mock failures** — exception types don't match what source catches
**Fix ordering:** categories 1→2 fix many at once, then 3→5 per-file.
## Safe Patching Patterns
### NEVER use replace_all=True with short strings
```python
# CATASTROPHIC: replaces ALL occurrences including in unrelated methods
patch(mode="replace", old_string=' assert "25%" in output', replace_all=True)
```
### DO include 3+ lines of surrounding context
```python
# SAFE: context makes it unique
patch(mode="replace", old_string=""" progress.update_percentage(25)
output = mock_stdout.getvalue()
assert "25%" in output""", new_string="""...""")
```
### Use write_file for complex multi-fix scripts
When `patch()` fails due to string escaping (multiline with quotes, braces), write a fix script:
```python
# Write to file, then execute
write_file("/tmp/fix_tests.py", fix_script_content)
terminal(command="python3 /tmp/fix_tests.py")
```
**WARNING:** Python f-strings break when the script content contains `{}`. Use `r"""..."""` raw strings for the script content to avoid this.
### Alternative: sed on line numbers
```bash
sed -i '124old_line/new_line/' tests/agent_os/commands/test_file.py
```
## Common Failure Patterns and Fixes
### Async tests: "async def functions are not natively supported"
- **Fix**: `uv pip install pytest-asyncio`, add `asyncio_mode = auto` to pytest.ini, ensure `pytest.mark.asyncio` marker exists
### ModuleNotFoundError with hardcoded paths
- **Fix**: Convert hardcoded paths to relative using `os.path.join(os.path.dirname(__file__), '...')`
### Assertion mismatches (expected X, got Y)
- **Probe actual API first**: `uv run python3 -c "from module import Class; c = Class(); print(repr(c.method()))"`
- **Then update test** to match actual behavior — don't guess
### AttributeError: object has no attribute
- Source API evolved; method removed. Either restore it in source or delete/skip the test.
### execute() crashes: "unhashable type: 'slice'"
- Root cause: source `execute(args: List[str])` called `parser.parse(args)` which does `args[1:]`, but tests pass `dict`.
- **Fix source** to accept both: `if isinstance(args, dict): parsed = dict_to_args(args); else: parsed = self.parser.parse(args)`
## Subagent Gotchas for Test Fixing
### delegate_task IS viable for test fixing — with caveats
The subagent's `patch`/`terminal` calls **DO execute on the real filesystem** (not a sandbox). However:
1. **Subagent summaries are USELESS** — 1-2 sentences despite consuming M of tokens. Always `git diff` or re-run tests after.
2. **Subagents OVERREACH** — they modify unrelated files (result ymls, agent configs). Always `git diff --stat` and `git checkout -- <unrelated>`.
3. **Max iterations kill progress** — always check how many failures remain when a subagent exits.
### Subagent delegate_task vs execute_code for test fixing
| Approach | Pros | Cons |
|----------|------|------|
| **execute_code + write_file patch script** | Full control, no summary loss, single call | Script file writing needed, f-string escaping traps |
| **delegate_task** | Subagent can reason about test/source alignment | 99% summary loss, may overreach, max iterations |
| **Direct patch() calls** | Precise, safe | Tedious for many fixes, escaping issues with multiline |
**Recommendation for >20 fixes**: delegate_task to do initial work, then `git diff --stat` to verify, then direct patches for remaining.
## The replace_all Anti-Pattern (SEVERE)
`replace_all=True` with generic substrings like `'assert "25%"'` corrupts UNRELATED test methods. Real incident: replacing `"25%"` in a 645-line test file broke 6 additional tests. If you MUST use `replace_all`, verify every changed line in `git diff` before committing.
## Indentation Corruption Gotcha
Partial patch replacements inside `with`, `if`, `for`, or `try` blocks can corrupt indentation.
**After every patch**, run:
```bash
python3 -c "import py_compile; py_compile.compile('tests/test_file.py', doraise=True)"
```
Common corruption: a `replace` inside a `with` block changes only the first line of a multi-line call, leaving subsequent lines at the wrong indent.
## GitHub Issues
`gh issue create --label "phase:2"` silently fails (prints warning, still creates issue) if label doesn't exist. Always use existing labels: `gh label list | grep -E "priority|cat:"`.
## Windows Line Ending Trap (\r\n)
Files originating from Windows (e.g., `agents/`, `*_integration.py` with `\r\n`) cause the `patch` tool to produce massive spurious diffs — 700+ line diffs from a 10-line change. The patch tool doesn't handle CR/LF gracefully.
**Detection:** `cat -A filename.py` — if lines end with `^M$`, it has `\r\n`.
**Fix:** Always use Python/`sed` for these files instead of `patch`:
```python
# Read, modify, write with explicit newline handling
with open(path, 'r') as f:
content = f.read()
content = content.replace(old_block, new_block, 1)
with open(path, 'w') as f:
f.write(content)
```
Or use `sed -i` for single-line fixes.
## __init__ vs execute() Timing Trap
When tests pass config via dict args to `execute()` (e.g., `agents_base_dir`), but the class initializes components (generators, loaders) in `__init__` with default paths, the override will be ignored.
**Pattern to fix:**
```python
def execute(self, args):
if isinstance(args, dict):
base_dir = args.get("agents_base_dir")
if base_dir:
self.base_dir = Path(base_dir)
self.agents_dir = self.base_dir / "agents"
# CRITICAL: reinitialize ALL components that depend on the path
self.structure_generator = AgentStructureGenerator(self.agents_dir)
```
**Better fix:** Pass `base_dir` to the constructor in tests instead of the dict workaround.
## SQLAlchemy PostgreSQL Models vs SQLite in CI
Tests using `PgEnum` (PostgreSQL-specific enum types) or schema-qualified columns ALWAYS fail under SQLite in-memory DB. Symptoms:
- `TypeError: 'name' is an invalid keyword argument for Model` — fixture uses old field name; model has evolved (e.g. `name` -> `company_name`)
- `RuntimeError: dictionary changed size during iteration` — source bug in code under test (fix source, not test)
- `AssertionError: assert None is not None` — computed field (IRR, etc.) returns None for test data; relax assertion or fix dataset
- `AttributeError: 'dict' object has no attribute 'data'` — API returns dict, test expects object; update assertion
**Fix strategy for PostgreSQL-only tests:**
- Skip entire test classes (not individual tests) with clear reason:
```python
@pytest.mark.skip(
reason="PostgreSQL-specific ENUM types (PgEnum) are incompatible with SQLite in-memory DB. "
"Run with --database marker against a real PostgreSQL instance."
)
class TestSomeModel:
...
```
- Always skip the WHOLE class — individual test skips miss fixture errors that trigger before the test body
**Check actual model field names FIRST before patching fixtures:**
```bash
grep -n 'Mapped\[' src/worldenergydata/module/database/models.py
```
Then update fixtures to match canonical field names (e.g. `company_name=`, `country_of_registration=`).
## Legacy Namespace Compat (worldenergydata._compat pattern)
When tests import from `worldenergydata.modules.X.Y.Z` but the canonical layout is `worldenergydata.X.Y.Z`:
- `_compat.py` handles top-level redirect (`worldenergydata.modules.bsee` -> `worldenergydata.bsee`) but NOT deep nested subpackages
- Fix deep nesting via symlink: `ln -s ../../modules/bsee/analysis/type_curves src/worldenergydata/bsee/analysis/type_curves`
- For test files with wrong prefixes (e.g. `src.worldenergydata.modules.fdas`, `sodir_module.*`), fix the test imports directly to canonical paths
**Wrong prefix patterns seen in practice:**
```python
# BAD — src. prefix invalid when package is installed
from src.worldenergydata.modules.fdas.analysis.cashflow import CashflowEngine
# BAD — stale module name no longer exists
from sodir_module.analysis import SodirAnalyzer
# GOOD — canonical path
from worldenergydata.fdas.analysis.cashflow import CashflowEngine
from worldenergydata.sodir.analysis import SodirAnalyzer
```
## pytest.ini norecursedirs for Broken Test Dirs
When `_archive/`, `legacy/`, date-based dirs (e.g., `2025-08-*`), or project dirs contain broken tests:
- Add them to `norecursedirs` in pytest.ini — but use glob patterns like `legacy*` (not just `legacy_`) to catch plain `legacy/` dirs
- `--noconftest` disables `collect_ignore_glob` in conftest.py, so use `--ignore=` in addopts for single-file exclusions
- Scan ALL errors at once with `pytest --collect-only 2>&1 | grep "ERROR collecting"` before iteratively adding — the iterative approach wastes tokens
## Verification
After each batch of fixes:
```bash
uv run python -m pytest tests/ -q --tb=line 2>&1 | tail -5
# Should show: X failed, Y passed, Z skipped in Ns
# Track: failures should be monotonically decreasing
```Related Skills
test-oversized-skill
A test fixture skill that exceeds 200 lines with multiple H2/H3 sections for split testing.
digitalmodel-worktree-test-execution-with-shared-venv
Run digitalmodel tests from isolated worktrees without uv editable-dependency failures by using the main repo's existing virtualenv and PYTHONPATH.
blender-worktree-test-hardening
Recover and harden digitalmodel Blender automation work in isolated worktrees when uv/editable dependency paths break and local machines lack a Blender executable.
work-around-merge-conflicts-in-test-execution
Run tests when repo has unresolved merge conflicts in config files by bypassing broken configs and executing tests directly
test-driven-hook-debugging
Debugging and fixing shell hooks by writing isolated test suites first, then using test failures to pinpoint logic bugs
preserved-plan-refile-with-attested-review-wave
Reopen a previously closed GitHub issue with a preserved local plan, rewrite it into a conservative draft, and drive iterative attested adversarial review waves until it is truly approval-ready.
dummy-backfill-test
Temporary validation skill used to test skill backfill and commit flows.
test-driven-development
Use when implementing any feature or bugfix, before writing implementation code. Enforces RED-GREEN-REFACTOR cycle with test-first approach.
skeleton-test-coverage-uplift
Write test suites for SKELETON packages (zero test coverage) to uplift them to DEVELOPMENT maturity. Handles non-importable modules, optional dependencies, separate sub-repos, and Flask/web packages without runtime.
webapp-testing
Web application testing toolkit using Playwright with Python. Use for verifying frontend functionality, debugging UI behavior, capturing browser screenshots, viewing browser logs, and automating web interactions.
testing-tdd-london
TDD London School (mockist) specialist for mock-driven, outside-in development. Use for behavior verification testing, contract-driven development, testing object collaborations, or when focusing on HOW objects interact rather than WHAT they contain.
testing-production
Production validation specialist ensuring applications are fully implemented and deployment-ready. Use to verify no mock/stub/fake implementations remain, validate against real databases and APIs, perform end-to-end testing with actual systems, and confirm production readiness.