Skip to main content
โ† Testing, Quality & Craft
๐Ÿงช Testing & CraftLesson 13 of 13

Capstone: Messy App โ†’ Tested, Clean & Documented

Take a messy app to tested, clean, documented, and reviewable โ€” with a real test suite.

Capstone: Messy App โ†’ Tested, Clean & Documented

Stage 3 ยท Testing, Quality & Craft ยท Capstone

You vibe-coded something real โ€” it works, users love it, and you moved fast. Now comes the part that separates hobbyists from engineers: you go back in. You add the tests that make you fearless, clean the code that makes you cringe, write the docs that make the next person (future you, a collaborator, an AI pair) feel welcomed instead of lost. This capstone is your before-and-after. By the end, you'll open the repo and feel proud.


๐ŸŽฏ The mission

Take a real application you've already built โ€” ideally something you vibe-coded in a hurry โ€” and bring it up to a professional, shippable standard. You'll measure the gap between where it started and where it ends: lines of test coverage, number of code smells removed, whether a stranger could clone and run it from your README alone. The point isn't to rewrite. It's to lift what already works until it's something you'd show a senior engineer without apology.


๐Ÿงฑ What to do

Work through these layers in order. Each one builds on the last โ€” don't skip ahead, because the tests you write first are what make every cleanup safe.

1. Pick your app and document the starting line

  • Choose a real project: a vibe-coded front end from Stage 1, a backend from D1, a full-stack prototype โ€” anything with logic worth protecting.
  • Take a snapshot of the starting state: no tests, coverage at 0%, a list of 3โ€“5 code smells you already know are there. Be honest; this is your baseline, not a judgment.
  • Identify the critical path โ€” the single sequence of steps a user takes to get the core value of the app (e.g., sign up โ†’ create item โ†’ see item listed). Everything else is secondary.

