A pytest plugin that enforces test quality standards through automatic marker detection and AAA structure validation
Project description
Pytest Drill Sergeant
You want elite tests? Then stop writing lazy chaos and start writing disciplined test code.
This plugin is your no-excuses drill instructor for:
- marker classification
- AAA structure (
Arrange/Act/Assert) - file-length discipline
It does not care about feelings. It cares about standards.
Mission Profile
1. Marker Rule
What it does:
- validates useful test markers
- auto-detects marker intent from path (for example
tests/unit/->@pytest.mark.unit) - supports custom directory-to-marker mappings
- reads marker declarations from both
pytest.iniandpyproject.toml([tool.pytest.ini_options])
What you do:
- put tests in intentional directories
- keep marker declarations real
- stop shipping unclassified tests
2. AAA Rule
What it does:
- enforces explicit AAA section comments in test bodies
- supports two modes:
basic: section presence requiredstrict: presence + order + no duplicate section declarations
- supports built-in/custom synonyms when enabled
Accepted grammar:
# <Keyword> - <description>
Examples:
# Arrange - create test fixture
# Act - call the function
# Assert - verify expected behavior
3. File-Length Rule
What it does:
- enforces max test file length
- supports modes:
error: failwarn: report onlyoff: disabled
- supports path exclusions and inline ignore token
Inline ignore example:
# drill-sergeant: file-length ignore
Use this sparingly. If you need it everywhere, the file should be split.
Quick Start
Install
uv add --group dev pytest-drill-sergeant
Minimal pytest.ini
[pytest]
addopts = -p drill_sergeant
markers =
unit: Unit tests
integration: Integration tests
drill_sergeant_enabled = true
drill_sergeant_enforce_markers = true
drill_sergeant_enforce_aaa = true
drill_sergeant_enforce_file_length = true
drill_sergeant_marker_severity = error
drill_sergeant_aaa_severity = error
drill_sergeant_aaa_mode = basic
drill_sergeant_auto_detect_markers = true
drill_sergeant_max_file_length = 350
Minimal Passing Test
import pytest
@pytest.mark.unit
def test_addition() -> None:
# Arrange - prepare operands
left = 2
right = 3
# Act - run operation
total = left + right
# Assert - validate result
assert total == 5
Configuration
Precedence (highest to lowest):
- environment variables
- pytest config (
pytest.inior[tool.pytest.ini_options]) [tool.drill_sergeant]inpyproject.toml- plugin defaults
pyproject.toml Example
[tool.drill_sergeant]
enabled = true
enforce_markers = true
enforce_aaa = true
aaa_mode = "basic"
marker_severity = "error"
aaa_severity = "error"
enforce_file_length = true
file_length_mode = "error"
file_length_exclude = ["tests/legacy/*"]
file_length_inline_ignore = true
file_length_inline_ignore_token = "drill-sergeant: file-length ignore"
auto_detect_markers = true
min_description_length = 3
max_file_length = 350
aaa_synonyms_enabled = false
aaa_builtin_synonyms = true
[tool.drill_sergeant.marker_mappings]
contract = "api"
smoke = "integration"
Environment Variables
DRILL_SERGEANT_ENABLEDDRILL_SERGEANT_ENFORCE_MARKERSDRILL_SERGEANT_ENFORCE_AAADRILL_SERGEANT_MARKER_SEVERITY(error|warn|off)DRILL_SERGEANT_AAA_SEVERITY(error|warn|off)DRILL_SERGEANT_AAA_MODEDRILL_SERGEANT_ENFORCE_FILE_LENGTHDRILL_SERGEANT_FILE_LENGTH_MODEDRILL_SERGEANT_FILE_LENGTH_EXCLUDEDRILL_SERGEANT_FILE_LENGTH_INLINE_IGNOREDRILL_SERGEANT_FILE_LENGTH_INLINE_IGNORE_TOKENDRILL_SERGEANT_AUTO_DETECT_MARKERSDRILL_SERGEANT_MIN_DESCRIPTION_LENGTHDRILL_SERGEANT_MAX_FILE_LENGTHDRILL_SERGEANT_MARKER_MAPPINGSDRILL_SERGEANT_DEBUG_CONFIGDRILL_SERGEANT_DEBUG_TELEMETRY(prints per-validator timing summary at session end)
Return Type Policy
Return-type annotation enforcement is intentionally handled by static tooling, not runtime test hooks.
Use Ruff ANN rules:
uv run ruff check --fix src tests
CI Contract
Required gates:
uv run pytest -quv run ruff check src testsuv run mypy src tests --config-file=pyproject.toml
Local parity command:
just verify
Coverage is informational, not a required merge gate.
Failure Intel
If a rule fails, use:
docs/Failure-Catalog.mdfor failure-to-fix mappingdocs/Decision-Log.mdfor scope decisions and rationaledocs/Release-Checklist.mdfor release executionSTABILIZATION_PLAN.mdfor phased recovery status
Development
Common commands:
just verify
just test
just lint
just type-check
Release Flow
Releases are managed by release-please:
- Push conventional commits to
main(feat:,fix:, etc.). Release Pleaseworkflow opens/updates a release PR.- Merge the release PR to create a GitHub Release + tag.
Production Release (PyPI)publishes to PyPI on release publish event.
Final Word
The point is not ceremony. The point is predictable, readable, maintainable test code under pressure.
Do the basics with discipline and your test suite will stop betraying you.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_drill_sergeant-0.3.0.tar.gz.
File metadata
- Download URL: pytest_drill_sergeant-0.3.0.tar.gz
- Upload date:
- Size: 22.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f39e1805c9600f3c432c44fdac0910df5a34ad413d0332890ab708d358004bc1
|
|
| MD5 |
9d951de7477653f07ea5ecbf163f9296
|
|
| BLAKE2b-256 |
3958b4f21c0fc8c45eb488645e4ef89152b9b0cab5ac781fb675dd48f0838afe
|
File details
Details for the file pytest_drill_sergeant-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pytest_drill_sergeant-0.3.0-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee27966f8f94a169422067c2901bf5387711093581f2abf24e7707f17055bf10
|
|
| MD5 |
3c174039880275eafded69c358bd0e86
|
|
| BLAKE2b-256 |
ed9575edbe0bb6ecaa827f561be77a34eb79673f6bdc5622ce939d965f721921
|