---
title: "01 — QA Foundations — Prevention Over Detection"
uid: qa-foundations
tags: ["roadmap:qa", "prioritization", "qa", "testing", "mindset", "test-oracles", "fundamentals"]
excerpt: "QA is process-oriented — it exists to prevent defects through the whole development cycle, not just detect them at the gate. The shift from find bugs to produce fewer is the whole reframe."
date: 2026-08-13T03:27:50+0000
source: https://www.aveshina.my.id/en/blog/qa-foundations
---

"The people who find bugs before release" was my QA definition, and it captured detection while missing the point. The idea that everything else hangs off: **QA is process-oriented and aims to prevent defects throughout the development cycle, not just detect them at the end** [1]. The shift from *find bugs* to *build a process that produces fewer bugs* is the whole reframe.

The framing that landed for me is the contrast between two adjacent words people use interchangeably. **Quality Control (QC)** is detection — inspecting the product, running tests, catching defects before they ship. **Quality Assurance (QA)** is prevention — shaping the process so defects are less likely to be introduced in the first place [1]. Testing is one tool inside the larger QA activity, not the whole of it. A test plan, a definition of done, a test oracle, a prioritization call — all of those are QA work that happens before a single test runs.

## The prevention-versus-detection split, as a picture

This is the diagram I keep coming back to. Same pipeline, two philosophies:

```figure
<svg viewBox="0 0 740 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Two versions of the same software pipeline. Top row labelled Detection (QC): four stages Requirements, Design, Code, Release with a single magnifying-glass icon only at the Release end. Bottom row labelled Prevention (QA): the same four stages, each with a small checkpoint tick, glowing. A caption under each explains the trade-off.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- TOP: Detection -->
    <text x="370" y="24" font-size="12" font-weight="700" fill="#7f1d1d" text-anchor="middle">Detection — QC: find defects at the gate</text>

    <g fill="#fee2e2" stroke="#dc2626" stroke-width="1.5">
      <rect x="40" y="40" width="140" height="44" rx="8"/>
      <rect x="210" y="40" width="140" height="44" rx="8"/>
      <rect x="380" y="40" width="140" height="44" rx="8"/>
      <rect x="550" y="40" width="140" height="44" rx="8"/>
    </g>
    <g font-size="12" font-weight="700" fill="#7f1d1d" text-anchor="middle" dominant-baseline="middle">
      <text x="110" y="63">Requirements</text>
      <text x="280" y="63">Design</text>
      <text x="450" y="63">Code</text>
      <text x="620" y="63">Release</text>
    </g>
    <!-- single check at end -->
    <circle cx="620" cy="108" r="14" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="620" y="112" font-size="14" text-anchor="middle">🔍</text>
    <text x="370" y="132" font-size="10" font-style="italic" fill="#7f1d1d" text-anchor="middle">defects found late — expensive to fix</text>

    <!-- BOTTOM: Prevention -->
    <text x="370" y="170" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">Prevention — QA: checkpoints at every stage</text>

    <g fill="#dcfce7" stroke="#16a34a" stroke-width="1.5">
      <rect x="40" y="186" width="140" height="44" rx="8"/>
      <rect x="210" y="186" width="140" height="44" rx="8"/>
      <rect x="380" y="186" width="140" height="44" rx="8"/>
      <rect x="550" y="186" width="140" height="44" rx="8"/>
    </g>
    <g font-size="12" font-weight="700" fill="#052e16" text-anchor="middle" dominant-baseline="middle">
      <text x="110" y="209">Requirements</text>
      <text x="280" y="209">Design</text>
      <text x="450" y="209">Code</text>
      <text x="620" y="209">Release</text>
    </g>
    <!-- check at every stage -->
    <g fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5">
      <circle cx="110" cy="252" r="11"/>
      <circle cx="280" cy="252" r="11"/>
      <circle cx="450" cy="252" r="11"/>
      <circle cx="620" cy="252" r="11"/>
    </g>
    <g font-size="12" text-anchor="middle">
      <text x="110" y="256">✓</text>
      <text x="280" y="256">✓</text>
      <text x="450" y="256">✓</text>
      <text x="620" y="256">✓</text>
    </g>
    <text x="370" y="274" font-size="10" font-style="italic" fill="#052e16" text-anchor="middle">defects caught early — cheap to fix</text>
  </g>
</svg>
```

The economics behind this are brutal and well documented: a defect caught in requirements costs roughly 1x to fix, the same defect caught in code costs 5–10x, and one caught in production costs 100x or more [3]. That ratio is the entire justification for moving QA left — earlier in the cycle — instead of parking it at the end.

## The test oracle problem

Once I had the prevention framing, the next idea I had to nail down was the **test oracle** — the mechanism, *different from the program itself*, used to decide whether the program's output is correct [2]. It sounds trivial until you try to test something without one.

