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 withpip install, this speeds up dependency installation significantly.ruff checkandruff 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 poetrythenpoetry install --with dev. Usepoetry run pytestfor test execution. - Django: Add
python manage.py migrate --noinputbefore pytest and setDJANGO_SETTINGS_MODULEin env. - Tox: Replace the test step with
pip install toxandtox -e py${{ matrix.python-version }}for environment-specific testing. - Pre-commit: Add
pip install pre-commit && pre-commit run --all-filesin the lint job to validate all hooks. - Security scanning: Add
pip install safety && safety check -r requirements.txtto scan for known vulnerabilities.