2. Set up the test runner and coverage

  • Install and configure a test runner appropriate for your stack (Vitest, Jest, Pytest, Go's built-in testing, etc.).
  • Add a test and test:coverage script to your package.json (or equivalent).
  • Verify you can run npm test from a clean clone and see output. Zero passing tests is fine โ€” the runner must work.
  • Set a coverage threshold in your config (start at 70% for the files you touch; raise it as you go).

3. Write the test suite โ€” critical path first

  • Unit tests for every pure function or isolated module on the critical path. Aim for fast, deterministic, no I/O.
  • At least one integration test that wires two real layers together โ€” e.g., a route handler calling a real database function (use a test database or an in-memory store).
  • At least one end-to-end test that drives the critical path from the outside โ€” a browser test with Playwright/Cypress, or a CLI test that calls your real server.
  • Use test doubles (mocks, stubs, fakes) deliberately: isolate the unit under test; never mock what you own when an integration test would be more meaningful.

A minimal unit test to get the shape right:

// utils/formatPrice.test.js
import { formatPrice } from './formatPrice.js'

describe('formatPrice', () => {
  it('formats whole dollars', () => {
    expect(formatPrice(10)).toBe('$10.00')
  })

  it('rounds to two decimal places', () => {
    expect(formatPrice(9.999)).toBe('$10.00')
  })

  it('returns $0.00 for zero', () => {
    expect(formatPrice(0)).toBe('$0.00')
  })

  it('throws on negative input', () => {
    expect(() => formatPrice(-1)).toThrow('Price cannot be negative')
  })
})

Every assertion should be meaningful โ€” it should fail for a real bug, not just to hit a number.

4. Refactor the worst module โ€” under green tests

  • Pick the single messiest module in the app (the one you dread opening). The tests you just wrote make this safe.
  • Apply a clean code pass:
    • Rename variables and functions to say what they are, not how they work.
    • Extract magic numbers and strings into named constants.
    • Replace nested if chains with guard clauses (return early).
    • Delete commented-out code and dead branches.
    • Break functions longer than ~20 lines into smaller, named pieces.
  • Run the tests after every meaningful change โ€” if they go red, you introduced a bug; fix it before continuing.
  • Write a short "before / after" comparison: paste the old function and the new one side by side. This becomes a deliverable.

5. Write / upgrade the README

Your README must let a complete stranger clone, configure, run, test, and deploy the app without asking you a single question. At minimum:

## Prerequisites
Node 20+, a Supabase project (or local Docker), an .env file.
Copy `.env.example` and fill in the values.

## Install
npm install

## Run locally
npm run dev        # http://localhost:3000

## Test
npm test           # unit + integration
npm run test:e2e   # end-to-end (requires dev server running)

## Deploy
npm run build && npx vercel deploy --prod

That skeleton is the floor, not the ceiling. Add a description, a screenshot, environment variable docs, and a link to the live demo.

6. Write one Architecture Decision Record (ADR)

  • Pick one non-obvious decision in the app โ€” why you chose a particular library, how you structured authentication, why you split the data model the way you did.
  • Write a one-page ADR: Context โ†’ Decision โ†’ Consequences. Store it at docs/adr/001-<short-slug>.md.
  • ADRs are how future-you (and future collaborators) avoid relitigating decisions that were already made carefully.

7. Clean up git history and adopt a PR flow

  • Ensure every commit from here forward follows Conventional Commits format: feat:, fix:, test:, refactor:, docs:.
  • Create a branch for at least one of the changes you made above (git checkout -b refactor/format-price-cleanup).
  • Open a pull request (even if it's just to your own main). Write a PR description that explains why, not just what.
  • Review your own PR as if you were a stranger โ€” would you merge this? Would the CI pass?

8. Self code-review against the Code Review Rubric

  • Go through the Code Review Rubric from Lesson 9 line by line, treating your entire diff as the thing under review.
  • Write a 1โ€“2 page self-review: what passed, what you caught and fixed, what you deliberately left for a future ticket and why.
  • This is not busywork โ€” it's the habit that turns "it works" into "it's maintainable."

๐Ÿ—บ๏ธ Run it through B.U.I.L.D.

LetterPhaseWhat it means for this capstone
BBlueprintThe critical path you mapped; the baseline you documented
UUnderstand & CleanThe refactor pass โ€” you can't clean safely without the tests
IImplement & IntegrateThe integration and e2e tests that prove the layers fit together
LLaunchThe README that makes it launchable by anyone, not just you
DDocument, Test, DeployCoverage report, ADR, self-review, PR โ€” the whole D column

The key insight: D and U are load-bearing here. The tests (D) are what make U (clean) safe. Every other step flows from that. Don't skip the order.


๐Ÿงช Deliverables

Submit all of the following โ€” these are concrete artifacts, not vibes:

  1. A passing test suite with a coverage report โ€” screenshot or CI output showing the percentage. The critical path must be covered. Flakey tests don't count; fix them or delete them.

  2. A before/after refactor document โ€” the old function and the new function side by side, with a 2โ€“3 sentence explanation of each change and why it matters.

  3. A real README โ€” passes the "stranger test": someone who has never seen your project clones it, follows your README, and has a running dev server within 10 minutes.

  4. One ADR โ€” a genuine Architecture Decision Record for a real decision in the app. Must include Context, Decision, and Consequences.

  5. A self-review write-up โ€” you ran the Code Review Rubric against your own diff and wrote up the results. At least one thing you caught and fixed; at least one thing you intentionally deferred with a reason.

  6. A measurable improvement โ€” compare your baseline to your final state on at least two of these axes: lines covered, number of named smells removed, README completeness, commit message quality. Numbers beat adjectives.


๐Ÿ† Stretch goals

These are not required, but they foreshadow D6 and separate good work from great work:

  • Add CI โ€” set up a GitHub Actions workflow that runs npm test and npm run test:coverage on every push and every pull request. A red test blocks the merge. This is what D6 (DevOps) is built on.
  • Get a real review โ€” share your PR with a peer, a mentor, or an AI and ask for a formal code review. Respond to every comment, even if your response is "won't fix โ€” here's why."
  • Add a pre-commit hook โ€” use Husky or a simple .git/hooks/pre-commit script that runs the linter and the unit tests before any commit is allowed. Fail fast, locally.
  • Achieve 90% coverage on the critical path module โ€” not 90% overall (that's a trap), but 90% on the single most important file in the app.
  • Write a second ADR โ€” document a decision you made during this capstone (e.g., "why I chose Vitest over Jest").

โœ… You're done whenโ€ฆ

  • The Code Review Rubric self-review is written and filed โ€” you went line by line, you caught real things, and you documented what you fixed and what you deferred.
  • The Pre-Ship Checklist is checked โ€” tests passing, README accurate, no hardcoded secrets, coverage above threshold, conventional commits from here forward.
  • The critical path is covered by meaningful tests โ€” unit + integration + e2e all pass, assertions would catch real regressions, not just presence checks.
  • The README lets a stranger run, test, and deploy the app โ€” you tested this by following your own instructions from a fresh directory (or asked someone else to).
  • The before/after refactor is documented โ€” not just done, but explained: what changed, why, and what the tests proved stayed the same.
  • The ADR exists and is honest โ€” it captures a real decision, not an obvious one, and the Consequences section is candid about trade-offs.

A note on vibe coding: You got here because you could build fast. That skill is real and it matters โ€” don't let anyone tell you otherwise. This capstone isn't about slowing down. It's about building a foundation that lets you go faster next time โ€” because the tests catch the bugs before they catch you, the clean code is readable at 2 AM, and the README means your team (or your future self) never has to reverse-engineer what you built. The best engineers vibe-code the prototype and bring it to this standard. Now you do both.


โžก๏ธ Next: Stage 3 finishes with DevOps, Deployment & Operations โ€” ship it and keep it alive. Build It Right, Or Don't Build It At All. ๐Ÿ›๏ธ

Always-on rigor toolkit

๐Ÿ›๏ธ Build It Right, Or Don't Build It At All.

Capstone: Messy App โ†’ Tested, Clean & Documented โ€” TOVCDI | HYVE CARES