Consistent, fast, and repeatable testing for the PyPNM codebase using pytest.
PyPNM follows a standard src/ + tests/ layout:
- Application code:
src/pypnm/... - Tests:
tests/
Typical patterns:
- Unit and parsing tests:
tests/test_*.py - PNM file parsing tests:
tests/test_pnm_*.py - Integration tests (cable modem SNMP):
tests/test_cable_modem_*.py(gated by markers and env vars)
pytest automatically discovers tests that follow these naming conventions.
pytest is configured via pyproject.toml under [tool.pytest.ini_options]. Key settings:
[tool.pytest.ini_options]
minversion = "8.0"
pythonpath = ["src"]
testpaths = ["tests"]
addopts = "-ra -q --strict-markers --tb=short -m 'not cm_it'"
asyncio_mode = "auto"
log_cli = true
log_cli_level = "INFO"
log_cli_format = "%(levelname)s %(name)s:%(lineno)d | %(message)s"
log_cli_date_format = "%H:%M:%S"
markers = [
"asyncio: mark test as asyncio-based (requires pytest-asyncio)",
"cm_it: cable modem integration tests (enable with -m cm_it)",
"slow: slow tests",
"net: network-required tests",
"pnm: PNM file parsing tests",
]Implications:
pythonpath=["src"]lets tests importpypnmwithout extra sys.path hacks.testpaths=["tests"]keeps discovery limited and fast.addoptsby default:-rashort summary of skipped/xfailed tests-qquiet output (per-test lines suppressed)--strict-markersenforces marker registration--tb=shortcompact tracebacks-m 'not cm_it'skips cable-modem integration tests by default
asyncio_mode="auto"enables seamless async tests usingpytest-asyncio.- CLI logging is enabled at
INFOwith a consistent format.
All commands assume you are in the project root (for example ~/Projects/PyPNM) and have the dev dependencies installed:
pip install -e '.[dev]'Run the standard test suite (unit + parsing + markers, no cm_it integration tests):
pytestThis uses the addopts defined in pyproject.toml.
Single file:
pytest tests/test_pnm_histogram_parse.pySingle test within a file:
pytest tests/test_pnm_histogram_parse.py::test_hist_parses_and_model_shapeTo temporarily override -q and get per-test lines, append -v:
pytest -vYou can combine this with explicit paths or markers as needed.
PyPNM uses markers to group tests by behavior and dependencies. All markers are registered in pyproject.toml and enforced via --strict-markers.
| Marker | Purpose | Example usage |
|---|---|---|
pnm |
PNM file parsing and model-shape tests | pytest -m pnm |
asyncio |
Async tests using pytest-asyncio |
pytest -m asyncio -v |
net |
Tests requiring live network connectivity | pytest -m net |
slow |
Long-running or heavy tests | pytest -m slow -v |
cm_it |
Cable-modem integration tests (SNMP, hardware) | pytest -m cm_it |
Combine markers with boolean expressions:
# All PNM parsing tests that are not slow
pytest -m "pnm and not slow"
# All network tests including their detailed output
pytest -m net -vThe default addopts excludes cm_it, so you must explicitly opt in to those tests.
Hardware integration tests are guarded by:
- Marker:
cm_it - An environment variable:
PNM_CM_IT=1
To run them:
export PNM_CM_IT=1
pytest -m cm_it -vTypical behavior:
- If
PNM_CM_ITis not set, tests will be skipped with a message such as:Hardware integration disabled. Set PNM_CM_IT=1 to run. - The default suite (
pytestwithaddopts) uses-m 'not cm_it', so these tests never run accidentally.
Use these tests sparingly (for example, before a release or when validating hardware-related changes).
PyPNM includes pytest-asyncio in its development dependencies and sets asyncio_mode="auto". This supports:
async deftest functions- Async fixtures
A typical async test looks like:
import pytest
@pytest.mark.asyncio
async def test_snmp_async_round_trip(snmp_client) -> None:
result = await snmp_client.get("sysDescr.0")
assert "LANCity" in resultKey points:
- The
@pytest.mark.asynciomarker is optional whenasyncio_mode="auto", but it is declared as a marker for clarity and selection. - Async tests are discovered and run like any other test.
Coverage is configured via [tool.coverage.*] in pyproject.toml. To run tests with coverage information:
pytest --cov=pypnm --cov-report=term-missingThis:
- Measures coverage over the
pypnmpackage - Shows a terminal report with missing lines per file
You can add --cov-report=html to generate an HTML report:
pytest --cov=pypnm --cov-report=term-missing --cov-report=htmlThe HTML report is usually written to htmlcov/index.html and can be opened in a browser.
The pypnm-software-qa-checker helper runs pytest as part of the QA suite. A typical invocation:
pypnm-software-qa-checkerBy default, this will:
- Run
ruff checkfor static analysis - Run
pytestwith the configuration described above - Run
pycycle --herefor import cycle detection
You can use that script as a single entry point before committing or pushing changes.
-
Ensure the virtual environment is active.
-
Confirm that
pytestis installed:pip show pytest
-
If needed, reinstall dev dependencies:
pip install -e '.[dev]'
If you see PytestUnknownMarkWarning or marker errors:
- Ensure markers are registered in
pyproject.tomlundermarkers. - Because
--strict-markersis enabled, any new marker must be added there. - Re-run pytest after updating
pyproject.toml.
If async tests fail due to event-loop issues:
- Verify that
pytest-asynciois installed in your environment. - Ensure you are not manually configuring another asyncio plugin that conflicts with
asyncio_mode="auto".
- pytest documentation
- pytest-cov documentation
pysnmpandpysmidocumentation for SNMP integration specifics.