Adds or updates pytest coverage for a Python change by first discovering the repository's existing test layout and conventions, matching them rather than imposing a new structure, deciding whether the change requires a test at all, and running the suite in a bounded verify loop. Use when a Python change adds behavior, fixes a bug, changes a public interface, or touches security-relevant logic, and when deciding where a new test belongs in an unfamiliar repository.
Add pytest coverage that fits the repository it lands in. Most test damage comes from writing
tests before reading the ones already there: a second fixture style, a parallel directory layout,
or a mocking convention the project deliberately avoids. This skill orders the work as discover,
decide, write, verify.
When to use this
A Python change adds behavior, fixes a bug, or changes a public interface.
A change touches security-relevant logic (input validation, authorization, crypto, secrets).
Deciding where a test belongs in a repository whose layout is unfamiliar.
When NOT to use this
Non-Python changes.
Repositories that use a test framework other than pytest. Follow what is there instead; do not
introduce pytest alongside an existing framework.
Steps
Discover the existing layout before writing anything. Do not assume a structure.
Find the test root: tests/, test/, alongside the source as test_*.py, or inside the
package. Check pyproject.toml, pytest.ini, setup.cfg, and tox.ini for testpaths,
python_files, addopts, and marker definitions.
Read two or three existing tests near the code being changed. Note the naming pattern, how
fixtures are shared (conftest.py, factory functions, plain constructors), whether
parametrization is used, and what the project mocks versus exercises for real.
Check for markers (slow, integration, network) and what the default run excludes.
Decide whether a test is required. See the table below. If a test is not required, say so
and why, rather than silently skipping it.
Write the test in the discovered style. Match the existing naming, fixture, and assertion
conventions. Do not introduce a new helper layer, a new mocking library, or a new directory
when the repository already has an answer.
Assert on behavior and public interfaces, not on internal call sequences, unless the call
itself is the contract.
For a bug fix, write the test so it fails against the unfixed code. Confirm that it does
before applying the fix, or by reverting the fix once.
Keep each test independent: no shared mutable state, no ordering assumptions, no reliance on
network or wall-clock time.
Keep the machine out of the test. A home-directory path, username, hostname, or real email
address baked into a fixture, an expected value, or a recorded snapshot is both a test that
only passes on one machine and information the repository has no reason to publish. Use
tmp_path, monkeypatch, and placeholders, and normalize captured paths before asserting
on them or committing a snapshot.
Run the suite in the bounded verify loop below.
Follow instructions/python_coding_instructions.md for the test code itself. Test files are
source, and the same ruff/ty gate applies to them.
When a test is required versus optional
Change
Test
New function, class, or public interface
Required
Bug fix
Required, and it must fail without the fix
Changed behavior of existing code
Required, updating the existing test rather than adding a parallel one
Input validation, authorization, crypto, or secret handling
Required, including the rejection and failure paths
Refactor with no behavior change
Not required; existing tests must pass unchanged, and that is the evidence
Formatting, comments, docstrings, type annotations
Not required
Generated code or vendored dependencies
Not required unless the repository already tests them
For anything else, ask what would have to break for the change to be wrong, and whether an
existing test would catch it.
Verify
Run the repository's own entry point, not a bare pytest invocation, when one exists: a tox
env, a Makefile target, or the command in .github/workflows/*.yml. Through the package manager
where one is configured, for example uv run pytest.
The full suite passes, not only the new tests.
The new test fails against the unfixed or unchanged code, for a bug fix or a behavior change.
Coverage tooling, if the repository has it configured, shows no drop. Do not add a coverage
tool that is not already there.
The bounded loop
One attempt is one full fix-and-rerun cycle: apply fixes for the failures from the previous
run, then rerun the suite to completion. Reading output, or re-reading a file without changing
anything, is not an attempt.
Baseline the loop at 3 attempts.
Continue past 3 only while making measurable progress, meaning each cycle ends with strictly
fewer failures than the one before it.
Stop early, before 3 attempts, if the loop is oscillating: the same failures recur, the count
stops dropping, or a fix for one failure reintroduces another.
When stopping for either reason, report to the user rather than proceeding or silently giving
up. Name the failing test, include its output, and state what was tried.
Never weaken a test, mark it xfail, or skip it to get a green run. If a test is wrong, fix the
test and say why it was wrong.
No hook enforces that rule here. instructions/agent_configuration_instructions.md covers
which rules need a mechanism rather than prose alone, and where one belongs.
Verification checklist
Existing test layout and conventions read before writing, and matched
No new test framework, directory, or mocking library introduced alongside an existing one
Test required by the table above was written, or its absence explained
For a bug fix, the test was confirmed to fail without the fix
Full suite run through the repository's own entry point, to a clean result or to a stop
under the loop rules above, with failures reported
ruff check, ruff format --check, and ty check clean on the test files too
No test weakened, skipped, or marked xfail to obtain a green run
Tests are independent of ordering, network access, and wall-clock time
No home-directory path, username, hostname, or real email address in test code, fixtures, or
committed snapshots; anything machine-specific is generated or normalized
References
Paths starting instructions/ are relative to this library's root. When this skill is installed
as a Claude Code plugin, read them at ${CLAUDE_PLUGIN_ROOT}/instructions/, which resolves to the
installed copy.
instructions/python_coding_instructions.md: the ruff/ty baseline, which applies to test
code as well.
instructions/agent_configuration_instructions.md: choosing between an instruction and a hook,
for the rules above that must hold every time rather than most of the time.
skills/python/python-secure-coding/SKILL.md: for security-relevant changes, whose rejection
and failure paths need coverage.
1---2name: python-testing3description: Adds or updates pytest coverage for a Python change by first discovering the repository's existing test layout and conventions, matching them rather than imposing a new structure, deciding whether the change requires a test at all, and running the suite in a bounded verify loop. Use when a Python change adds behavior, fixes a bug, changes a public interface, or touches security-relevant logic, and when deciding where a new test belongs in an unfamiliar repository.4---56# python-testing78## Purpose910Add pytest coverage that fits the repository it lands in. Most test damage comes from writing11tests before reading the ones already there: a second fixture style, a parallel directory layout,12or a mocking convention the project deliberately avoids. This skill orders the work as discover,13decide, write, verify.1415## When to use this1617- A Python change adds behavior, fixes a bug, or changes a public interface.18- A change touches security-relevant logic (input validation, authorization, crypto, secrets).19- Deciding where a test belongs in a repository whose layout is unfamiliar.2021## When NOT to use this2223- Non-Python changes.24- Repositories that use a test framework other than pytest. Follow what is there instead; do not25 introduce pytest alongside an existing framework.2627## Steps28291. **Discover the existing layout before writing anything.** Do not assume a structure.30 - Find the test root: `tests/`, `test/`, alongside the source as `test_*.py`, or inside the31 package. Check `pyproject.toml`, `pytest.ini`, `setup.cfg`, and `tox.ini` for `testpaths`,32 `python_files`, `addopts`, and marker definitions.33 - Read two or three existing tests near the code being changed. Note the naming pattern, how34 fixtures are shared (`conftest.py`, factory functions, plain constructors), whether35 parametrization is used, and what the project mocks versus exercises for real.36 - Check for markers (`slow`, `integration`, `network`) and what the default run excludes.372. **Decide whether a test is required.** See the table below. If a test is not required, say so38 and why, rather than silently skipping it.393. **Write the test in the discovered style.** Match the existing naming, fixture, and assertion40 conventions. Do not introduce a new helper layer, a new mocking library, or a new directory41 when the repository already has an answer.42 - Assert on behavior and public interfaces, not on internal call sequences, unless the call43 itself is the contract.44 - For a bug fix, write the test so it fails against the unfixed code. Confirm that it does45 before applying the fix, or by reverting the fix once.46 - Keep each test independent: no shared mutable state, no ordering assumptions, no reliance on47 network or wall-clock time.48 - Keep the machine out of the test. A home-directory path, username, hostname, or real email49 address baked into a fixture, an expected value, or a recorded snapshot is both a test that50 only passes on one machine and information the repository has no reason to publish. Use51 `tmp_path`, `monkeypatch`, and placeholders, and normalize captured paths before asserting52 on them or committing a snapshot.534. **Run the suite in the bounded verify loop below.**545. Follow `instructions/python_coding_instructions.md` for the test code itself. Test files are55 source, and the same `ruff`/`ty` gate applies to them.5657## When a test is required versus optional5859| Change | Test |60|---|---|61| New function, class, or public interface | Required |62| Bug fix | Required, and it must fail without the fix |63| Changed behavior of existing code | Required, updating the existing test rather than adding a parallel one |64| Input validation, authorization, crypto, or secret handling | Required, including the rejection and failure paths |65| Refactor with no behavior change | Not required; existing tests must pass unchanged, and that is the evidence |66| Formatting, comments, docstrings, type annotations | Not required |67| Generated code or vendored dependencies | Not required unless the repository already tests them |6869For anything else, ask what would have to break for the change to be wrong, and whether an70existing test would catch it.7172## Verify7374Run the repository's own entry point, not a bare `pytest` invocation, when one exists: a `tox`75env, a Makefile target, or the command in `.github/workflows/*.yml`. Through the package manager76where one is configured, for example `uv run pytest`.7778- The full suite passes, not only the new tests.79- The new test fails against the unfixed or unchanged code, for a bug fix or a behavior change.80- Coverage tooling, if the repository has it configured, shows no drop. Do not add a coverage81 tool that is not already there.8283### The bounded loop8485One **attempt** is one full fix-and-rerun cycle: apply fixes for the failures from the previous86run, then rerun the suite to completion. Reading output, or re-reading a file without changing87anything, is not an attempt.8889- Baseline the loop at 3 attempts.90- Continue past 3 only while making measurable progress, meaning each cycle ends with strictly91 fewer failures than the one before it.92- Stop early, before 3 attempts, if the loop is oscillating: the same failures recur, the count93 stops dropping, or a fix for one failure reintroduces another.94- When stopping for either reason, report to the user rather than proceeding or silently giving95 up. Name the failing test, include its output, and state what was tried.9697Never weaken a test, mark it `xfail`, or skip it to get a green run. If a test is wrong, fix the98test and say why it was wrong.99100No hook enforces that rule here. `instructions/agent_configuration_instructions.md` covers101which rules need a mechanism rather than prose alone, and where one belongs.102103## Verification checklist104105- [ ] Existing test layout and conventions read before writing, and matched106- [ ] No new test framework, directory, or mocking library introduced alongside an existing one107- [ ] Test required by the table above was written, or its absence explained108- [ ] For a bug fix, the test was confirmed to fail without the fix109- [ ] Full suite run through the repository's own entry point, to a clean result or to a stop110 under the loop rules above, with failures reported111- [ ] `ruff check`, `ruff format --check`, and `ty check` clean on the test files too112- [ ] No test weakened, skipped, or marked `xfail` to obtain a green run113- [ ] Tests are independent of ordering, network access, and wall-clock time114- [ ] No home-directory path, username, hostname, or real email address in test code, fixtures, or115 committed snapshots; anything machine-specific is generated or normalized116117## References118119Paths starting `instructions/` are relative to this library's root. When this skill is installed120as a Claude Code plugin, read them at `${CLAUDE_PLUGIN_ROOT}/instructions/`, which resolves to the121installed copy.122123- `instructions/python_coding_instructions.md`: the `ruff`/`ty` baseline, which applies to test124 code as well.125- `instructions/agent_configuration_instructions.md`: choosing between an instruction and a hook,126 for the rules above that must hold every time rather than most of the time.127- `skills/python/python-secure-coding/SKILL.md`: for security-relevant changes, whose rejection128 and failure paths need coverage.
Run npx skillmds@latest add konstruktoid/python-testing in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Adds or updates pytest coverage for a Python change by first discovering the repository's existing test layout and conventions, matching them rather than imposing a new structure, deciding whether the change requires a test at all, and running the suite in a bounded verify loop. Use when a Python change adds behavior, fixes a bug, changes a public interface, or touches security-relevant logic, and when deciding where a new test belongs in an unfamiliar repository. It is listed under Security on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
konstruktoid (@konstruktoid) published this skill. Their other Agent Skills are listed on their SkillMD profile.