Skip to content

Commit 1b34fdd

Browse files
authored
feat: using pytest as a test runner, diversify the tests into unit, integration, and performance tests (#1028)
1 parent 0bceaf0 commit 1b34fdd

8 files changed

Lines changed: 96 additions & 48 deletions

File tree

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ repos:
8787
language: python
8888
files: ^src/package/|^tests/
8989
types: [text, python]
90-
args: [--config-file, pyproject.toml]
90+
args: [--explicit-package-bases, --config-file, pyproject.toml]
9191

9292
# Check for potential security issues.
9393
- repo: https://github.com/PyCQA/bandit
@@ -160,14 +160,14 @@ repos:
160160
hooks:
161161
- id: actionlint
162162

163-
# On push to the remote, run the unit tests. Note that the `COVERAGE_CORE` variable is
164-
# required for Python 3.12+ to make sure Coverage uses the new Python monitoring module.
163+
# On push to the remote, run all tests. Note that the `COVERAGE_CORE` variable is required
164+
# for Python 3.12+ to make sure Coverage uses the new Python monitoring module.
165165
# See also: https://blog.trailofbits.com/2025/05/01/making-pypis-test-suite-81-faster/#optimizing-coverage-with-python-312s-sysmonitoring
166166
- repo: local
167167
hooks:
168168
- id: pytest
169169
name: Run unit tests
170-
entry: env COVERAGE_CORE=sysmon pytest -c pyproject.toml --cov-config pyproject.toml src/package/ tests/ docs/
170+
entry: env COVERAGE_CORE=sysmon pytest --config-file pyproject.toml --cov-config pyproject.toml -m 'not integration and not performance' src/package/ tests/ docs/
171171
language: python
172172
verbose: true
173173
always_run: true

Makefile

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,20 +165,27 @@ check-actionlint:
165165
check:
166166
pre-commit run --all-files
167167

168-
# Run all unit tests. The --files option avoids stashing but passes files; however,
169-
# the hook setup itself does not pass files to pytest (see .pre-commit-config.yaml).
170-
.PHONY: test
171-
test:
172-
pre-commit run pytest --hook-stage push --files tests/
168+
# Run different kinds of tests: unit tests, integration tests, performance tests.
169+
# Note that the default goal 'test' runs the unit tests only, mainly for convenience
170+
# and compatibility with existing scripts.
171+
.PHONY: test test-all test-unit test-integration test-performance
172+
test: test-unit
173+
test-unit:
174+
COVERAGE_CORE=sysmon python -m pytest --config-file pyproject.toml --cov-config pyproject.toml -m 'not integration and not performance' src/package/ tests/ docs/
175+
test-integration:
176+
python -m pytest --config-file pyproject.toml --no-cov -m integration tests/
177+
test-performance:
178+
python -m pytest --config-file pyproject.toml --no-cov -m performance tests/
179+
test-all: test-unit test-integration test-performance
173180

174181
# Build a source distribution package and a binary wheel distribution artifact.
175182
# When building these artifacts, we need the environment variable SOURCE_DATE_EPOCH
176183
# set to the build date/epoch. For more details, see: https://flit.pypa.io/en/latest/reproducible.html
177184
.PHONY: dist
178185
dist: dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-md.zip dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-build-epoch.txt
179-
dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl: check test dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-build-epoch.txt
186+
dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl: check test-all dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-build-epoch.txt
180187
SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) flit build --setup-py --format wheel
181-
dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz: check test dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-build-epoch.txt
188+
dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz: check test-all dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-build-epoch.txt
182189
SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) flit build --setup-py --format sdist
183190
dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip: docs-html
184191
python -m zipfile -c dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip docs/_build/html/

README.md

Lines changed: 43 additions & 35 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ test = [
6565
"faker ==38.2.0",
6666
"hypothesis >=6.21.0,<6.148.6",
6767
"pytest >=7.2.0,<9.0.0",
68+
"pytest-benchmark ==5.2.0",
6869
"pytest-cases ==3.9.1",
6970
"pytest-custom_exit_code ==0.3.0",
7071
"pytest-cov ==6.3.0", # Uses: coverage[toml] >=7.5
@@ -258,7 +259,7 @@ max-line-length = 120
258259
# https://github.com/yashtodi94/pytest-custom_exit_code
259260
[tool.pytest.ini_options]
260261
minversion = "7.0"
261-
addopts = """-vv -ra --tb native --durations 0 \
262+
addopts = """-vv -ra --tb native --durations 0 --strict-markers --import-mode importlib \
262263
--hypothesis-show-statistics --hypothesis-explain --hypothesis-verbosity verbose \
263264
--doctest-modules --doctest-continue-on-failure --doctest-glob '*.rst' --doctest-plus \
264265
--suppress-no-test-exit-code \
@@ -281,3 +282,7 @@ filterwarnings = [
281282
"error::pytest.PytestUnraisableExceptionWarning",
282283
"error::pytest.PytestUnhandledThreadExceptionWarning",
283284
]
285+
markers = [
286+
"integration: more complex application-level integration tests.",
287+
"performance: performance tests.",
288+
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Test the Package itself using its external interface as in integration into a larger run context."""
2+
3+
# https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess
4+
import subprocess # nosec B404
5+
6+
import pytest
7+
8+
9+
@pytest.mark.integration
10+
def test_package() -> None:
11+
"""Test the Something command."""
12+
# For testing we disable this warning here:
13+
# https://bandit.readthedocs.io/en/latest/plugins/b603_subprocess_without_shell_equals_true.html
14+
# https://bandit.readthedocs.io/en/latest/plugins/b607_start_process_with_partial_path.html
15+
completed = subprocess.run(["something"], check=True, shell=False) # nosec B603, B607
16+
assert completed.returncode == 0
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Test the performance of various package parts, or the package as a whole."""
2+
3+
import pytest
4+
from pytest_benchmark.fixture import BenchmarkFixture
5+
6+
from package.something import Something
7+
8+
9+
@pytest.mark.performance
10+
def test_something(benchmark: BenchmarkFixture) -> None:
11+
"""Test performance of the function."""
12+
benchmark.pedantic(Something.do_something, iterations=10, rounds=100) # type: ignore[no-untyped-call]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Test the Something module. Add more tests here, as needed."""
1+
"""Test the Something module as a unit test. Add more tests here, as needed."""
22

33
import faker
44
from hypothesis import given, strategies

0 commit comments

Comments
 (0)