---
name: test-writing
description: >-
  Writes effective, maintainable tests that catch real bugs across unit, integration,
  and edge-case categories. Use when features need tests, bug fixes need regression tests,
  or user asks to "write tests for X" or "add test coverage".
  NOT for debugging, code review, or test infrastructure setup.
allowed-tools:
  - Read
  - Glob
  - Grep
  - Bash
---

## Identity & Scope

**Executor:** either
**Model tier:** sonnet
**Scope:** Test files and source code (read-only) in the target project
**Off-limits:** Modifying source code logic, changing CI/CD, deploying
**Hard rules:** No credentials in output. No deleting files. No spending money. Report via structured output.

## Essential Principles

1. **Test behavior, not implementation** — Assert what the function does, not how. Guards against: brittle tests that break on every refactor.
2. **One concept per test** — Each test verifies exactly one behavior. Guards against: ambiguous failures from multi-assert tests.
3. **Test the unhappy paths** — Error cases, nulls, and boundaries harbor the real bugs. Guards against: green suites that miss production failures.
4. **Tests must be independent** — No test depends on another's state or order. Guards against: cascade failures and flaky suites.

## When to Use
- Writing tests alongside new feature development
- Adding regression tests before fixing a bug
- User asks to "write tests for X" or "increase coverage"
- Adding edge case tests to existing suites
- Verifying behavior before refactoring

## When NOT to Use
- Debugging a failing test — use bug-debugging skill
- Reviewing test quality — use code-review skill
- Setting up test infrastructure — use devops skill
- Performance/load testing — use performance skill

## Rationalizations to Reject

| Shortcut | Why it's wrong |
|----------|----------------|
| "Happy path test is sufficient" | Bugs live in unhappy paths; happy-only suites miss real failures |
| "Mock everything for speed" | Tests that mock everything test the mocks, not the code |
| "Too simple to test" | Simple code gets complex; untested simple becomes untested complex |
| "One big test covers all cases" | Multi-assert tests fail ambiguously and are hard to maintain |

## Workflow

### Phase 1: Analysis
**Entry:** Target code accessible
**Actions:**
1. Read source code to identify public functions and entry points
2. List existing tests and identify untested code paths
3. Categorize needed tests: unit, integration, edge case, error path
4. Identify the test framework in use (pytest, jest, go test, etc.)
**Exit:** Test plan listing each test with category and target function

### Phase 2: Write Unit Tests
**Entry:** Test plan complete
**Actions:**
1. Write tests using Arrange-Act-Assert pattern
2. Name each test as sentence: `test_user_cannot_login_with_wrong_password`
3. Cover happy path for each function
4. Cover error paths: invalid input, missing data, exceptions
5. Cover edge cases: empty, null, max values, boundaries
**Exit:** Unit tests written for all identified functions

### Phase 3: Write Integration Tests
**Entry:** Unit tests complete
**Actions:**
1. Identify component boundaries needing integration testing
2. Write tests exercising real interactions (DB, filesystem, APIs)
3. Use minimal mocking — only mock truly external services
4. Verify data flows across component boundaries
**Exit:** Integration tests written for key paths

### Phase 4: Verify
**Entry:** All tests written
**Actions:**
1. Run full test suite — all new tests must pass
2. Verify each test would fail if tested behavior broke
3. Confirm no test depends on another's execution or state
4. Confirm output matches required JSON format
**Exit:** All checks pass — report results

## Output Format

```json
{
  "skill": "test-writing",
  "status": "completed",
  "worker": "worker-id",
  "summary": "Wrote 14 tests (8 unit, 3 integration, 3 edge case) for user_service.py — all passing",
  "findings": [
    {"item": "test_user_service.py — 8 unit tests", "severity": "info", "detail": "create_success, create_duplicate_email, get_found, get_not_found, update_valid, update_nonexistent, delete_active, delete_already_deleted"},
    {"item": "test_user_integration.py — 3 integration tests", "severity": "info", "detail": "full_lifecycle, concurrent_creation, cascade_delete_with_orders"},
    {"item": "Untested: user_service.py:67 bulk_import()", "severity": "medium", "detail": "Requires CSV fixtures. Flagged for follow-up."}
  ],
  "errors": [],
  "elapsed_seconds": 51.3
}
```

**Bad example:** `{"summary": "Added tests", "findings": [{"item": "tests written"}]}` — no count, no names, no categories, unverifiable.

## Escalation Triggers

STOP and report to boss when:
- No test framework and adding one requires project decisions
- Tests require paid APIs or production databases
- Code is untestable without significant refactoring
- Test data requires real user data or credentials
- Code behavior is ambiguous — cannot determine expected output

## Success Criteria

- [ ] Every public function/method has at least one test
- [ ] Happy, error, and edge cases covered
- [ ] All tests pass when run
- [ ] Each test is independent (no shared mutable state)
- [ ] Test names describe behavior as readable sentences
- [ ] Untested paths documented with reason
- [ ] Output matches JSON format above
- [ ] No unresolved escalation triggers
