Testing in Continuous Integration
The CI is the pipeline that runs the suite on every push: the pytest the job, the --junitxml the report the platform parses, the -n auto the parallel the time cuts, the matrix (the python-version) the version's guard, and the two lanes (the unit's fast, the integration's dedicated). The exit code is the pipeline's signal (the 0 the green, the 1 the red, the 2 the collection, the 5 the no-tests), and the fail-under the coverage's gate. This chapter covers the GitHub Actions job (the pytest, the --junitxml, the upload), the matrix (the Python version, the OS), the pytest-xdist parallel (the -n, the loadfile), and the coverage gate (the --cov-fail-under) the pipeline enforces.
The GitHub Actions Job
# the .github/workflows/test.yml (the push's
# the trigger, the every PR)
name: test
on: [push, pull_request]
jobs:
unit:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev]"
- run: >
pytest -m "not integration" -n auto
--junitxml=report/junit.xml
--cov=texttools --cov-report=xml
--cov-fail-under=80
- uses: actions/upload-artifact@v4
if: always()
with:
name: junit-${{ matrix.python-version }}
path: report/
The job is the steps: the checkout, the setup-python (the matrix version, the 3.10 to the 3.12 the guard), the pip install -e ".[dev]" (the package plus the test deps, the repeatable), and the pytest the command (the -m "not integration" the unit lane, the -n auto the parallel, the --junitxml the report, the --cov plus the --cov-fail-under=80 the gate). The upload-artifact (the if: always() - the failure's report kept) is the junit.xml the platform parses (the <testcase>/<failure> the per-item, the PR's check the red/green). The exit code is the signal: the 0 the green, the 1 the red (the test or the coverage), the 2 the collection (the import error, the misconfig), the 5 the no-tests (the testpaths the wrong, the pipeline must catch) - the four the pipeline branches on, and the next section's parallel is the time's cut.
The xdist Parallel and the Coverage Gate
# the XDIST (the parallel, the time's cut):
pytest -n auto # the CPU's workers
pytest -n 4 --dist=loadfile # the per-file (the
# session the no
# cross-worker)
# the the limit: the shared state (the the
# the tmp_path_factory, the session DB are the
# the per-worker - the no cross-worker share)
# the COVERAGE GATE (the total below -> the exit 1):
pytest --cov=texttools --cov-fail-under=80
# the total 73% -> the FAILED (the below 80)
# the TWO LANES (the unit's fast, the integration's):
pytest -m "not integration" -n auto # the unit
pytest -m integration # the integration
The pytest-xdist is the time's cut: the -n auto spawns the CPU's workers (the parallel, the suite's wall-time divided), and the --dist=loadfile keeps the file within one worker (the session fixture the no cross-worker - the database the one worker, the no two DBs). The limit is the shared state: the tmp_path_factory and the session DB are the per-worker (the no cross-worker share), so the loadfile is the rule for the suite with the session fixture. The coverage gate is the --cov-fail-under=80 (the total below the 1 exit, the red pipeline on the green suite) - the coverage.xml the artifact, the badge the floor's guard. The two lanes are the -m "not integration" (the unit, the fast, the every push) and the -m integration (the real DB, the container, the nightly or the PR's slow job) - the split the mark makes, and the rule is the one: the CI runs the pytest the job, the --junitxml the report, the -n the parallel, the matrix the version, and the fail-under the coverage's gate.
The Working Rules
- The job is the steps, the
pytestthe command. The checkout, thesetup-python(the matrix3.10to the3.12), thepip install -e ".[dev]", and thepytest -m "not integration" -n auto --junitxml=... --cov --cov-fail-under=80- theupload-artifacttheif: always()the failure's report. - The exit code is the pipeline's signal. The
0the green, the1the red (the test or the coverage), the2the collection (the import error), the5the no-tests (thetestpathsthe wrong) - the four the branch, the5the misconfig the must-catch. - The
-n autois the parallel, theloadfilethe session's guard. The CPU's workers the time's cut, the--dist=loadfilethe file stays in one worker (the session DB the no cross-worker), and thetmp_path_factorythe per-worker (the no share). - The
fail-underis the coverage's gate, the two lanes the mark's split. The--cov-fail-under=80the total below the1exit (the green suite, the red pipeline), the-m "not integration"the fast unit, the-m integrationthe dedicated job - the unit every push, the integration the nightly.
Summary
The CI is the pipeline that runs the suite on every push: the pytest the job (the checkout, the setup-python the matrix 3.10 to the 3.12, the pip install -e ".[dev]"), the --junitxml=report/junit.xml the report the platform parses (the <testcase>/<failure>), and the upload-artifact the if: always() the failure's report kept. The exit code is the signal (the 0/1/2/5 the green, the red, the collection, the no-tests), the pytest-xdist -n auto the parallel (the --dist=loadfile the session fixture's guard, the per-worker the no cross-worker), and the --cov-fail-under=80 the coverage gate (the total below the 1 exit). The two lanes are the -m "not integration" (the unit, the fast, the every push) and the -m integration (the container's DB, the dedicated job). The next chapter's best practices are the suite's rules - the naming, the isolation, the AAA, the no-sleep, the fixture-first - the CI runs what the practice writes.