AI EngineeringZero to ProductionHome·About·What’s new·Contact
Software Testing · Project TQ

Add a test suite to real code

The capstone: untested code to professionally tested — units, integration, fixtures, mocking, a test-first bug fix, coverage, and a required CI quality gate. A repo safe to change.

⏱️ ~3.5 hours🏗️ Capstone project🎯 Intermediate→Tech-lead
What you'll buildTake an untested module to professionally tested: units, edges, exceptions, fixtures, mocking, a test-first bug fix, coverage, and a required CI quality gate. The deliverable is a repo where every change is guarded by a fast, trusted suite.

Learning objectives

  • Add a real suite to untested code.
  • Combine every TQ technique.
  • Fix a bug test-first.
  • Gate the repo on tests + coverage.
▶ Runnable companionThe code here is also saved under code/proj-tq-suite/. Python blocks run offline; config files are ready to drop into a project.

1 · The untested code essential

one small class · a dict of stock · three methods · zero tests add(sku, qty) remove(sku, qty) level(sku) _stock (dict) "A" → 7 "B" → 0 no tests yet edits break silently A tiny class with something to protect. Three methods read and write one private _stock dictionary — and right now nothing verifies they behave, so any future edit can break them without anyone noticing.
🗺️ How to read this diagram

Read it left-to-right: the three methods on the left all act on the one dictionary in the middle, and the red box on the right is what's missing today.

  • The blue method boxes (add, remove, level) are the module's whole public surface — the only ways to change or read stock.
  • The arrows point into the purple _stock box — the private dictionary mapping each sku to its quantity. Every method's job is ultimately to touch that map.
  • The red "no tests yet" box is the point of the whole project: this behaviour is currently unguarded, so the rest of the sections add the suite that pins it down.

In short: three methods, one dictionary, and no safety net yet — that gap is exactly what the test suite you build here fills.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Python · inventory.py (the 'before', runs)
inventory.pyclass Inventory:
    def __init__(self): self._stock = {}
    def add(self, sku, qty):
        if qty <= 0: raise ValueError("qty must be positive")
        self._stock[sku] = self._stock.get(sku, 0) + qty
    def remove(self, sku, qty):
        if self._stock.get(sku, 0) < qty: raise ValueError("insufficient stock")
        self._stock[sku] -= qty
    def level(self, sku): return self._stock.get(sku, 0)

inv = Inventory(); inv.add("A", 10); inv.remove("A", 3)
print("level:", inv.level("A"))    # 7
level: 7
▶ How this works

This is the module we are going to test — a tiny warehouse stock tracker. Right now it has zero tests, so nobody knows if it really works or whether a future edit will quietly break it. Read it once so the tests later make sense: it is just a class that remembers how much of each item (a sku, i.e. a product code) you have.

  1. self._stock = {} is an empty dictionary — a lookup table that maps each sku to its quantity. The leading underscore is a convention meaning "internal, please don't poke at it directly".
  2. add first guards against nonsense: if qty is zero or negative it raises a ValueError — Python's way of refusing bad input loudly instead of storing garbage. Otherwise it adds to the current amount (.get(sku, 0) means "the current count, or 0 if we've never seen this sku").
  3. remove guards the other direction: you cannot take out more than you have, so it raises "insufficient stock" when asked to.
  4. level just reports how much of a sku is on hand (0 if unknown). The last three lines are a quick manual check: add 10, remove 3, expect 7.

What the output means: level: 7 — the manual sanity check at the bottom worked. But a single hand-run like this is not a test suite: it only checks one happy path and nothing re-runs it automatically.

Try this: Predict what inv.remove("A", 99) would do before reading on. (It would hit the "insufficient stock" guard and raise.) That guard is exactly the kind of behaviour a real suite must pin down.

2 · Cover behavior, edges, exceptions intermediate

a real suite covers three kinds of case, not just the happy path behavior add 10, remove 3 → 7 edges unknown sku → 0 exceptions qty≤0, over-remove→ raise "full suite passes" only prints if every case above holds Three columns, one bar to clear. A trustworthy suite pins the normal path, the awkward boundaries, and the error paths — miss any column and a whole class of bug slips through.
🗺️ How to read this diagram