The hard truth is that for most of the software I write, there is no independent spec I can hand the output to. The "specification" is usually another expression of the same code, or a fuzzy memory of what the stakeholder asked for. That's the **oracle problem**: how do you know the answer is right when you don't have an independent way to compute the right answer? The practical workarounds are:

- **A separate implementation** — a reference function, or a competing product's output, used as ground truth.
- **Properties, not values** — instead of asserting f(x) == 42, assert invariants that must hold for any output (e.g., "the sorted list contains exactly the same elements as the input"). This is what property-based testing exploits.
- **A human oracle** — a domain expert who knows the expected output. Expensive, slow, fallible, but often the only option.
- **Heuristics / metamorphic relations** — if I double the input, the output should double too; if I run it twice with the same input, the outputs must match.

Naming the oracle problem out loud made me stop writing tests that were just "re-execute the implementation and assert it equals itself," which is what a lot of brittle unit tests secretly are.

## The tester's mindset

The roadmap is emphatic that there is a distinct **QA mindset** — a set of critical-thinking habits — and it's not the same as the developer's mindset [4]. The tension is real: the person who wrote the code optimized hard to make it work, so they are psychologically invested in it working. The tester's job is to assume it doesn't and go hunting for the way it breaks.

The habits that separate a strong tester from someone merely running test cases [4][5]:

- **Questioning assumptions.** Especially the assumption that "happy path = the only path." What if the input is empty? Negative? Unicode? Concurrent?
- **Thinking in failure modes.** Instead of "does it do X?", asking "what would make X impossible?" — then trying to produce that.
- **Advocating for the user.** The stakeholder who asked for the feature is not the person who will use it under deadline pressure at 2am. The tester represents that person.
- **Thoroughness balanced with pragmatism.** You cannot test everything. The art is spending the finite budget where it finds the most defects — which is where prioritization comes in.

The mindset is the part you can't fully automate. Tools run the checks a human wrote; the human still has to imagine what could go wrong.

## Prioritization: where to spend the finite budget

Because testing is unbounded and time is not, **test prioritization** — ordering test cases so the highest fault-detecting ones run first — is its own discipline [6]. The roadmap frames it as meeting two constraints, time and budget, by maximizing the rate of fault detection as early as possible.

The levers I actually use:

- **Risk-based.** Rank by severity × likelihood. The payment flow ranks above the settings tooltip.
- **Coverage-based.** Which tests cover code paths that recently changed? Changed code is where new defects live.
- **Historical.** Which areas have produced the most defects before? That's a signal for where they'll appear again.
- **Business-critical path.** The journey a real user takes to do the thing that makes the company money gets tested first and most often.

The output of prioritization is an ordered list, and the first item runs first — so when CI fails on minute two, it fails on the test most likely to matter.

## White box, black box, gray box

The last foundational distinction the roadmap insists on is the **testing approach** — the perspective from which tests are designed [7]:

- **Black box.** Test behavior with no knowledge of internals. Input in, output out, spec on the wall. This is what most user-facing tests are.
- **White box.** Test internal structure — code paths, branches, conditions — with full visibility into the implementation. Coverage tools live here.
- **Gray box.** Some knowledge of internals, used to design better black-box tests. E.g., I know the API hits a cache, so I write a test that varies a header to confirm the cache key changes.

These aren't separate test suites; they're lenses. A good test strategy uses all three. Black box catches missing requirements; white box catches uncovered branches; gray box catches integration bugs that neither alone would surface.

```figure
<svg viewBox="0 0 560 200" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Three boxes representing testing approaches. Left: a solid black box labelled Black Box — tests behavior, internals hidden. Middle: a transparent box with visible gears labelled White Box — tests internal paths. Right: a half-shaded box labelled Gray Box — partial knowledge of internals.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- Black box -->
    <rect x="30" y="50" width="120" height="90" rx="8" fill="#1f2937" stroke="#0f172a" stroke-width="1.5"/>
    <text x="90" y="160" font-size="12" font-weight="700" fill="#1f2937" text-anchor="middle">Black Box</text>
    <text x="90" y="176" font-size="10" fill="#64748b" text-anchor="middle">behavior only</text>
    <text x="90" y="100" font-size="28" fill="#e5e7eb" text-anchor="middle">?</text>

    <!-- White box -->
    <rect x="220" y="50" width="120" height="90" rx="8" fill="#ffffff" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4 3"/>
    <text x="280" y="160" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">White Box</text>
    <text x="280" y="176" font-size="10" fill="#64748b" text-anchor="middle">internals visible</text>
    <g stroke="#16a34a" stroke-width="1.5" fill="none">
      <circle cx="252" cy="80" r="8"/>
      <circle cx="308" cy="80" r="8"/>
      <circle cx="280" cy="115" r="8"/>
      <line x1="260" y1="86" x2="276" y2="109"/>
      <line x1="300" y1="86" x2="284" y2="109"/>
    </g>

    <!-- Gray box -->
    <rect x="410" y="50" width="120" height="90" rx="8" fill="#9ca3af" stroke="#475569" stroke-width="1.5" opacity="0.6"/>
    <text x="470" y="160" font-size="12" font-weight="700" fill="#1f2937" text-anchor="middle">Gray Box</text>
    <text x="470" y="176" font-size="10" fill="#64748b" text-anchor="middle">partial knowledge</text>
    <text x="470" y="100" font-size="28" fill="#1f2937" text-anchor="middle">~</text>
  </g>
</svg>
```

