Testing

Concept

Writing down inputs and expected results ahead of time so a machine re-checks that code behaves as intended, instead of a person clicking through it.

Why it exists

After changing code, somebody has to confirm it still works. Back when a product had five screens, that was easy. Whoever made the change opened a browser, clicked through the five screens, and was done in ten minutes.

At a hundred screens the habit quietly broke. Nobody clicks through a hundred screens after touching one file. They check the area around their own change instead, and that works right up until it doesn't, because code is not connected the way screens look connected. A change to the checkout screen breaks the notification email when both call the same shared function.

At Pagedeck this surfaced while adding expiry dates to document share links. Theo built the feature, clicked through creating a link and opening it, and shipped on a Friday. Two days later Sofia called from the middle of a customer demo: people who had been invited to a document could no longer open it. The permission check he had edited to handle expiry ran on the invite path too, and he never opened the invite screen. He had no reason to. That file never appeared in the change.

The failure is not that someone was careless. It is that the scope of re-checking was decided by human memory. Memory keeps up in a ten-file product and does not keep up in a two-thousand-file one, and it degrades fastest exactly where it matters most, in the shared code several features quietly lean on. Manual checking also charges full price every time. A flow that took thirty minutes to verify yesterday takes thirty minutes again today after a one-line change.

Testing moves that verification out of memory and onto the page. You write, in code, that feeding in points of 3, 5 and 2 must produce a total of 10 — the input and the result it has to produce. Once written, every check you have ever written re-runs automatically no matter who changes what, and anything that broke reports itself by name in red. Nothing about human checking is removed; the one check a person already did is simply stored in a form that can be replayed. The economics flip as a result. Manual verification costs the same every release forever, while a written check costs an hour once and pennies after that.

Within engineering, testing sits in the quality group. Code quality, its neighbor there, lowers the odds of something breaking six months from now. Testing finds what is already broken today.

How this field breaks down

The terms under testing fall into eight groups, and four questions organize them: how wide a slice you check at once (scope), what you are trying to prevent (purpose), how you work and what props the work up (practice and tooling), and what you measure, what goes wrong, and who handles it (coverage, flakiness, assurance, defects).

Scope splits by how large a chunk each check covers, and it has four members.

  1. Unit test — one function or module, pulled out on its own. Feed values into a pricing function and look only at what comes back. A single case finishes in a millisecond, so two thousand of them run in under a minute.
  2. Integration test — several pieces wired together. The pricing function, the database and the payment module all connected for real. Parts that are each fine on their own break at the seams often enough to deserve their own layer.
  3. End-to-end test — an end-to-end (E2E) test launches a real browser and clicks from login to checkout the way a person would. It is the closest thing to reality and also the slowest, roughly ten seconds per case, and it breaks when a button label changes.
  4. Smoke test — a handful of critical paths checked right after a deploy. Does login work, does the home screen render, does a document open. The goal is not coverage; it is deciding within two minutes whether to roll back.

The four are layers, not rankings. Lower down is faster, cheaper, and more precise about where the break is, but further from a real user. Higher up is closer to reality but slower and more fragile. Teams typically keep thousands of unit tests and a few dozen end-to-end tests covering signup, checkout and similar paths. Invert that ratio — five hundred end-to-end tests — and a full run takes two hours, at which point nobody runs it at all.

Purpose splits by what the run is meant to prevent, and it has two members.

  1. Regression test — checks that what already worked still works after a change. The share-link incident above is exactly what this layer catches. It asks whether the untouched parts stayed intact, not whether the new feature works.
  2. Load test — checks whether the system holds up under a crowd. A service that answers in 0.3 seconds at 500 concurrent users but takes 8 seconds at 2,000 is functionally broken for the user even though every feature works. You cannot learn that number without the crowd, so you create one deliberately.

Practice and tooling cover how testing is used and what supports it. Three members.

  1. Test-driven development (TDD) — write the test before the implementation. State the expected result of a feature that does not exist yet, then fill in code to turn red into green. Reversing the order forces you to define what "done" means before building, which cuts down on the tests-written-afterward problem where the expected value is quietly set to whatever the code already returns.
  2. Mock — a stand-in that only pretends to be the real thing. You cannot send a real request to a card processor every time the payment tests run, so you put a fake processor that always answers "approved" in its place. It exists to cut out anything slow, expensive, or dependent on someone else's server.
  3. Fixture — the state and data laid out before a test runs. Rather than hand-building "three documents and two users already exist" each time, you describe it once and load it.