Read the three columns left-to-right; each names a kind of case and shows a concrete example from this module, and all three feed the single result at the bottom.

  • The green "behavior" column is the normal path — the everyday use that must work (add 10, remove 3, expect 7).
  • The amber "edges" column is the awkward boundary — asking for an unknown sku must report 0, not crash.
  • The red "exceptions" column is the error paths — bad input (qty≤0) and over-removal must raise, on purpose.
  • The bottom box is the payoff: "full suite passes" prints only when every case in all three columns holds — one red case and you never reach it.

In short: behavior + edges + exceptions is the coverage checklist — leave a column empty and that's exactly where the regression hides.

Python · the suite (runs as plain asserts)
test_inventory.pyfrom inventory import Inventory   # in pytest: fixtures + pytest.raises

def fresh(): i = Inventory(); i.add("A", 10); return i

assert fresh().level("A") == 10
i = fresh(); i.remove("A", 3); assert i.level("A") == 7
assert fresh().level("Z") == 0

for bad in (0, -5):
    try: fresh().add("A", bad); assert False
    except ValueError: pass

try: fresh().remove("A", 999); assert False
except ValueError as e: assert "insufficient" in str(e)
print("full suite passes")
full suite passes
▶ How this works

Now we test the module properly. A good suite checks three things: the normal case ("behavior"), the awkward boundaries ("edges"), and the error paths ("exceptions"). This file runs as plain assert statements so you can see the logic; the comment notes how the same ideas become pytest fixtures and pytest.raises in a real project.

  1. from inventory import Inventory pulls in the class from the file we just read — your tests live beside the code and import it, they don't copy it.
  2. def fresh(): is a tiny helper that builds a known starting state (a new inventory with 10 of "A") for every test. Reusing it keeps each check independent — this is the idea a pytest fixture formalises.
  3. The three assert lines check behavior + an edge: a fresh level is 10; after removing 3 it's 7; and an unknown sku "Z" reports 0 instead of crashing. An assert silently passes when true and blows up when false.
  4. The for bad in (0, -5): loop checks the exception path: adding a bad quantity must raise. The try / except ValueError: pass means "we expected this error, so catching it counts as success"; the assert False right after the call is a trap that fires only if no error was raised.
  5. The final block does the same for over-removing, and also checks the message contains "insufficient" — so a future rename of the error can't silently slip through.

What the output means: full suite passes prints only if every assert held. If any one failed, Python would stop at that line with an AssertionError and you'd never reach the print — that's the suite catching a regression.

Try this: Break the code on purpose: change level to return self._stock.get(sku, 1) and re-run. The "unknown sku is 0" assert now fails — proof the test is actually guarding that behaviour.

3 · Fix a bug test-first advanced

write the test FIRST — it must fail before it can prove the fix bug report remove all, re-add RED test reproduces it GREEN fix → test passes keep it regression guard bug returns later → the kept test goes RED again RED before GREEN, then keep the test forever. A failing test that reproduces the bug proves it can catch it; only then do you fix the code — and the test stays as a permanent regression guard that fires if the bug ever comes back.
🗺️ How to read this diagram

Follow the blue arrows left to right — that's the order you actually work, one step handing off to the next.

  • The white "bug report" box is the reported sequence (add 10, remove 10, add 5). You encode it as a test before touching the code.
  • The red "RED" box is that test failing against the broken build — this is the proof the test can actually catch the bug. The green "GREEN" box is after you fix inventory.py so the same test passes.
  • The blue "keep it" box is the payoff: the test is never deleted. The dashed curved arrow shows why — if the bug ever creeps back, that kept test flips to RED again and catches the regression.

In short: write the failing test first, make it pass, and keep it — RED → GREEN → permanent guard.

Python · reproduce then fix (runs)
bugfix.pyinv = Inventory()
inv.add("A", 10); inv.remove("A", 10)   # down to 0
inv.add("A", 5)                          # previously reported broken
assert inv.level("A") == 5               # RED first, then confirm GREEN
print("bug fixed with a permanent regression test")
bug fixed with a permanent regression test
▶ How this works

This is the test-first (TDD) bug-fix workflow in miniature. The rule: before you touch the code, first write a test that reproduces the bug and therefore fails ("RED"). Then you fix the code until the same test passes ("GREEN"). The test stays forever as a regression test so the bug can never silently return.

  1. The setup recreates the exact scenario from the bug report: add 10, remove all 10 so the stock hits 0, then add("A", 5) — the step a user claimed was broken.
  2. assert inv.level("A") == 5 is the whole point: it states the correct expected result. Run this first — if the bug is real it fails (RED), which proves your test can actually catch it.
  3. You then fix inventory.py and run again; when the assert holds, you're GREEN. Here the current code already behaves correctly, so it passes straight away.

