ワンクリックで
usethis-python-test
General guidelines for writing tests in the usethis project, including test class organization
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
General guidelines for writing tests in the usethis project, including test class organization
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Guidelines for Python code design decisions such as when to share vs. duplicate code
Create a lesson from a development difficulty, covering root cause analysis, principle generalisation, and filing as a GitHub issue
Write bespoke prek hooks as reusable Python scripts for custom checks
Check whether docs/config-files.txt (the machine-readable export) is in-sync with the per-tool tables in docs/about/config-files.md
Enforce version bumping, scope checking, and content quality guidelines when modifying SKILL.md files
Modify Python code (e.g. refactor, add new code, or delete code)
| name | usethis-python-test |
| description | General guidelines for writing tests in the usethis project, including test class organization |
| compatibility | usethis, Python, pytest |
| license | MIT |
| metadata | {"version":"1.6"} |
Use the usethis-python-test-full-coverage skill when you need to measure and verify test coverage. This skill covers general test organization and conventions.
Tests are organized into nested pytest classes that mirror the structure of the code being tested. Understanding and following this convention is essential when adding new tests.
Each public function or class in a module gets its own top-level test class in the corresponding test file. Name it Test<FunctionOrClassName>.
For example, if a module defines add_deps_to_group() and remove_deps_from_group(), the test file should have TestAddDepsToGroup and TestRemoveDepsFromGroup as separate top-level classes.
Within a top-level test class, use nested classes to organize tests by sub-concern. Common nesting patterns include:
TestMyClass contains TestAdd and TestRemove for add/remove operations on that class.TestIgnoreRules contains TestWhenRulesAreNew, TestWhenSomeRulesAlreadyIgnored, etc.TestAddDepsToGroup contains TestPoetry and/or TestUv for backend-specific behavior of the same function.When adding tests for a new variant of existing functionality (such as Poetry backend support for a function that already has uv tests), nest the variant tests inside the existing top-level test class. Do not create a new standalone top-level class.
For example, to add Poetry backend tests for add_default_groups():
TestPoetry nested class inside the existing TestAddDefaultGroups.TestAddDefaultGroupsPoetry class.This keeps all tests for the same function grouped together, making it easy to see all variants at a glance and ensuring consistent test coverage across backends.
Nesting can go two or three levels deep when the logical structure demands it. For example:
TestCodespell → TestAdd → test_config()TestPyprojectFmt → TestAdd → TestDeps → test_added()Use the minimum depth needed to clearly communicate the test's context. Avoid nesting beyond three levels.
Tests for a class method must live in the test file that corresponds to the source file where the class is defined — not in a test file for a utility that the method happens to call internally.
If a method is defined on SomeClass in some_module.py, its tests belong under TestSomeClass in test_some_module.py, regardless of which helper utilities the method uses at runtime.
Placing tests near the utility they exercise (rather than near the class they belong to) breaks the structural correspondence between source and test files, making tests harder to find and maintain.
Avoid adding docstrings to test classes and test functions when the name alone is sufficient. For example, """Tests for validate_or_raise.""" on TestValidateOrRaise is redundant — the class name already communicates this.
However, a docstring is appropriate when it adds genuinely new information that the name does not convey, such as explaining the test strategy, describing a non-obvious constraint, or clarifying how the test output is validated.
files_manager in testsThe files_manager() context manager defers all configuration file writes until the context exits. This has important implications for how tests are structured, especially when subprocess calls are involved.
files_manager before a subprocessAny subprocess that reads configuration files from the filesystem (e.g. ruff, pytest, pre-commit, deptry, codespell) will not see in-memory changes made inside a files_manager() context. You must exit the context (flush writes to disk) before running the subprocess.
# Correct: exit files_manager before the subprocess reads config from disk
with change_cwd(tmp_path), files_manager():
use_ruff()
call_uv_subprocess(["run", "ruff", "check", "."], change_toml=False)
# Wrong: subprocess runs inside the context, but config hasn't been flushed yet
with change_cwd(tmp_path), files_manager():
use_ruff()
call_uv_subprocess(["run", "ruff", "check", "."], change_toml=False)
# ruff may not see the configuration written by use_ruff()
Multiple usethis function calls that operate through FileManager-based access (not subprocesses) can safely share a single files_manager() context. They see each other's uncommitted in-memory changes via get() and commit().
# Safe: both functions use FileManager access, no subprocess involved
with change_cwd(tmp_path), files_manager():
use_ruff()
use_deptry()
# Both tools' config changes are visible to each other in memory
use_* functions, get_deps_from_group, assertions on config state): safe to combine in one context.call_uv_subprocess, subprocess.run, call_subprocess): require an atomic write first, so exit the files_manager context before running them.When a test captures console output (via capfd.readouterr() or similar), always assert the complete, exact output string using equality (assert out == "..."), not a substring check (assert "..." in out).
Substring checks allow partial output to pass silently: they do not detect missing messages or unexpected extra messages. Exact equality catches both problems immediately.
Console output often depends on the detected package manager backend. Always set the backend explicitly in tests that assert console output, so the result is fully deterministic:
with usethis_config.set(backend=BackendEnum.uv):
use_some_tool()
out, err = capfd.readouterr()
assert out == "✔ Some message.\n"
When multiple messages are printed, assert the entire combined string at once:
assert out == (
"✔ First message.\n"
"☐ Second message.\n"
)
# noqa: RUF001 for ambiguous Unicode charactersSome console icons (e.g. ℹ, ×) trigger the Ruff RUF001 rule ("ambiguous Unicode character"). Place the # noqa: RUF001 comment on the line that contains the character:
assert out == "ℹ Some info message.\n" # noqa: RUF001
Exact equality is not appropriate for Rich table output (from table_print or show_usage_table), where the rendered string depends on terminal width and Rich's internal formatting. For table tests, substring or structural checks are acceptable.
When adding @functools.cache to a function:
tests/conftest.py.<function_name>.cache_clear() call in the body of the clear_functools_caches autouse fixture.The clear_functools_caches autouse fixture runs before each test, ensuring every test starts with a clean cache. Without this registration, a test that triggers caching will leave stale values in memory that silently affect subsequent tests, causing order-dependent failures that are hard to diagnose.
When a feature has two or more independent dimensions of variation, tests that vary only one dimension at a time are necessary but not sufficient. Always include at least one test that exercises non-trivial values on multiple axes simultaneously.
A feature that composes two independent behaviours (e.g. path traversal depth and value nesting depth) may work correctly for each behaviour in isolation while failing when both are exercised together. The interaction between dimensions is a distinct code path that single-axis tests cannot reach.
For a function that sets a value at a key path, the two axes are:
["tool"] vs. ["tool", "ruff"])"pep257" vs. {"select": ["A"], "pydocstyle": {"convention": "pep257"}})Single-axis tests cover multiple keys with a scalar value, and a single key with a nested dict value. The cross-axis test uses multiple keys and a multiply-nested dict value together.
When the code under test only reads path metadata (.name, .suffix, .parent) and does not access the filesystem, use string paths via usethis_config.set(project_dir="/fake/dir-name") instead of creating real directories with tmp_path and mkdir().
Real directory creation couples tests to OS-specific path normalisation rules. On Windows, certain directory names are invalid or silently rewritten:
"..." is interpreted as parent directory traversal (../..), causing FileExistsError."project.") are silently stripped.CON, NUL, PRN are forbidden.These restrictions are irrelevant when the function never touches the filesystem — they only introduce cross-platform test failures.
Pass a string path directly to the configuration context manager. The project_dir parameter accepts str, which is converted to a Path internally:
# Correct: synthetic path, no filesystem dependency
def test_leading_dot(self):
with usethis_config.set(project_dir="/fake/.github-private"):
assert get_project_name_from_dir() == "github-private"
# Wrong: creates an unnecessary real-path dependency via tmp_path
def test_leading_dot(self, tmp_path: Path):
with usethis_config.set(project_dir=tmp_path / ".github-private"):
assert get_project_name_from_dir() == "github-private"
If the code under test calls filesystem operations (e.g. is_dir(), exists(), mkdir(), reads or writes files), real directories via tmp_path are appropriate and necessary.