PythonLevel 6 · Testing & QualityLesson 46

L6 · 46

CI Basics — GitHub Actions for pytest

Pre-commit guards your machine; CI guards the whole team. Push a branch and a fresh cloud machine runs your linters, type checks and tests — giving every PR a green tick or a red cross before anyone merges.

⏱ 1 hour☁️ CI lesson📚 After PY-L6-45💻 GitHub
01

Learning Goals

3 min
  • Explain Continuous Integration and why CI beats "works on my machine".
  • Write a GitHub Actions workflow that installs deps and runs pytest.
  • Add lint + type-check + coverage steps.
  • Read the CI result and use it as a merge gate.
02

Warm-Up · A Robot That Runs Your Tests

5 min
you:    git push
GitHub: spins up a FRESH cloud machine
        → installs Python + your dependencies
        → runs ruff, mypy, pytest
        → reports ✅ pass or ❌ fail on the commit / PR
Today's big idea

CI runs your checks on a clean machine in the cloud, every push. It catches "works on my machine but I forgot to commit a file" and "passes for me but not on Python 3.11". A red CI blocks the merge — so broken code never reaches the main branch.

03

New Concept · A GitHub Actions Workflow

14 min

Where it lives

yourrepo/
└─ .github/
    └─ workflows/
        └─ ci.yml      ← GitHub runs this automatically on push/PR

A minimal CI workflow

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]      # run on every push and PR

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4            # get the code
      - uses: actions/setup-python@v5        # install Python
        with:
          python-version: "3.12"
      - run: pip install -e ".[dev]"         # install deps (incl. test tools)
      - run: ruff check .                    # lint
      - run: ruff format --check .           # formatting
      - run: mypy myapp                       # types
      - run: pytest --cov=myapp --cov-fail-under=80   # tests + coverage gate

Read the anatomy

on:        what triggers the run (push, PR, schedule)
jobs:      one or more jobs, each on a fresh machine
runs-on:   the OS image (ubuntu-latest, etc.)
steps:     ordered commands; if ANY step fails, the job fails (❌)

Test on multiple Python versions (matrix)

jobs:
  test:
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - run: pip install -e ".[dev]"
      - run: pytest
# → runs the whole suite on 3 Python versions in parallel

The merge gate

In GitHub branch-protection settings, require the CI check to pass before merging. Now a red CI literally blocks the merge button — the team can't ship broken code even by accident.

04

Worked Example · A Full CI Pipeline

12 min
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: pip
      - name: Install
        run: pip install -e ".[dev]"
      - name: Lint
        run: ruff check .
      - name: Format check
        run: ruff format --check .
      - name: Type check
        run: mypy myapp
      - name: Tests + coverage
        run: pytest --cov=myapp --cov-report=term-missing --cov-fail-under=80
# on GitHub, the PR shows:
✅ CI / quality (3.11)
✅ CI / quality (3.12)
All checks have passed — merge enabled.

# or, if a test breaks:
❌ CI / quality (3.12) — pytest failed
Merging is blocked until checks pass.

Read the diff

Every push now triggers lint → format-check → types → tests-with-coverage, on two Python versions, on clean cloud machines. The PR gets a clear pass/fail; merging is blocked on red. This is the same pipeline you ran locally (Lesson 43's make check) — now enforced for the whole team, automatically, forever. CI is the capstone that ties together everything in this level.

05

Try It Yourself

13 min
01 🟢 First workflow

Add .github/workflows/ci.yml that checks out, installs deps, and runs pytest. Push and watch the Actions tab go green.

02 🟡 Add the quality steps

Add ruff, format-check, and mypy steps. Push a deliberately failing commit and confirm CI goes red.

03 🔴 Matrix + gate

Add a Python-version matrix and a coverage threshold. In repo settings, require the CI check to pass before merging. Open a PR and confirm a red CI blocks the merge button.

06

Mini-Challenge · A Status Badge

8 min

Add a CI status badge to your README so anyone sees the build health at a glance. (GitHub: Actions → your workflow → "Create status badge".) Confirm it shows green when tests pass and red when they fail.

Show the badge markdown
![CI](https://github.com/USER/REPO/actions/workflows/ci.yml/badge.svg)

A green badge on a portfolio repo signals "this person tests their code" — a real plus when sharing projects.

07

Recap

3 min

CI runs your checks on a fresh cloud machine on every push. A GitHub Actions workflow (.github/workflows/ci.yml) checks out, installs, lints, type-checks, and tests — optionally across a version matrix. Require it as a merge gate so broken code can't reach main. CI + pre-commit + tests = the full quality system. Next: the capstone — bring a real app to 90% coverage.

Vocabulary Card

CI
Continuous Integration — automatically building/testing on every change.
workflow
A YAML file defining CI jobs and steps (GitHub Actions).
matrix
Running the same job across several configs (Python versions, OSes).
merge gate
A required passing check before a PR can be merged.
08

Homework

4 min

Add a complete CI workflow to a GitHub repo: install, ruff, format-check, mypy, pytest with coverage, on a 2-version matrix. Make it pass. Add a status badge to the README. Bonus: enable branch protection so the check is required before merge.