Skillversitas

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 pytest the command. The checkout, the setup-python (the matrix 3.10 to the 3.12), the pip install -e ".[dev]", and the pytest -m "not integration" -n auto --junitxml=... --cov --cov-fail-under=80 - the upload-artifact the if: always() the failure's report.
  • The exit code is the pipeline's signal. The 0 the green, the 1 the red (the test or the coverage), the 2 the collection (the import error), the 5 the no-tests (the testpaths the wrong) - the four the branch, the 5 the misconfig the must-catch.
  • The -n auto is the parallel, the loadfile the session's guard. The CPU's workers the time's cut, the --dist=loadfile the file stays in one worker (the session DB the no cross-worker), and the tmp_path_factory the per-worker (the no share).
  • The fail-under is the coverage's gate, the two lanes the mark's split. The --cov-fail-under=80 the total below the 1 exit (the green suite, the red pipeline), the -m "not integration" the fast unit, the -m integration the 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.