github-actions

GitHub Actions Python Tests

GitHub Actions workflow to run Python tests with pytest across multiple versions, dependency caching, and coverage.

Overview

A GitHub Actions workflow for running Python tests on every push and pull request. Tests across multiple Python versions with dependency caching, linting with ruff, type checking with mypy, and coverage reporting.

Configuration

# .github/workflows/test.yml

name: Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

concurrency:
  group: test-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: Lint & Type Check
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip                   # Cache pip packages

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff mypy
          pip install -r requirements.txt

      - name: Run ruff linter
        run: ruff check .

      - name: Run ruff formatter check
        run: ruff format --check .

      - name: Run mypy type checking
        run: mypy src/ --ignore-missing-imports

  test:
    name: Python ${{ matrix.python-version }}
    runs-on: ubuntu-latest
    needs: lint                        # Run after linting passes

    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.10", "3.11", "3.12"]

    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: testdb
        ports:
          - 5432:5432
        options: >-
          --health-cmd "pg_isready -U test"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Run tests with coverage
        run: |
          pytest \
            --cov=src \
            --cov-report=xml:coverage.xml \
            --cov-report=html:htmlcov \
            --junitxml=reports/test-results.xml \
            -v --tb=short
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          PYTHONPATH: src

      - name: Upload coverage to Codecov
        if: matrix.python-version == '3.12'
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: coverage.xml
          fail_ci_if_error: false

      - name: Upload test artifacts
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-py${{ matrix.python-version }}
          path: |
            reports/
            htmlcov/
            coverage.xml
          retention-days: 7

      - name: Check coverage threshold
        if: matrix.python-version == '3.12'
        run: |
          coverage report --fail-under=80

Key Options Explained

  • cache: pip — Caches the pip download cache between runs. Combined with pip install, this speeds up dependency installation significantly.
  • ruff check and ruff format — Ruff is a fast Python linter and formatter that replaces flake8, isort, and black. Running it first catches issues before slower tests.
  • --cov-report=xml — Generates a Codecov-compatible XML coverage report. The HTML report is for local debugging via artifacts.
  • --junitxml — Produces JUnit XML test results that GitHub can parse for the Actions summary page.
  • --fail-under=80 — Fails the job if test coverage drops below 80%, enforcing minimum coverage standards.
  • needs: lint — Tests only run after linting passes, saving compute time when there are obvious code quality issues.

Common Modifications

  • Poetry: Replace pip commands with pip install poetry then poetry install --with dev. Use poetry run pytest for test execution.
  • Django: Add python manage.py migrate --noinput before pytest and set DJANGO_SETTINGS_MODULE in env.
  • Tox: Replace the test step with pip install tox and tox -e py${{ matrix.python-version }} for environment-specific testing.
  • Pre-commit: Add pip install pre-commit && pre-commit run --all-files in the lint job to validate all hooks.
  • Security scanning: Add pip install safety && safety check -r requirements.txt to scan for known vulnerabilities.