# Kiwi Home AI Coding Resources Tdd Patterns

> TDD Patterns

- Skill: `tomevault-io/kiwi-home-ai-coding-resources-tdd-patterns` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/kiwi-home-ai-coding-resources-tdd-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/kiwi-home-ai-coding-resources-tdd-patterns/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/kiwi-home-ai-coding-resources-tdd-patterns

---


# TDD Patterns

## Universal TDD Patterns

### Test Selection

Test behavior through the public API, not implementation details.

**Corrective**: Claude tends to test whatever is easiest to access -- private helpers, internal state, intermediate variables. These tests break on every refactor and prove nothing about behavior.

| Test This | Not This |
|-----------|----------|
| Return values from public methods | Internal helper function calls |
| Observable side effects (DB writes, API calls, events) | Private state mutations |
| Error messages/codes returned to callers | Internal exception handling paths |
| User-facing output (rendered HTML, API response shape) | Component internal DOM structure |

**Rule**: If you must access a private member to write the test, you are testing the wrong thing. Find the public boundary where the behavior is observable.

### Assertion Quality

Assert behavior, not existence.

| Weak (proves little) | Strong (proves behavior) |
|----------------------|-------------------------|
| `assert result is not None` | `assert result.status == "completed"` |
| `expect(component).toBeDefined()` | `expect(component).toHaveTextContent("Success")` |
| `assert len(errors) > 0` | `assert errors[0].code == "VALIDATION_FAILED"` |

**Decision rule**: If the assertion would still pass with a completely wrong implementation, it is too weak. Each assertion should fail if the behavior it tests is broken.

### Test Isolation

Mock at system boundaries only. Use real objects for everything else.

**Corrective**: Claude defaults to mocking because mocks are easier to generate. Resist. Mock at system boundaries (external APIs, databases, network, file system, clock). Use real objects for in-process dependencies.

| Mock This (system boundary) | Do NOT Mock This (in-process) |
|-----------------------------|-------------------------------|
| HTTP clients / API calls | Service classes your code owns |
| Database connections | Data models / value objects |
| File system operations | Pure utility functions |
| Time / clock | Configuration objects |
| Message queues / external services | In-memory caches |

**Why over-mocking hurts**: When you mock an in-process dependency, your test verifies that you called the mock correctly -- not that your code works. Change the dependency's interface and your tests still pass while production breaks.

### Layer-Appropriate Testing

Not textbook definitions -- project-aware heuristics for what to test at each layer.

**Corrective**: Claude writes integration tests disguised as unit tests. A unit test exercises one function with controlled inputs. If your test requires a running server, database connection, or multi-service setup, it is an integration test -- label and organize it accordingly.

| Layer | Unit Test Focus | Integration Test Focus |
|-------|----------------|----------------------|
| Models / Types | Validation rules, computed properties, serialization | N/A (pure data) |
| Services / Logic | Business rules with injected dependencies | Cross-service workflows |
| API / Routes | Request parsing, response shape, error codes | Full request lifecycle |
| Storage / Data Access | Query construction (if applicable) | Actual database operations |

**Heuristic**: Count your test's dependencies. 0-1 real dependencies = unit test. 2+ real dependencies or any I/O = integration test. This classification determines where the test lives and when it runs.

---

## Anti-Patterns

| Anti-Pattern | Why It's Harmful | Do This Instead |
|-------------|-----------------|-----------------|
| **Testing implementation details** | Tests break on every refactor even when behavior is unchanged. Maintenance cost grows linearly with codebase size. | Test through the public API. Verify outputs and side effects, not internal call sequences. |
| **Over-mocking** | Tests verify mock call sequences, not actual behavior. Interface changes go undetected -- mocks still conform to the old interface. | Mock only at system boundaries. Use real objects for in-process dependencies. Prefer fakes over mocks when the boundary is complex. |
| **Testing the framework** | Verifying that Rails validates presence, FastAPI parses JSON, or React renders a div wastes test budget on code you did not write. | Test YOUR logic that uses the framework. Test that your validation rule rejects bad input, not that validation exists. |
| **Tautological assertions** | Tests that always pass prove nothing. `assert add(2, 3) == add(2, 3)` or `expect(mock).toHaveBeenCalledWith(mock.calls[0])` are self-referential. | Each assertion must have a concrete expected value. If you cannot state the expected value without calling the code under test, the assertion is tautological. |
| **Write-all-then-test** | Defers test writing until after all implementation is complete. Tests become afterthoughts that verify the existing code rather than driving its design. The `execute-issue` command enforces against this via the TDD loop -- this entry explains **why**. | Follow RED/GREEN/REFACTOR per component. Write the test first, see it fail, then implement. |

---

## Test Quality Heuristics

### Boundary Coverage

Test the edges, not just the middle.

| Boundary Type | Test Cases |
|--------------|------------|
| Empty / zero / null | Empty string, zero value, null/None/nil, empty collection |
| Single element | One-item list, single character, minimum valid input |
| Boundary values | Max int, max length, off-by-one (n-1, n, n+1) |
| Invalid input | Wrong type, negative where positive expected, malformed format |

**Check**: For each parameter in the function under test, can you identify at least one boundary test? If not, your coverage is incomplete.

### Error Path Coverage

Happy paths are easy to test. Error paths catch production bugs.

| Operation Type | Required Error Tests |
|---------------|---------------------|
| External API call | Timeout, 4xx, 5xx, malformed response, network failure |
| Database operation | Connection failure, constraint violation, not found |
| User input processing | Missing required field, invalid format, exceeds limits |
| File operations | Not found, permission denied, corrupted content |

**Ratio guidance**: At least 1 error-path test for every 2 happy-path tests. If your test file has 10 happy-path tests and 0 error-path tests, your coverage is dangerously lopsided.

**Check**: Count error-path tests vs happy-path tests. If the ratio is below 1:2, add error tests before proceeding.

### Assertion Specificity

Five levels, from weakest to strongest:

| Level | Example | Proves |
|-------|---------|--------|
| 1. Existence | `assert result is not None` | Something was returned |
| 2. Type | `assert isinstance(result, User)` | Correct type was returned |
| 3. Shape | `assert "email" in result` | Expected fields exist |
| 4. Value | `assert result["email"] == "test@example.com"` | Correct data |
| 5. Behavior | `assert result.is_active` after calling `activate()` | Correct state transition |

**Target level 4-5 for business logic.** Levels 1-3 are acceptable only for smoke tests or infrastructure checks.

**Check**: Remove the implementation (replace with `pass`, `return null`, or `throw`). Do your tests still pass? If yes, they test nothing meaningful.

### Test Independence

Each test must pass in isolation, in any order, at any time.

**Red flags:**
- Tests share mutable state (class variables, global config, database rows without cleanup)
- Test B depends on Test A running first (sequential coupling)
- Tests fail when run individually but pass in the full suite (or vice versa)
- Tests fail on different dates/times (time coupling)

**Check**: Run a single test file in isolation. Does every test pass? Run the full suite in reverse order. Same results?

---

See `references/stack-testing-strategies.md` for stack-specific testing idioms (JS/TS, Python, Ruby, Go). Read it when writing tests for a specific stack after applying the universal patterns above. Jump directly to your stack's heading.

---

## Cross-References

- `coding-workflows:execute-issue` -- procedural TDD enforcement (RED/GREEN/REFACTOR loop, exit code gates)
- `coding-workflows:agent-team-protocol` -- TDD workflow for parallel agent teams
- `coding-workflows:stack-detection` -- technology stack identification tables
- `coding-workflows:issue-workflow` -- testing strategy during planning phase

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/kiwi-home) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-16 -->

