Module 8: Project A Ci Pipeline For Reservo
8. Project: a CI pipeline for Reservo
Description
The concert has arrived. For eight modules you rehearsed each section separately; in this module you brought them together, layer by layer, over Reservo's suite. Now you play them all at once in a single deliverable: Reservo's complete CI pipeline, woven from the six layers you assembled —the base workflow (lesson 2), the reproducibility (lesson 3), the matrix (lesson 4), the speed (lesson 5), the coverage gate (lesson 6), and the flaky policy (lesson 7)— plus the proof that it does what it says.
This is the formal project of the capstone, and its evaluation is different from everything before: it is judged by the method, not by the number of tests. It does not matter whether your suite has fourteen tests or two hundred; it matters that each decision of the pipeline is justified —why this matrix, why this threshold, why -n auto or why not, what you do with a flaky—. A pipeline with six tests and six well-defended decisions is worth more than one with two hundred tests and none. You are going to produce two deliverables: the complete tests.yml, commented decision by decision, and the local parity setup (check.sh), a script that runs on your machine exactly what the CI runs on the runner. And you are going to evaluate yourself against a rubric of method.
This lesson, moreover, closes the guide. After the project comes the review of the eight modules —the complete map of what you learned— and the path toward where to continue, with the sibling guides of the testing ecosystem. The rehearsal is over; play the concert and then the map of what follows.
Connection with the module: this lesson integrates the previous seven. Lesson 1 gave you the blueprint and the stage↔problem table; lessons 2 through 7 assembled each layer in its place; the project weaves them into a single file and asks you to defend them. It is the practical exam, not the written one: nobody tells you which layer comes at each moment; you decide it, with judgment, and justify it. And as the closing of the guide, it looks outward: to the sibling guides that teach what this one, on purpose, did not teach —how to write the tests, how to diagnose a failure, how to think about the strategy—.
The practical driving exam, with the instructor beside you
Think of getting your driver's license. The written exam proves that you know the rules: what a sign means, at what distance you brake. The practical exam proves something different: that you can apply them all at once, in real traffic, without anyone telling you which rule comes at each moment. You get in the car, the instructor sits beside you, and you drive —coordinating the wheel, the pedals, the mirrors, the signals— in a single integrated task where everything matters simultaneously.
Lessons 1 through 7 were the written exam: each one taught you a layer and tested you on it separately. This project is the practical one. You get in the car and assemble a real pipeline, making yourself the decisions that were previously given to you. Which versions in the matrix? Which coverage threshold? -n auto yes or no? Which flaky policy? Nobody dictates it to you; you decide it with what you learned, and you defend it. And as in the practical exam, what is evaluated is not theoretical perfection but integrated competence: at the end, a tests.yml that protects Reservo, that reads well, whose decisions you can justify one by one, and that —verified with the local parity— does exactly what it says.
The project is the practical exam: applying the six layers at once, making yourself the decisions, and defending each one. What is evaluated is not the number of tests, but the method —why each piece of the pipeline is the way it is—.
The assignment
You are responsible for Reservo's CI as a library —you publish it so that other teams install it—. Its pyproject.toml declares requires-python = ">=3.11" and you promise to support 3.11, 3.12, and 3.13. Reservo is pure logic (integer arithmetic, dates; no network, no disk, no DB), with a suite of fourteen tests in tests/ and the version-dependent feature report_pages. Your work, the two deliverables:
Deliverable 1 — the complete tests.yml. A single workflow that weaves the six layers: triggers on push and PR (lesson 2), runs on the matrix of three versions (lesson 4) with reproducible installation from requirements-dev.txt (lesson 3), caches the dependencies (lesson 5), applies the coverage gate (lesson 6), and has a flaky policy coherent with the suite (lesson 7). Commented decision by decision.
Deliverable 2 — the local parity (check.sh). A short script that runs on your machine the same installation, the same suite, and the same gate that the CI runs, with its real output as evidence. Its value: it turns the CI from "where I discover that something was wrong" into "the confirmation of something I already know is right."
And a decision note for each layer: why this matrix and not another, why this threshold, why -n auto or why not, which flaky policy. That note is what the rubric evaluates. Try each deliverable on your own before looking at the reference solution; the learning is in building it yourself.
The rubric: the method is evaluated
Before the solution, the rubric, because it defines what a good pipeline is here. There are no points for "many tests" or "many stages turned on." There are points for justified decisions:
| Criterion | What is evaluated | Sign that it is right |
|---|---|---|
| Layer coverage | Are the six layers present and in place? | Push+PR, matrix, pinned deps, cache, gate, flaky policy — none missing, none superfluous without reason. |
| Matrix justification | Does the size of the matrix respond to a real risk? | Three versions = the README's promise; a single OS row = pure logic. Not 3×3 by reflex. |
| Defensible threshold | Is the gate where it bites without demanding the impossible? | Threshold below the real coverage, with cushion; neither 100% (fetish) nor 50% (decorative). |
| Speed with judgment | Does it always cache, parallelize only if it pays off? | Cache yes; -n auto decided with the suite's measurement, not by reflex. |
| Honest flaky policy | Is there a policy, and is it not "global retry"? | Per-test patch + ticket + root; never permanent global --reruns. |
| Local parity executed | Did you run on your machine what the CI runs? | check.sh with real green output; the YAML is content, the run is evidence. |
| Scope honesty | Do you say what the pipeline protects and what it does not? | Scope note that acknowledges what is left out, without selling it as "complete." |
Notice what is not in the rubric: the number of tests, the lines of YAML, the number of cells. A pipeline is evaluated by whether its decisions protect what Reservo really risks, with the minimum of cost and noise. That is the craft.
The five steps of the project
Step 1 — Weave the tests.yml. Start from the base workflow of lesson 2 and stack: the matrix of 4, the pinned installation of 3, the cache of 5, the gate of 6. Comment each block with the lesson it comes from.
Step 2 — Write check.sh. The parity script: upgrade pip, install from requirements-dev.txt, run the suite with the coverage gate. The same commands as the CI, in your terminal.
Step 3 — Run the local parity. Execute check.sh (or its commands) for real and capture the output. This is the real evidence; the YAML is content.
Step 4 — Decide the flaky policy. Reservo today has no flaky (it is deterministic). Write the policy for when they appear, coherent with what the suite is.
Step 5 — Write the decision notes. One per layer, justifying the why. It is what the rubric weighs.
Reference solution
Deliverable 1 — The complete tests.yml, commented
# .github/workflows/tests.yml
name: tests
# Layer 1 (lesson 2): triggers on every push and every pull request.
# push = feedback on every change; pull_request = guardian of the merge to main.
on: [push, pull_request]
jobs:
test:
# Layer 3 (lesson 4): a single operating-system row.
# Reservo is pure logic -> gives identical results on Linux/macOS/Windows,
# so testing on 3 OSes would be cost and noise without catching an extra bug.
runs-on: ubuntu-latest
strategy:
# Being a library, we want the COMPLETE MAP of versions, not to cancel
# at the first red: fail-fast off.
fail-fast: false
matrix:
# Layer 3 (lesson 4): exactly what the README promises.
# Not one version too many (reflex), nor one too few (untested promise).
python-version: ["3.11", "3.12", "3.13"]
steps:
# Layer 1 (lesson 2): first bring the code, or nothing works.
- name: Check out the code
uses: actions/checkout@v5
# Layer 1 + 4: installs the version of THIS matrix cell.
# Layer 4 (lesson 5): cache: pip avoids re-downloading deps on each run;
# the key is tied to requirements-dev.txt, so it is rebuilt only
# when the dependencies change (honest cache).
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: requirements-dev.txt
# Layer 2 (lesson 3): reproducible installation from a pinned list.
# requirements-dev.txt brings pytest + the CI tools (cov, xdist,
# rerunfailures), all with == so that the runner installs identically to you.
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
# Layer 5 (lesson 6): the coverage gate BREAKS the build if it drops below 85%.
# --cov-branch also measures the branches (if/else), not just the lines: it is what
# fills the Branch/BrPart columns and makes the real 88% comparable.
# Layer 4 (lesson 5): -n auto stays as a ready-to-scale form; today the
# suite is 0.02s and the parallelism only pays off when it grows (see note).
# No global --reruns: the flaky policy is per-test (lesson 7).
- name: Run the suite with the coverage gate
run: |
python -m pytest -n auto \
--cov=reservo --cov-branch --cov-report=term-missing \
--cov-fail-under=85
And the two dependency lists the workflow uses:
# requirements.txt (production: Reservo is pure stdlib)
pytest==9.1.1
# requirements-dev.txt (development + CI: production + tools)
pytest==9.1.1
pytest-cov==7.1.0
pytest-xdist==3.8.0
pytest-rerunfailures==16.4
Each line of the YAML points to its lesson. The whole workflow is the stage↔problem table of lesson 1, made file: on solves "I forgot to run the tests" (M2); the pinned requirements-dev.txt solves "it works on my machine" (M3); the matrix solves "it works in my version" (M4); cache: pip solves "the CI takes long" (M5); --cov-fail-under solves "code arrived without tests" (M6); the absence of global --reruns is the honest flaky policy (M7).
Deliverable 2 — The local parity script (check.sh)
#!/usr/bin/env bash
# check.sh — runs on your machine THE SAME thing as the CI, so as not to have surprises.
# Local parity: same installation, same suite, same coverage gate.
set -e # aborts at the first command that fails, as the runner would
# Same commands as the workflow's "Install dependencies" step.
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
# Same "Run the suite with the coverage gate" step, in SERIES:
# Reservo's suite is 0.02s, and -n auto there only adds overhead
# (lesson 5). The pipeline keeps -n auto as a ready-to-scale form;
# here we measure the gate, which is what decides green/red.
python -m pytest --cov=reservo --cov-branch --cov-report=term-missing --cov-fail-under=85
The decision to run check.sh in series (without -n auto) while the pipeline has it in place is exactly lesson 5 applied with honesty: on a 0.02s suite, -n auto would make it slower, so locally you do not use it; the YAML's -n auto is the ready form for when the suite grows, whose real effect you already measured (4.07s → 1.19s on a slow suite). The parity that matters —the one that decides green or red— is the installation and the coverage gate, and those are identical.
Deliverable 3 — The local parity, really executed
./check.sh
What to expect (real output on Python 3.14.0 with pytest 9.1.1; the coverage-gate section):
================================ tests coverage ================================
Name Stmts Miss Branch BrPart Cover Missing
-----------------------------------------------------------------
reservo/__init__.py 0 0 0 0 100%
reservo/calendar.py 9 1 0 0 89% 15
reservo/models.py 9 0 0 0 100%
reservo/pricing.py 5 0 2 0 100%
reservo/refunds.py 7 0 4 0 100%
reservo/reports.py 7 2 2 1 67% 14-16
reservo/schedule.py 20 3 8 2 82% 15, 16->13, 40-41
-----------------------------------------------------------------
TOTAL 57 6 16 3 88%
Required test coverage of 85% reached. Total coverage: 87.67%
========================= 13 passed, 1 skipped in 0.03s =========================
echo "exit code: $?"
exit code: 0
The evidence, pointed out:
13 passed, 1 skipped— the complete suite, with the expected skip of thereport_pagesfallback (on 3.14, ≥ 3.12). The thirteen greens include the six anchor numbers (7500, 6000, 2500 and the refunds 6000, 3000, 0).TOTAL ... 88%andRequired test coverage of 85% reached— the gate passed: 88% ≥ 85%. The 67% gap inreports.pyis the branch of another version (covered by the 3.11 cell), not a defect.exit code: 0— green. On the runner, this zero would paint each matrix cell green; with branch protection, the merge is enabled. A nonzero exit (from a broken test or from coverage below the threshold) would block it.
This run is the parity made evidence: you have just executed, with your hands, what the three cells of the pipeline would execute. The only difference from the runner is the platform line (darwin locally vs. linux on the runner) and that here it is one version (3.14) where the runner would run three (3.11/3.12/3.13).
Deliverable 4 — The flaky policy
# Flaky test policy (Reservo README)
Reservo today is deterministic (pure logic, no network/disk/time/concurrency),
so it has no flaky and the pipeline does NOT use global --reruns. When external
dependencies are integrated (e.g. a payments API that sometimes takes a while) and a
flaky appears, it is treated like this: (1) contain with @pytest.mark.flaky(reruns=2) PER TEST, or
quarantine with -m "not flaky" if very annoying; never global --reruns.
(2) open a ticket with an owner and a date. (3) fix at the root (isolate the resource with
a test double, so as not to depend on the real latency of a third party); the
diagnosis follows the test-failure-diagnosis guide. (4) once fixed, remove the
patch. A quarantined flaky that starts failing ALWAYS = real bug.
The policy is coherent with what Reservo is: since there is no flaky today, the pipeline does not anticipate with retry (that would only hide future bugs); it defines the procedure for when the project's surface grows. It explicitly forbids the global --reruns —the most common mistake— and ties the retry to a ticket, so that the containment is temporary and not a permanent silencer.
Deliverable 5 — The decision notes (what the rubric weighs)
- Matrix: three versions, one OS row. Reservo is a library and its README promises 3.11/3.12/3.13 — each version is a promise to users I do not control, and
report_pageshas version branches that only the matrix exercises in its cell. The OS dimension does not pay off: the pure logic gives identical results on every system; adding it would be nine cells for the same green six times. Trigger for adding OS: when Reservo writes to disk (paths, line breaks). - Threshold: 85%. Below the real 88% (cushion of ~3 points so as not to break over noise), but high to bite: a 40-line function without tests would sink the coverage and trigger the gate. Neither 100% (impossible fetish because of the
reports.pyversion branch) nor a decorative number far below the real one. - Speed: cache yes,
-n autoas an investment. I cache because the matrix repeats the installation three times and the cache eliminates it without a downside.-n autodoes not pay off today (0.02s suite; the worker overhead would make it slower), but I leave it in place, documented, ready for when the suite grows — its real effect I measured in lesson 5 (4.07s → 1.19s on a slow suite). - Flaky: per-test policy, no global retry. Reservo is deterministic; the pipeline retries nothing. The policy is written for the future, with the explicit prohibition of the global
--reruns. - Scope, honestly: the pipeline protects the 14 tests on every push/PR, on three versions, with a coverage gate at 85%. It does not cover other OSes (unnecessary for pure logic today), nor does it actually parallelize (instantaneous suite), nor does it deploy to production (this guide is test CI, not CD). It is the pipeline that Reservo really deserves — neither inflated by reflex, nor below its promises.
Common mistakes
Delivering the YAML without the local parity executed. What happens: someone writes an impeccable tests.yml and considers it done, without ever running the commands on their machine. They push, and the real CI reveals in minutes what the terminal would have revealed in seconds. Why it happens: the YAML "looks good" and gives a feeling of finished work. How to detect it: if you do not have a real pytest/coverage output from your own terminal, you verified nothing. How to fix it: the local parity (check.sh with its green output) is half the deliverable, not an extra; the YAML is content, the run is evidence.
Inflating the pipeline so it "looks complete." What happens: someone adds the 3×3 matrix, -n auto, and a gate at 100% so that the pipeline looks robust, without asking whether Reservo needs it. They end up with nine cells for pure logic, parallelism that slows down a 0.02s suite, and an impossible gate because of the reports.py version branch. Why it happens: "more stages turned on" feels more professional. How to detect it: for each stage, ask yourself "what real problem of Reservo does it remove?"; if the answer is "none," it is cost and noise. How to fix it: the rubric rewards the method, not the size — justify each layer by the real risk, and have the judgment to leave -n auto as a documented investment, not as active parallelism that gets in the way.
Presenting the pipeline as "complete CI" hiding what it does not do. What happens: someone sets up the pipeline and announces "done, complete CI," without mentioning that it does not test other OSes, does not deploy, and its -n auto does not accelerate anything today. Why it happens: finishing the capstone feels like covering everything. How to detect it: try to list what your pipeline does not do; if several things come out, it is not "complete." How to fix it: write the scope note of deliverable 5 — naming what is left out is not admitting a failure, it is engineering honesty, and it is exactly what the rubric values.
Exercises
Exercise 1 — The pipeline with three method defects. A colleague delivers this "complete" tests.yml. It has three method defects (not syntax ones). Find them and correct them.
name: tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -r requirements-dev.txt
- run: pytest --reruns 3 --cov=reservo --cov-fail-under=100
See solution
The three method defects:
-
The 3×3 matrix (nine cells) for pure logic.
os: [ubuntu, macos, windows]× three versions = nine cells, but Reservo is integer and date arithmetic: it gives identical results on every OS. Eight of the nine cells test environments where the code does not behave differently — cost and noise. Correction: a single OS row (runs-on: ubuntu-latest, without theosdimension), three version cells. The OS dimension is added the day Reservo touches the disk. -
The gate at 100%.
--cov-fail-under=100is impossible to satisfy per cell: theelsebranch ofreport_pagesis unreachable on 3.12+ (and theifone, on 3.11), so the gate would fail always, not because of badly tested code, but because of branches of another version. Correction: a defensible threshold below the real coverage (85%), which bites without demanding the impossible. -
The global
--reruns 3. It retries all the tests, hiding not only flaky but real bugs — and Reservo does not even have flaky (it is deterministic), so the global retry is pure risk with no benefit. Correction: remove global--reruns; the flaky policy is per-test, and today none is needed.
A defensible fourth point: on: [push] without pull_request loses the guardian of the merge; on: [push, pull_request] is preferable. And the cache and the pinned versions of the deliverable are missing. Corrected, the pipeline looks like the reference solution: three version cells on Linux, cache, pinned installation, gate at 85%, without global retry. The lesson: the defects were not syntax (the YAML is valid), but method — turning on stages by reflex instead of by Reservo's real risk.
Exercise 2 — Defend a decision in the review. A reviewer tells you: "Your pipeline has -n auto but your check.sh runs in series. That is inconsistent, choose one." Answer them defending the decision with what you know from lesson 5.
See solution
It is not inconsistency; it is lesson 5 applied with judgment. -n auto pays off when the suite is slow enough to amortize the fixed overhead of starting workers (≈1s); it gets in the way when the suite is already instantaneous. Reservo's suite runs in 0.02 seconds, so today -n auto would make it slower, not faster — I measured it. That is why check.sh, which runs on your machine where the goal is immediate feedback, goes in series: it is objectively faster for this suite.
Why then does the pipeline keep -n auto? As a ready-to-scale and documented investment. The pipeline is infrastructure that lives for years; the day Reservo grows to hundreds of tests or to slow tests (when it integrates that payments API, say), -n auto will start to pay off without anyone having to touch the YAML in the middle of an emergency. Leaving it in place with a comment saying "does not pay off today, ready for when the suite grows" is foresight, not contradiction. And the part that decides green or red —the pinned installation and the coverage gate— is identical between check.sh and the pipeline; the only difference is the -n auto, which does not change the result, only (eventually) the time. The parity that matters is that of the verdict, and that is exact.
If the reviewer preferred strict consistency, the equally defensible alternative is removing -n auto from both until the suite asks for it — also correct. What would be indefensible is the opposite: putting -n auto in check.sh "for consistency" and making your local feedback slower for a cosmetic symmetry. The decision is made by the measurement, not by the symmetry.
Exercise 3 — Extend the pipeline for a new capability. Reservo adds export_report(path), which writes the daily report to a text file. Suddenly, three layers of the pipeline could need changes. Say which and how, applying the capstone's judgment.
See solution
Writing to a text file makes Reservo touch the terrain of the operating system (path separator, line breaks \n vs \r\n, encoding) and the disk (real I/O, resources, possible slowness), which activates three layers:
-
The matrix (lesson 4) gains the OS dimension. Until now a single Linux row sufficed because the logic was pure; now
export_reportcan behave differently on Windows (paths with\,\r\nline breaks) than on POSIX. The matrix grows toos: [ubuntu-latest, windows-latest]× three versions = six cells (I include Windows because of the real POSIX/Windows difference; macOS is almost redundant with Linux for this). The trigger that lesson 4 anticipated came true. -
The flaky policy (lesson 7) becomes relevant. The disk I/O introduces shared resources (two tests writing to the same file) and timing (the disk sometimes takes a while), the classic triggers of flaky — especially under the pipeline's
-n auto, which distributes the tests and can make two collide on the same file. The policy goes from "written for the future" to "active": each test ofexport_reportmust use a unique per-test temporary file (pytest'stmp_path), so as not to depend on another's file. If a flaky appears, it is contained per-test with a ticket. -
The speed (lesson 5) may start to really justify
-n auto. The I/O tests are slower than the pure arithmetic; if the suite grows with several of them and starts to take seconds, the-n autothat today is a future investment starts to pay off. It is re-measured: if the serial suite already hurts, the parallelism stops being overhead and becomes real acceleration.
(The coverage gate of lesson 6 would also notice the change: export_report is new code, and without tests that exercise it the coverage would drop and the gate would trigger — exactly its job, pushing you to test the new function.) The capstone's lesson: a pipeline is not static; it evolves with what the code risks. When Reservo went from pure logic to touching the disk, three layers that were dormant for good reason woke up, each one because of a new and concrete risk — not by reflex, but because the terrain changed.
Summary and next step
In this project you integrated the whole module by producing the real deliverable of the capstone: Reservo's complete CI pipeline. You wove the six layers into a single tests.yml —push+PR (M2), matrix of three versions (M4), pinned installation (M3), cache (M5), coverage gate at 85% (M6), honest flaky policy (M7)—, each block commented with the lesson it comes from. You wrote check.sh, the local parity, and ran it for real: 13 passed, 1 skipped, coverage 88%, gate at 85% satisfied, exit code 0 — the evidence that the pipeline will do the right thing. And you wrote the decision notes that the rubric weighs, justifying each layer by Reservo's real risk, not by reflex.
Above all, you practiced the practical exam: making all the decisions at once —which matrix, which threshold, -n auto or not, which flaky policy— and defending each one. That is what separates whoever copies a pipeline from the internet from whoever designs the one their project deserves. A pipeline is evaluated by its method, not by its size.
Closing of the guide: the eight modules
You reached the end. Look at the whole path, because each module was a piece of a single skill: running your suite automatically on every change, and doing it well.
- Module 1 — From your machine to the pipeline. Why CI exists: "it works on my machine" is a claim about an environment; a pipeline runs your tests on every push, without relying on someone remembering. The feedback loop and the stages of a pipe.
- Module 2 — Your first pipeline. The GitHub Actions workflow that runs pytest on every push: the anatomy of the YAML (
on,jobs,steps), the checkout,setup-python, install,pytest, and how the exit code paints the job. - Module 3 — Reproducing a CI failure locally. The environment gap: CI in red, local in green. Pinned dependencies, deterministic installations, and how to reproduce on your machine what you only saw in CI.
- Module 4 — The matrix. Running the suite on several Python versions (and OSes) at once with
strategy.matrix; one job per combination; and when the matrix pays off and when it is noise. - Module 5 — Fast CI. Caching dependencies and parallelizing with
pytest-xdist; the speed/cost trade-off; and the discipline of measuring before optimizing. - Module 6 — Quality gates. The coverage threshold that breaks the build (
--cov-fail-under); where to put it; and why 100% is a fetish. - Module 7 — Flaky in CI. The test that sometimes passes and sometimes fails; the retry debate; the quarantine; and why a flaky erodes trust in the whole pipeline.
- Module 8 — The capstone. Weaving the six layers into a complete pipeline for Reservo, with local parity, evaluated by the method of its decisions.
You come out knowing how to set up a pipeline that protects the main branch without becoming a bottleneck: automatic, reproducible, multi-version, fast when it matters, demanding in quality, and honest with the flaky. That is the complete skill.
Where to continue
This guide taught how to run your tests in CI, and on purpose left out other skills of the testing ecosystem, each with its sibling guide. Here the map of where to go according to what you want to learn:
- How to write and organize the tests that this pipeline runs →
testing-fundamentals-and-tdd(the ABC of writing a good test, TDD, coverage as a local tool) andtest-automation-framework-architecture(how to structure a large suite: fixtures, layers, maintainability). Here the tests already existed; there you learn to create them. - How to diagnose a failure —especially a flaky— down to its root cause →
test-failure-diagnosis. Module 7 taught you to contain a flaky in CI; this guide teaches you to hunt it: reproduce, isolate the trigger, correct the fixture. The boundary we marked points here. - How to think about the quality strategy —what to test, how much, where to put the effort →
test-strategy-and-quality-engineering. It goes up a level: from the tools to the judgment of what deserves a test and what does not, the test pyramid, the risk. - How to test a real web app —not pure logic like Reservo, but endpoints, database, authentication →
testing-backend-applications-guide. It grazes the "CD to production" that this guide left out; there the tests touch the world (network, DB) and the pipeline you assembled here becomes the one that protects that deployment.
The pipeline you assembled is not the end of anything; it is the infrastructure on which everything else runs. Each test you write (fundamentals), each suite you structure (architecture), each failure you diagnose (diagnosis), each app you test (backend) is going to run in a pipeline like Reservo's. You learned to build the machine that protects the main branch; now you have the whole ecosystem to fill it with tests worth having. That was the goal of the whole guide: knowing which tests are useful — and running them, automatically, on every change.
Resources
- Building and testing Python — GitHub Actions — the official guide that walks through matrix, cache, and coverage in a single Python workflow, the complete pipeline you assembled. The reference for setting it up in your own project.
- Workflow syntax for GitHub Actions — the complete syntax of the YAML (
on,jobs,strategy,steps), to consult any detail of thetests.ymlyou wove. The dictionary of the pipeline. - pytest-xdist, pytest-cov, and pytest-rerunfailures — the three tools that layers 5, 6, and 7 activated, together in the final pipeline. Their documentation, to fine-tune each layer in your project.
- About protected branches — GitHub — how to make the pipeline's checks required to merge, the link that turns "the CI is red" into "the merge is blocked." The step that gives teeth to everything you set up.