## How I use this

The practical payoff is a checklist I run before writing any test suite:

- What's the oracle here — how, independently, do I know the right answer? If I can't answer, the test isn't going to catch much.
- Which lens — black, white, or gray — fits what I'm trying to catch? I reach for white-box when I want branch coverage, black-box when I want to verify user-visible behavior.
- What's the priority order? The top three tests should be the ones most likely to find a real defect, not the three easiest to write.
- Where in the cycle does this check belong? If it's catching a requirements ambiguity, it belongs at requirements review, not in CI.

QA is the whole-process activity of making defects less likely; testing is one input to it. Getting that order straight was the thing that made the rest of the roadmap click into place.

## References

[1] Guru99, "What is Quality Assurance (QA)? Process, methods, examples," 2023. [Online]. Available: [https://www.guru99.com/all-about-quality-assurance.html](https://www.guru99.com/all-about-quality-assurance.html)

[2] eComputernotes, "What are test oracles," 2023. [Online]. Available: [https://ecomputernotes.com/software-engineering/what-are-test-oracles](https://ecomputernotes.com/software-engineering/what-are-test-oracles)

[3] ISTQB, "Certified Tester Foundation Level Syllabus v4.0," 2024. [Online]. Available: [https://astqb.org/assets/documents/ISTQB_CTFL_Syllabus-v4.0.pdf](https://astqb.org/assets/documents/ISTQB_CTFL_Syllabus-v4.0.pdf)

[4] softwaretester.careers, "The software tester's mindset," 2023. [Online]. Available: [https://softwaretester.careers/the-software-testers-mindset/](https://softwaretester.careers/the-software-testers-mindset/)

[5] B. Norrish, "How to think like a tester," Medium, 2022. [Online]. Available: [https://medium.com/@blakenorrish/how-to-think-like-a-tester-7a174ff6aeaf](https://medium.com/@blakenorrish/how-to-think-like-a-tester-7a174ff6aeaf)

[6] BrowserStack, "A tour of what is test prioritization?," 2023. [Online]. Available: [https://www.browserstack.com/guide/test-case-prioritization](https://www.browserstack.com/guide/test-case-prioritization)

[7] QualityLogic, "Functional testing approaches and agile considerations," 2023. [Online]. Available: [https://www.qualitylogic.com/knowledge-center/functional-testing-approaches-agile-considerations/](https://www.qualitylogic.com/knowledge-center/functional-testing-approaches-agile-considerations/)

```quiz
Q: The core distinction between Quality Assurance (QA) and Quality Control (QC) is…
- QA is prevention-oriented across the whole process; QC is detection at the end
- QA runs automated tests; QC runs manual tests
- QA is for backend; QC is for frontend
correct: 0
explain: QA shapes the process to prevent defects throughout the cycle. QC inspects the finished product to catch defects before release. Testing is one tool inside the larger QA activity.

Q: A test oracle is…
- the same program, re-run with different inputs
- an independent mechanism used to decide whether the program's output is correct
correct: 1
explain: The oracle must be different from the program under test, otherwise you're just asserting the program equals itself. Reference implementations, properties, and human experts are common oracles.

Q: Why does test prioritization matter?
- Because testing is unbounded and time/budget are not — ordering maximizes early fault detection
- Because CI can only run one test at a time
- Because low-priority tests are always wrong
correct: 0
explain: With finite time, you want the tests most likely to find defects to run first, so a failure surfaces in minute two, not minute forty.

Q: In gray-box testing, the tester…
- has no knowledge of internals at all
- has full visibility into every line of code
- has partial knowledge of internals, used to design better behavioral tests
correct: 2
explain: Gray box combines both lenses — some knowledge of internals (e.g., a cache exists) shapes black-box-style tests (vary the header to confirm the cache key changes).

Q: A defect caught in requirements versus the same defect caught in production typically costs…
- roughly the same to fix
- orders of magnitude more in production
- less in production, because the team is already deployed
correct: 1
explain: The cost of fixing a defect grows steeply the later it's caught — often 100x or more from requirements to production. That ratio is the entire justification for shifting QA left.
```
