python-tdd
Generate focused, behavior-driven Python tests using TDD methodology with pytest. TRIGGER WHEN: writing Python tests, improving coverage, reviewing test quality, or practicing red-green-refactor workflows with pytest. DO NOT TRIGGER WHEN: the target is non-Python (use testing:tdd for language-agnostic TDD or testing:e2e-testing-patterns for browser E2E).
What this skill does
# Python Testing Patterns
Generate focused, behavior-driven tests with pytest. Prioritize observable behavior over implementation details. For framework configuration (pyproject.toml, plugins, CI), see `references/framework-config.md`. For deep conftest patterns and heavy-dep mocking, see `references/pytest-infrastructure.md`. For full TDD discipline, see `references/tdd-best-practices.md`.
## 1. Test Philosophy
### What to test
- Observable behavior and return values
- User-facing workflows and API contracts
- Edge cases and error handling at system boundaries
- State transitions and side effects
- Data validation and transformation rules
### What NOT to test
- Framework internals (pytest, SQLAlchemy, FastAPI mechanics)
- Implementation details (private methods, internal variable names)
- Third-party library behavior
- Simple utility functions with no branching logic
- That objects are truthy -- this asserts nothing useful
### Mocking strategy
**DO mock**: External API calls / HTTP requests, database queries in unit tests, file system operations, time-dependent behavior (`datetime.now`, `time.time`), environment variables.
**DON'T mock**: Your own models and dataclasses, simple utility functions, the function under test itself, framework features you rely on.
### Test count discipline
- Max **10 tests** per file for simple modules.
- Max **15 tests** per file for complex modules.
- More than that = the module is too complex; suggest splitting it.
## 2. TDD Workflow
### Red-Green-Refactor
1. **RED** -- write a failing test. Must fail for the right reason (NOT import error, NOT syntax error).
2. **GREEN** -- write the minimal code to make it pass. No more.
3. **REFACTOR** -- improve code while all tests stay green. No new behavior.
### Coverage targets
| Priority | What | Target |
|----------|------|--------|
| P0 | Critical paths (auth, payments, data integrity) | 100% line + branch |
| P1 | Core business logic + public API | 90%+ line |
| P2 | Utilities, helpers, config | 80%+ line |
Overall: 80%+ line coverage; 100% on critical paths.
## 3. Naming Convention (BDD)
**Class-based grouping** -- nest by feature, then scenario:
```python
class TestUserService:
class TestCreateUser:
def test_should_create_when_valid_data(self): ...
def test_should_raise_when_email_exists(self): ...
```
**Flat function naming** -- `test_<action>_should_<outcome>_when_<condition>`:
```python
def test_create_user_should_succeed_when_valid_data(): ...
def test_login_should_fail_when_password_expired(): ...
```
## 4. AAA Structure (Arrange-Act-Assert)
```python
def test_transfer_should_debit_sender_when_sufficient_funds():
# Arrange
sender = make_account(balance=100)
receiver = make_account(balance=50)
# Act
transfer(sender, receiver, amount=30)
# Assert
assert sender.balance == 70
assert receiver.balance == 80
```
## 5. Reusable Fakes via Factory Fixtures
Prefer factory fixtures in `conftest.py` over raw dicts:
```python
# conftest.py
@pytest.fixture
def make_user():
def _make_user(name="Test User", email="[email protected]", active=True):
return User(name=name, email=email, active=active)
return _make_user
def test_deactivate_user(make_user):
user = make_user(active=True)
user.deactivate()
assert user.active is False
```
## 6. Core Patterns (the few worth memorizing)
### Fixtures -- narrowest scope possible
```python
@pytest.fixture
def db_session():
session = SessionLocal()
yield session
session.rollback()
session.close()
@pytest.fixture(scope="module") # or scope="session" for engine
def api_client(): yield TestClient(app)
```
Share via `conftest.py` -- pytest auto-discovers.
### Parametrization with custom IDs
```python
@pytest.mark.parametrize("input_val,expected", [
pytest.param("[email protected]", True, id="valid-email"),
pytest.param("no-at-sign.com", False, id="missing-at"),
pytest.param("", False, id="empty-string"),
])
def test_is_valid_email(input_val, expected):
assert is_valid_email(input_val) == expected
```
### Mocking -- the only patterns you need
```python
@patch("myapp.services.requests.get") # patch where USED (top-level import)
def test_fetch_user(mock_get):
mock_get.return_value.json.return_value = {"id": 1}
result = fetch_user(1)
mock_get.assert_called_once_with("https://api.example.com/users/1")
@patch("myapp.services.UserRepository", autospec=True) # autospec catches signature drift
def test_repo_call(MockRepo): ...
@patch("myapp.client.requests.get", side_effect=ConnectionError("timeout"))
def test_network_error(mock_get):
with pytest.raises(ServiceUnavailableError):
fetch_data("https://api.example.com/data")
```
### The lazy-import gotcha (the rule that catches everyone)
When a function imports inside another function (lazy import), it is NOT a module-level attribute at the usage site. **Patch at the definition site, not the usage site.**
```python
# WRONG -- AttributeError: module has no attribute 'get_db'
monkeypatch.setattr("myapp.services.processor.get_db", mock_db)
# CORRECT -- patch where get_db is DEFINED
monkeypatch.setattr("myapp.database.get_db", mock_db)
```
Rule: if `monkeypatch.setattr` raises `AttributeError`, check whether the target is a lazy import. If so, patch the module where the function is defined.
### Async tests
```python
@pytest.mark.asyncio
async def test_async_fetch():
result = await async_fetch("https://api.example.com")
assert result["status"] == "ok"
```
Set `asyncio_mode = "auto"` in `pyproject.toml` to skip the `@pytest.mark.asyncio` decorator.
### Property-based with hypothesis
```python
from hypothesis import given, strategies as st
@given(st.integers(min_value=0, max_value=1000))
def test_deposit_increases_balance(amount):
account = Account(balance=0)
account.deposit(amount)
assert account.balance == amount
```
### Temporary files / env
```python
def test_export(tmp_path): # auto-cleaned
output = tmp_path / "report.csv"
export_report(output, [{"name": "Alice"}])
assert "Alice" in output.read_text()
def test_db_url(monkeypatch):
monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test")
assert get_database_url() == "postgresql://localhost/test"
```
## 7. Anti-Patterns
| Anti-pattern | Why it's bad | Do this instead |
|--------------|--------------|-----------------|
| `assert obj is not None` alone | Asserts nothing about behavior | Assert on a specific attribute or return value |
| Mocking the function under test | Tests nothing real | Mock its dependencies instead |
| 40+ tests for a simple module | Sign of over-testing or bloated module | Split module or consolidate parametrized tests |
| Testing framework internals | Validates pytest/SQLAlchemy, not your code | Test your logic through public API |
| Copy-pasting mock setup in every test | Fragile, hard to maintain | Extract into fixtures or factory functions |
| Testing private methods directly | Couples tests to implementation | Test through the public interface |
| Catching exceptions inside test code | Swallows real failures silently | Use `pytest.raises` as context manager |
| No assertions in test body | Test always passes, proves nothing | Every test must assert something |
| Asserting on `mock.called` only | Doesn't verify correct arguments | Use `assert_called_once_with(expected_args)` |
| Hardcoded golden values (`== 660`) | Breaks when algorithm improves, not when behavior is wrong | Assert invariants, use `pytest.approx`, derive from inputs |
| Heavy mocks in sub-directory conftest | Root tests load real deps first; mocks too late | Place ALL heavy dependency mocks in root `tests/conftest.py` |
| Missing markers on heavy-dep tests | Tests break silently when deps are mocked by default | Mark `@pytest.mark.slow`, use `--strict-markers` |
| Incomplete external serRelated in Writing & Docs
jax-development
IncludedUse this skill when the user is writing, debugging, profiling, refactoring, reviewing, benchmarking, parallelising, exporting, or explaining JAX code, or when they mention JAX, jax.numpy, jit, grad, value_and_grad, vmap, scan, lax, random keys, pytrees, jax.Array, sharding, Mesh, PartitionSpec, NamedSharding, pmap, shard_map, Pallas, XLA, StableHLO, checkify, profiler, or the JAX repo. It helps turn NumPy or PyTorch-style code into pure functional JAX, fix tracer/control-flow/shape/PRNG bugs, remove recompiles and host-device syncs, choose transforms and sharding strategies, inspect jaxpr/lowering/IR, and benchmark compiled code correctly.
nature-article-writer
IncludedDrafts, rewrites, diagnostically critiques, and style-calibrates primary research manuscripts for Nature and Nature Portfolio journals. Use when the user wants a Nature-style title, summary paragraph or abstract, introduction, results, discussion, methods, figure legends, presubmission enquiry, cover letter, reviewer response, or when a scientific draft sounds generic, jargon-heavy, structurally weak, or AI-ish and needs precise, broad-reader-friendly prose without inventing data, analyses, or references. Best for primary research articles and letters rather than reviews or press releases unless explicitly adapting one.
deckrd
IncludedDocument-driven framework that derives requirements, specifications, implementation plans, and executable tasks from goals through structured AI dialogue. Use when user says "write requirements", "create spec", "plan implementation", "derive tasks", "structure this feature", "break down into tasks", or "document this module". Also use for reverse engineering existing code into docs (/deckrd rev). Do NOT use for direct code writing — use /deckrd-coder after tasks are generated. Do NOT use when the user only wants to run or fix existing code without planning.
clinical-decision-support
IncludedGenerate professional clinical decision support (CDS) documents for pharmaceutical and clinical research settings, including patient cohort analyses (biomarker-stratified with outcomes) and treatment recommendation reports (evidence-based guidelines with decision algorithms). Supports GRADE evidence grading, statistical analysis (hazard ratios, survival curves, waterfall plots), biomarker integration, and regulatory compliance. Outputs publication-ready LaTeX/PDF format optimized for drug development, clinical research, and evidence synthesis.
handling-sf-data
IncludedSalesforce data operations with 130-point scoring. Use this skill to create, update, delete, bulk import/export, generate test data, and clean up org records using sf CLI and anonymous Apex. TRIGGER when: user creates test data, performs bulk import/export, uses sf data CLI commands, needs data factory patterns for Apex tests, or needs to seed/clean records in a Salesforce org. DO NOT TRIGGER when: SOQL query writing only (use querying-soql), Apex test execution (use running-apex-tests), or metadata deployment (use deploying-metadata).
accelint-ac-to-playwright
IncludedConvert and validate acceptance criteria for Playwright test automation. Use when user asks to (1) review/evaluate/check if AC are ready for automation, (2) assess if AC can be converted as-is, (3) validate AC quality for Playwright, (4) turn AC into tests, (5) generate tests from acceptance criteria, (6) convert .md bullets or .feature Gherkin files to Playwright specs, (7) create test automation from requirements. Handles both bullet-style markdown and Gherkin syntax with JSON test plan generation and validation.