What the output means: bug fixed with a permanent regression test — the assert passed, so the reported behaviour is correct and this test now permanently guards it.

Try this: Temporarily make the bug real: in add, change the fallback to self._stock.get(sku, 100). Re-run — the assert fails (RED). Undo it and watch it go GREEN. That RED-then-GREEN cycle is the heart of TDD.

4 · Professional — coverage professional

coverage watches which lines actually run under the tests def add(self, sku, qty): self._stock[sku] = ... raise ValueError(...) def level(self, sku): ... green = a test ran it · red = never reached coverage report Stmts 12 · Miss 1 Cover 92% Missing: line 6 Coverage points at the lines you forgot. It runs the suite, marks every executed line green and every un-run line red, then the "Missing" column names the exact line still needing a test — driving you to 100%.
🗺️ How to read this diagram

Read the stacked lines on the left as the module's source; the report on the right is what the coverage tool prints after running your suite over them.

  • The green lines were executed by at least one test — proof those statements actually ran while the suite was measuring.
  • The red line (a raise guard) was never reached — no test triggered that error path, so it's uncovered.
  • The report box turns that into numbers: statements counted, how many were missed, the percentage, and crucially the "Missing" line number that tells you exactly where to add a test.

In short: green ran, red didn't, and the Missing column is your to-do list — add the test it names and the percentage climbs to 100%.

shell · measure it
cov.shpytest --cov=inventory --cov-report=term-missing
# Name           Stmts  Miss  Cover  Missing
# inventory.py      12     0   100%
▶ How this works

A passing suite tells you the lines you tested work — it says nothing about the lines you forgot. Coverage answers that: it runs your tests while watching which lines of the module actually execute, then reports what got missed.

  1. pytest --cov=inventory runs the test suite and measures coverage of the inventory module specifically (not the test files themselves).
  2. --cov-report=term-missing prints the summary and the line numbers of any statements no test reached — the "Missing" column tells you exactly where to add a test.
  3. The commented lines below the command are the report pytest prints: 12 statements, 0 missed, 100% covered, nothing in "Missing".

What the output means: 100% with an empty Missing column means every line of inventory.py was exercised by the suite. 100% is a nice signal, but remember it proves lines ran, not that every case was checked well.

Try this: Delete one of the exception tests from section 2, then re-run coverage. The guard line inside add now shows up under "Missing" and the percentage drops — coverage just told you what you stopped testing.

5 · Tech-lead — the CI quality gate tech-lead

on every pull request — the gate decides if merge is allowed pull request opened / updated CI job runs pytest, cov floor 90% pass fail / low cov ✓ merge allowed green check ✗ merge blocked required check is red The gate turns "should test" into "cannot merge broken code." A PR triggers the CI job; passing tests and a coverage floor open the merge, while a failing test or a coverage dip turns the required check red and blocks it.
🗺️ How to read this diagram

Scan left to right: a pull request flows into the CI job, and the job's result branches up (allow) or down (block).

  • The white "pull request" box is the trigger — every open or update runs the job automatically, so nobody has to remember.
  • The blue "CI job runs" box is the gate itself: it runs pytest with --cov-fail-under=90, so the suite must both pass and hold the coverage floor.
  • The two branches are the verdict. Pass → the green "merge allowed" box. Fail or low coverage → the red "merge blocked" box — and because the check is marked Required, that red result actually stops the merge button.

In short: the PR must clear the CI gate — green merges, red is blocked — which is what makes the suite non-optional.

config · required gate on every PR
quality.ymlname: quality
on: [pull_request]
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install pytest pytest-cov
      - run: pytest --cov=inventory --cov-fail-under=90   # fails if coverage < 90%
# mark this check REQUIRED on main -> no red PR merges
▶ How this works

