TDD for Python
Enforces the Red-Green-Refactor cycle when adding new code to a Python project. No production code is written until a failing test exists that demands it.
When to Use This Skill
- User asks to implement a new function, class, or module in Python
- User mentions "TDD", "test-first", or "red-green-refactor"
- User says "write tests before implementation" or "use TDD"
- User opens a GitHub issue and asks to implement it with tests
Prerequisites
pytestinstalled (check withpytest --version; install viapip install pytest)- An existing test directory (e.g.,
tests/) with at least an__init__.pyorconftest.py, or a flat test file layout
Step-by-Step Workflows
Red-Green-Refactor Cycle
Follow these three phases strictly and in order for every new piece of code.
Phase 1 — Red: Write a Failing Test
- Identify the smallest testable behaviour to implement next.
Ask: "What is the simplest input/output or state change this code must produce?" - Create or open the relevant test file (e.g.,
tests/test_<module>.py). - Write one test that:
- Has a clear, behaviour-describing name:
test_<action>_<condition>_<expected_result> - Imports the symbol to be created (it does not exist yet — that is intentional)
- Contains a single
assertor a focused set of related assertions - Uses
pytest.raisesfor expected exceptions
- Has a clear, behaviour-describing name:
- Run the test suite and confirm it fails (red):
The failure must be because the production code is missing, not because the test itself is broken.pytest tests/ -v - Do not write any production code yet.
Phase 2 — Green: Write the Minimum Production Code
- Open (or create) the production module referenced by the test.
- Write the simplest code that makes the failing test pass — nothing more.
- Hard-coding a return value is acceptable if it makes the test green; a later test will force a real implementation.
- Do not add logic, parameters, or abstractions not demanded by the current test.
- Run the suite again and confirm all tests pass (green):
pytest tests/ -v - If other tests break, fix only what was broken by the new code; do not touch unrelated tests.
Phase 3 — Refactor: Improve Without Changing Behaviour
- Review the production code for duplication, poor naming, or structural issues.
- Review the test code for the same.
- Apply improvements in small, safe steps.
- After every change, run the full suite to confirm nothing regressed:
pytest tests/ -v - When all tests are still green and the code is clean, the cycle is complete.
Starting a New Feature (Multiple Cycles)
Repeat the Red-Green-Refactor cycle for each behaviour:
- List all expected behaviours as plain-English bullet points before writing any code.
- Pick the simplest one and run one complete Red-Green-Refactor cycle.
- Pick the next simplest behaviour and repeat.
- Continue until all behaviours are implemented and tested.
Gotchas
- Never write production code before a failing test exists. If the test passes on the first run, either the test is wrong or the feature already exists.
- One failing test at a time. Resist the urge to write all tests upfront and then implement everything. Keep the cycle tight and fast.
- Failing for the right reason. A
ModuleNotFoundErrororImportErroris acceptable red — it means the module does not exist yet. ASyntaxErrorin the test itself means fix the test first. - Hard-coding is not cheating in the Green phase. The next test will force generalisation. This is by design (triangulation).
- Do not refactor in the Red phase. Refactoring is only safe when all tests are green.
- Commit after each Green phase so you always have a known-good checkpoint to return to.
Troubleshooting
| Issue | Solution |
|---|---|
| Test passes immediately (stays green) | Check whether the feature already exists, or whether the test actually exercises the right code path |
ImportError on the symbol under test |
Expected in Red phase — create the module/function stub to unblock the import, then confirm the test fails for the right reason |
| Many tests fail after a small change | The production code change was too large; revert to last green commit and take a smaller step |
| Refactoring breaks tests | Undo the refactor, run tests, confirm green, then retry the refactor in a smaller increment |
pytest not found |
Run pip install pytest or pip install -e .[dev] if a pyproject.toml with a dev extra is present |