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.
Warm-Up · A Robot That Runs Your Tests
5 minyou: git push
GitHub: spins up a FRESH cloud machine
→ installs Python + your dependencies
→ runs ruff, mypy, pytest
→ reports ✅ pass or ❌ fail on the commit / PRCI 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.
New Concept · A GitHub Actions Workflow
14 minWhere it lives
yourrepo/
└─ .github/
└─ workflows/
└─ ci.yml ← GitHub runs this automatically on push/PRA 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 gateRead 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 parallelThe 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.
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.
Try It Yourself
13 minAdd .github/workflows/ci.yml that checks out, installs deps, and runs pytest. Push and watch the Actions tab go green.
Add ruff, format-check, and mypy steps. Push a deliberately failing commit and confirm CI goes red.
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.
Mini-Challenge · A Status Badge
8 minAdd 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

A green badge on a portfolio repo signals "this person tests their code" — a real plus when sharing projects.
Recap
3 minCI 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.
Homework
4 minAdd 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.
Use the full ci.yml from the worked example. The badge + branch protection turn it into a real team quality gate.