The final step makes the suite mandatory. This is a GitHub Actions workflow — a recipe a server runs automatically on every pull request (PR). If the tests fail or coverage is too low, the PR is blocked from merging. That turns "we should run tests" into "you cannot merge broken code".

  1. on: [pull_request] is the trigger: this job runs every time someone opens or updates a PR — nobody has to remember to run it.
  2. The steps: are the checklist the server follows on a fresh Ubuntu machine: check out the code, install Python 3.12, then pip install the test tools (pytest and the coverage plugin pytest-cov).
  3. The last run is the gate: pytest --cov=inventory --cov-fail-under=90 runs the suite and exits with an error if coverage falls below 90%. A failing exit turns the PR check red.
  4. The final comment is the human step done once in GitHub settings: mark this check Required on the main branch, so a red result actually blocks the merge button.

What the output means: On a healthy PR the job goes green and merging is allowed. If a test breaks or coverage dips under 90%, the job goes red and — because the check is Required — the merge is blocked until it's fixed.

Try this: Lower the threshold thought-experiment: change --cov-fail-under=90 to 100. Now even one untested line blocks every PR. Teams tune this number to balance safety against friction — that trade-off is the tech-lead's call.

The finished deliverableA repo where every function is tested, edges + exceptions covered, external calls mockable, coverage enforced, and CI blocks any PR that breaks a test or drops coverage. That repo is safe to change — the entire point of testing, and what a lead delivers.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Read the module, predict behaviourBeginner

Context: Before changing or testing code you don't fully know, you characterize it: plain asserts that pin the current happy-path behaviour so any later regression is caught.

Your task: Pin the lesson's Inventory happy path with plain asserts — add 10, remove 3, expect level 7 — so a regression would be caught.

Requirements:

  • Use plain assert statements (runs as a script)
  • Exercise the add-then-remove happy path
  • Assert the resulting level is correct
  • No test framework required
  • The asserts would fail on a regression

💡 Hint: This is milestone 1 — characterize the 'before' code; a couple of asserts that codify the expected level are enough to catch a future break.

Show solution

Milestone 1: characterize the 'before' code with asserts (runs as a plain script). Runnable:

class Inventory:
    def __init__(self): self._stock = {}
    def add(self, sku, qty):
        if qty <= 0: raise ValueError("qty must be positive")
        self._stock[sku] = self._stock.get(sku, 0) + qty
    def remove(self, sku, qty):
        if self._stock.get(sku, 0) < qty: raise ValueError("insufficient stock")
        self._stock[sku] -= qty
    def level(self, sku): return self._stock.get(sku, 0)

inv = Inventory(); inv.add("A", 10); inv.remove("A", 3)
assert inv.level("A") == 7
print("happy path pinned")
Exercise 2 · Cover behaviour, edges, and exceptionsIntermediate

Context: A real suite covers behaviour, edges, and exceptions. A proper unittest.TestCase pins the normal path, the unknown-key edge, and both input guards, run by the stdlib runner.

Your task: Write a unittest.TestCase covering a normal add/remove, the unknown-sku edge (level 0), and both guards (add rejecting qty≤0, remove rejecting over-withdrawal).

Requirements:

  • A TestCase with a setUp
  • A normal add/remove test
  • The unknown-sku edge returns level 0
  • add rejects a non-positive quantity
  • remove rejects withdrawing more than the stock
  • Runs with the stdlib unittest runner

💡 Hint: Inline the module so it runs as-is; use assertRaises for the two guards and setUp to stock a starting item.

Show solution

Self-contained stdlib unittest — the module is inlined so it runs as-is:

import unittest

class Inventory:
    def __init__(self): self._stock = {}
    def add(self, sku, qty):
        if qty <= 0: raise ValueError("qty must be positive")
        self._stock[sku] = self._stock.get(sku, 0) + qty
    def remove(self, sku, qty):
        if self._stock.get(sku, 0) < qty: raise ValueError("insufficient stock")
        self._stock[sku] -= qty
    def level(self, sku): return self._stock.get(sku, 0)

class TestInventory(unittest.TestCase):
    def setUp(self):
        self.inv = Inventory(); self.inv.add("A", 10)
    def test_add_remove(self):
        self.inv.remove("A", 3)
        self.assertEqual(self.inv.level("A"), 7)
    def test_unknown_sku_is_zero(self):
        self.assertEqual(self.inv.level("ZZ"), 0)
    def test_add_rejects_nonpositive(self):
        with self.assertRaises(ValueError):
            self.inv.add("A", 0)
    def test_remove_rejects_overdraw(self):
        with self.assertRaises(ValueError):
            self.inv.remove("A", 99)