The last cluster is what you measure, what goes wrong, and who deals with the result. Four members.

  1. Test coverage — the share of code the tests actually executed. If 720 of 1,000 lines ran at least once, coverage is 72 percent. A low number is reliably bad news and a high number is not reliably good news, which is what makes this figure awkward to manage.
  2. Flaky test — a test that passes some days and fails others with no code change in between, usually because it depends on timing or on the order the tests run in. As flaky tests accumulate, the team starts seeing red and shrugging it off as "those again," and from that moment the suite has stopped working as a warning.
  3. Quality assurance (QA) — the whole activity of checking and vouching for quality before release. Automated tests live inside it, and so do people using the product to find what feels wrong, and the work of deciding what has to be verified before anything ships.
  4. The defect group covers what happens after something is found. A bug is the fault itself, behavior that differs from intent. Severity is how much that fault costs the service, which keeps a blocked checkout and a misaligned label off the same line. Reproduction steps are the recorded sequence that triggers the fault again; without them, whoever fixes it spends the first hour reconstructing the situation.

The eight groups chain together. Scope decides how wide a slice gets checked, purpose decides when those checks run, practice and tooling make them workable, coverage shows how much is included, flakiness governs whether the signal can be trusted, and assurance and defects hand what was caught back to people.

How it sounds at work

"This pull request is red. Can someone look before we merge it?"

A test failed on the code about to be merged, so find out why first.

"If we're tight, let's skip the end-to-end run and ship. But we're definitely running smoke right after deploy."

There isn't time for the full check, so drop the slow layer, on the condition that the critical paths get verified the moment it goes out.

"That case was never in the tests, so it passed. I'll add the case first, then fix it."

The test wasn't wrong; the situation was never written down. The fix starts by writing it down.

After the share-link incident, Pagedeck changed one thing. Theo wrote a test against the permission check stating that an invited person must be able to open a document regardless of whether a share link has expired. For that failure to recur now, that test has to go red first.

Mia uses the same loop from the product side. When she hands over a spec, she lists three or four situations that have to be verified, and Theo turns those lines into test names more or less verbatim. Problems Sofia hears from customers after launch enter at the same point. Anything the team has been burned by once gets moved out of human memory and into a test.

What people get wrong

The most common mistake is reading a fully green suite as proof there are no bugs. A screen full of green makes that a natural conclusion. But a test only checks the cases someone wrote down in advance. A situation nobody described isn't merely unchecked; as far as the suite is concerned, it does not exist. If no one ever wrote a case for uploading a file with a 200-character name, that case will never be inside the green. Left alone, this belief turns green into a release permit, and on the day a 90-percent-coverage product goes down, the first sentence in the room is "but every test passed." Passing means what was written down did not break, not that what was never written down is fine. The useful habit is to treat every bug that reaches a customer as a missing case rather than a missing fix.

The second mistake is treating tests as extra work bolted onto feature development. It looks like writing the same feature twice, so it becomes the first thing dropped when a deadline tightens. In practice it is closer to moving verification you were already doing to the front and paying for it once. Teams that check by hand still click through the same screens before every deploy, and those hours are spent whether or not tests exist. Keep dropping the tests and the pre-deploy manual pass grows into a half-day job; once half a day feels expensive, releases slide from weekly to every other week. Bigger batches then ship at once, and the bigger the batch, the harder it is to find the cause when something goes wrong.

Easily confused with

TermWhat it refers toHow it differs from testing
Quality assuranceThe full activity of checking and vouching for quality before releaseBroader than testing. It includes setting the bar for what must be verified before shipping and people trying the product by hand; testing is the machine-run part inside it
TestThe stage in a design process where a proposed design is put in front of usersDifferent subject. This page asks whether code behaves correctly; that stage asks whether a person can accomplish their goal with the screen

Where to go next

  • Engineering

    see which of the thirteen groups testing sits in before going deeper

  • Unit test

    start with the layer you write first and accumulate most of

Sub-terms

Last updated 2026-09-16