if __name__ == "__main__":
    unittest.main(verbosity=2)
Exercise 3 · Fix a bug test-first (RED then GREEN)Advanced

Context: The TDD bug-fix loop is RED then GREEN: write a regression test that reproduces the reported bug (it would fail on the broken build), then confirm it passes on the real code, leaving the test as a permanent guard.

Your task: Reproduce a reported bug (add 10, remove 10, add 5, expect 5) as a regression test that would go RED against a broken build and GREEN against the real code.

Requirements:

  • A test encodes the exact reported sequence
  • It expects the correct final level (5)
  • It would fail against the broken behaviour (RED)
  • It passes against the real, correct code (GREEN)
  • It stays as a permanent regression guard

💡 Hint: Write the failing case first from the bug report; here the real code is already correct so it goes straight to GREEN, but the test remains to prevent a recurrence.

Show solution

The TDD bug-fix loop from section 3. Runnable — here the real code is already correct so it goes straight to GREEN, and the test stays as a permanent guard:

import unittest

class Inventory:
    def __init__(self): self._stock = {}
    def add(self, sku, qty):
        if qty <= 0: raise ValueError("qty must be positive")
        self._stock[sku] = self._stock.get(sku, 0) + qty
    def remove(self, sku, qty):
        if self._stock.get(sku, 0) < qty: raise ValueError("insufficient stock")
        self._stock[sku] -= qty
    def level(self, sku): return self._stock.get(sku, 0)

class TestRefillAfterZero(unittest.TestCase):
    def test_add_back_after_zero(self):
        inv = Inventory()
        inv.add("A", 10); inv.remove("A", 10)   # down to 0
        inv.add("A", 5)                          # the reported step
        self.assertEqual(inv.level("A"), 5)      # RED if broken, else GREEN

if __name__ == "__main__":
    unittest.main(verbosity=2)
Exercise 4 · Isolate collaborators with a mockExpert

Context: When code emits a side effect through a collaborator (an audit sink), you isolate the collaborator with a mock so the test asserts the interaction without a real external system.

Your task: Test that Inventory's audit callback is called with the right args on each remove, using unittest.mock.MagicMock instead of a real audit system.

Requirements:

  • Inventory takes an audit callback and calls it on remove
  • A MagicMock stands in for the audit sink
  • The test asserts the callback's call arguments
  • No real audit system is involved
  • Uses assert_called_once_with (or equivalent)

💡 Hint: Inject the audit sink as a callback so a MagicMock can replace it; assert it was called once with the expected action, sku, and quantity.

Show solution

TQ3's fixtures + mocking, on stdlib. The mock stands in for the external audit sink:

import unittest
from unittest.mock import MagicMock

class Inventory:
    def __init__(self, audit):
        self._stock = {}; self._audit = audit
    def add(self, sku, qty):
        if qty <= 0: raise ValueError("qty must be positive")
        self._stock[sku] = self._stock.get(sku, 0) + qty
    def remove(self, sku, qty):
        if self._stock.get(sku, 0) < qty: raise ValueError("insufficient stock")
        self._stock[sku] -= qty
        self._audit("remove", sku, qty)          # side effect to isolate
    def level(self, sku): return self._stock.get(sku, 0)

class TestAudit(unittest.TestCase):
    def test_remove_audits(self):
        audit = MagicMock()
        inv = Inventory(audit); inv.add("A", 10)
        inv.remove("A", 4)
        audit.assert_called_once_with("remove", "A", 4)

if __name__ == "__main__":
    unittest.main(verbosity=2)
Exercise 5 · Measure coverage and close the gapsProfessional

Context: Coverage tells you which lines ran. Driving it to 100% with a missing-line report surfaces untested branches — but 100% means every line ran, not that every case is checked well.

Your task: Run coverage on the module and drive it to 100% using the missing-line report to find untested branches; write the command and interpret a sample report.

Requirements:

  • The coverage command targets only the module under test
  • A term-missing report names the uncovered lines
  • The uncovered lines are the guard branches
  • Tests are added to hit those lines, reaching 100%
  • Note that 100% line coverage is not proof of correctness

💡 Hint: This rung is a worked artifact (shell + report), not runnable Python; the --cov-report=term-missing output points you straight at the untested guard lines.

Show solution

Worked artifact (shell + report) — coverage is a tool invocation, not runnable Python here. From section 4:

# run the suite while measuring the inventory module only
pytest --cov=inventory --cov-report=term-missing

# sample report BEFORE closing gaps:
# Name           Stmts   Miss  Cover   Missing
# inventory.py      12      2    83%   6, 9      <- the two guard lines

# add tests that hit the raise on line 6 (add qty<=0) and line 9
# (remove over-draw), then re-run:
# inventory.py      12      0   100%
#
# 100% means every line RAN -- not that every case is checked well;
# keep the edge/exception tests from milestone 2 as the real proof.
Exercise 6 · A required CI quality gateIndustry scenario

Context: The tech-lead move is making the suite a required gate: on every pull request, tests must pass and coverage must not drop below a floor, or the PR is blocked.

Your task: Make the suite a required CI gate on every pull request — tests pass and coverage stays above a floor — by writing the GitHub Actions workflow.

Requirements:

  • A workflow triggered on pull requests
  • Checks out, sets up Python, installs pytest + coverage
  • Runs the suite with a coverage floor that fails the job if breached
  • The failing job blocks the PR
  • Note the branch-protection step that marks the check required

💡 Hint: This rung is a GitHub Actions YAML artifact; --cov-fail-under makes the job fail below the floor, and branch protection makes that check required to merge.

Show solution

Worked artifact (GitHub Actions YAML) — the section-5 tech-lead gate. It runs in CI, not here:

name: quality
on: [pull_request]
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: pip install pytest pytest-cov
      # --cov-fail-under makes the job FAIL (and block the PR) if
      # coverage drops below the floor, so tests are truly required
      - run: pytest --cov=inventory --cov-report=term-missing --cov-fail-under=100

# Then in branch protection, mark the "gate" check as required:
# no merge unless tests pass AND coverage stays at 100%.

✓ Checkpoint — you can move on when you can…

  • Add a real suite to untested code.
  • Combine units, fixtures, mocking, coverage.
  • Fix a bug test-first.
  • Enforce a required CI quality gate.
📋 Staff-level self-scoring — is this suite one the team can trust to change code?
DimensionMeets the barAbove the bar (staff)
CoverageCoverage is measured on the module under test and is high (e.g. ≥ 90%); the Missing column is understood.Coverage is enforced in CI (--cov-fail-under), and you can name which uncovered lines are deliberately untested and why — coverage is used as a floor, not a vanity number.
Edge & failure casesTests cover behavior, boundary/edge inputs, and the exception paths — not just the happy path.Error paths assert on the specific exception/message, boundary classes are enumerated deliberately, and the suite would catch a regression that only breaks an edge case.
Fixture & mocking qualityFixtures set up state cleanly and external calls are mocked so tests are fast and offline.Mocks assert on interactions (not just return values), the boundary between real and mocked code is deliberate, and tests are isolated (no shared mutable state, order-independent).
Test-first bug fixA bug was reproduced with a failing test before the fix, then the test went green.The regression test is kept, named for the bug, and clearly documents the defect so it can never silently return.
CI quality gateA CI workflow runs the suite on every PR and blocks merge on failure or low coverage.The check is marked Required on main, the threshold is tuned to a defended balance of safety vs friction, and a red gate has actually stopped a bad merge.

Score each dimension 0 (missing), 1 (meets bar), or 2 (above bar). 0–4: a few tests. 5–7: a real suite. 8–10: staff-level — edges and failures covered, mocks that assert, a regression test for the bug, and a required CI gate. A 0 on Edge & failure cases or CI quality gate means the suite gives false confidence — fix first.

Knowledge check check yourself

✓ Knowledge check

The project has you fix a bug test-first. What does that mean, and why write the test before the fix?

Show answer
You first write a failing test that reproduces the bug, then change the code until the test passes. Writing it first proves the test actually catches the bug (it fails before the fix) and permanently guards against the bug returning as a regression.
✓ Knowledge check

Why does the capstone gate the repo on a required CI check for tests and coverage, rather than just running tests locally?

Show answer
A required CI quality gate makes the suite non-optional: no change merges unless the tests pass and coverage holds, so the repo stays safe to change for everyone. Local-only runs can be skipped or forgotten, which is how untested code creeps back in.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in