Levels and Types of Testing
When people first hear “testing” they imagine one activity: clicking around an app looking for bugs. In reality, testing happens in distinct layers, at different moments, performed by different people, and aimed at catching very different problems. A bug inside a single function is not found the same way as a bug that only appears when ten modules work together, which again is different from a performance collapse under load. This module gives you the vocabulary and the mental map — the levels of testing (how deep) and the types of testing (what quality we are checking) — so that when someone says “we need system-level regression and a quick smoke after the deploy,” you know exactly what they mean and what to do.
Learning objectives
By the end of this lesson you will be able to:
- Describe the four test levels — unit, integration, system, acceptance — and say who performs each and what each is designed to catch.
- Explain the main integration approaches (big-bang, top-down, bottom-up, sandwich) and the role of stubs and drivers.
- Distinguish functional from non-functional testing and name the key non-functional types.
- Contrast smoke vs sanity and regression vs retesting precisely — two pairs interviewers love to confuse you with.
- Explain black-box, white-box, and grey-box testing and when each applies.
The four test levels: how deep are we looking?
A test level is the scope of what you are testing — how much of the system is wired together at the moment you test it. The classic model has four levels, and the cleanest way to remember them is to picture them stacking up from the smallest piece of code to the finished product in a real user’s hands.
Analogy: building and inspecting a car
Think of a car factory. First, each part is checked on its own — does this one spark plug fire, does this one brake caliper grip? That is unit testing. Next you bolt parts together and check the joints — does the fuel pump actually feed the engine through the pipe that connects them? That is integration testing. Then you start the whole assembled car and drive it around the test track — lights, steering, braking, acceleration all working together as one vehicle. That is system testing. Finally the customer takes it for a test drive to decide “is this the car I actually wanted to buy?” That is acceptance testing. Each stage catches faults the next stage would find far more expensively — it is much cheaper to replace a faulty spark plug on the bench than when the customer is stranded on a motorway.
Level 1 — Unit testing
Unit testing checks the smallest testable piece of software in isolation — typically a single function, method, or class. The goal is to prove that one small building block does exactly what it should for a range of inputs, including odd ones, before it is combined with anything else.
- Who does it: usually the developer who wrote the code, often as automated tests (using frameworks like JUnit, NUnit, or PyTest).
- What it catches: logic errors in a single function — a wrong calculation, an off-by-one error, a mishandled null.
- Why it matters to a manual tester: even though you may not write unit tests, knowing they exist tells you where the cheap safety net is. Low unit coverage is a warning that even simple logic bugs may reach you.
Level 2 — Integration testing
Integration testing checks that separately developed units work correctly together. Individual pieces can each be perfect and yet fail at the seams: mismatched data formats, a wrong API call, a module expecting dollars while another sends cents. Integration testing targets exactly those joins.
- Who does it: developers and/or testers, depending on the team.
- What it catches: interface mismatches, broken data passing between modules, incorrect assumptions one module makes about another.
A simple example: a Login module authenticates the user, then hands control to a Dashboard module that loads the user’s data. Each works alone, but if Login passes the user’s ID as a string and Dashboard expects a number, the dashboard shows nothing. Only integration testing reveals that.
Level 3 — System testing
System testing evaluates the complete, integrated application as a whole, in an environment that mimics production. This is end-to-end: real user workflows from start to finish, against requirements. It is where most manual testers spend the bulk of their time.
- Who does it: the independent QA / testing team.
- What it catches: gaps between what was built and what was required, broken end-to-end flows, and — importantly — both functional and non-functional problems (performance, security, usability) of the whole product.
Level 4 — Acceptance testing (UAT)
Acceptance testing answers one business question: “Is this system acceptable to deliver?” The focus shifts from “does it work?” to “does it meet the real needs of the people who will use and pay for it?” The most common form is User Acceptance Testing (UAT), performed by the actual end users or the client.
- Who does it: end users, business stakeholders, or the client — not the development team.
- What it catches: requirements that were built correctly but were the wrong requirements; workflows that are technically fine but do not fit how the business actually works.
You will also hear Alpha testing (done in-house by the organisation’s own staff before release) and Beta testing (done by a limited set of real external users in their own environment). Both are forms of acceptance testing.
| Level | Scope | Who performs it | Main faults caught |
|---|---|---|---|
| Unit | One function / class | Developer | Logic errors in isolated code |
| Integration | Modules joined together | Developer / Tester | Interface & data-passing faults |
| System | Whole integrated app | QA team | End-to-end & requirement gaps |
| Acceptance | Whole app vs business need | End users / client | “Right product?” mismatches |
Integration approaches: how do we wire the pieces together?
Integration testing is not a single technique — there are several strategies for the order in which you combine modules. To understand them you first need two helper concepts.
- Stub
- A stub is a dummy stand-in for a module that is called by the module you are testing but is not ready yet. It replaces a “lower” or “called” component and returns canned answers. Used in top-down integration.
- Driver
- A driver is a dummy stand-in that calls the module you are testing — it simulates a “higher” or “calling” component that is not ready yet. Used in bottom-up integration.
Memory hook: a driver drives (calls into) the module from above; a stub is stubbed in below as a fake thing your module calls.
The four approaches
- Big-bang integration
- Wait until all modules are built, combine everything at once, and test the whole lot together. It is simple to set up but brutal to debug — when something breaks, the fault could be anywhere, and nothing is tested until the very end. Best only for small systems.
- Top-down integration
- Start from the top-level (controlling) modules and work downward. Because the lower modules may not exist yet, you use stubs to stand in for them. Advantage: major design and control-flow flaws surface early. Disadvantage: low-level details are tested late.
- Bottom-up integration
- Start from the lowest-level modules and work upward. Because the higher modules that would call them may not exist yet, you use drivers to invoke them. Advantage: foundational utilities are verified early. Disadvantage: the overall product is not visible until late.
- Sandwich / hybrid integration
- Combine top-down and bottom-up at the same time, meeting in the middle. It uses both stubs and drivers. It gets the best of both approaches but needs more resources and planning.
Analogy: building a house
Imagine constructing a house. Top-down is like finishing the roof and upper floors first, propping up the unbuilt lower floors with temporary scaffolding (stubs) so the structure holds while you work down. Bottom-up is laying the foundation and ground floor first, using a temporary crane (driver) to simulate the weight and connections of floors not yet built. Big-bang is assembling every pre-fabricated room in a warehouse and only bolting them together on the final day — thrilling if it works, a nightmare to diagnose if a pipe leaks somewhere. Sandwich is a big crew working from the roof down and the foundation up simultaneously to finish faster.
Functional vs non-functional testing: what are we checking?
Where levels describe how much of the system is in scope, types describe which quality you are verifying. The biggest split is functional versus non-functional.
Functional testing checks what the system does — its features and behaviour against the requirements. “When I click Transfer with a valid amount, does the money move and the balance update?” If a requirement says the system shall do something, functional testing proves it does.
Non-functional testing checks how well the system does it — the qualities and constraints, not the features themselves. The transfer might work perfectly for one user but take 40 seconds under 5,000 simultaneous users, or leak the account number in an error message. Those are non-functional concerns.
One-line distinction: functional = “does it work?”; non-functional = “how well does it work?”
The key non-functional types
| Type | Question it answers | Example check |
|---|---|---|
| Performance | How fast / responsive is it under expected conditions? | A page loads in under 2 seconds with normal traffic. |
| Load | Does it hold up at the expected peak number of users? | Checkout stays stable with 5,000 concurrent shoppers. |
| Stress | Where does it break, and does it fail gracefully beyond its limits? | Push to 20,000 users; confirm it degrades, not corrupts data. |
| Usability | Is it easy and intuitive for real users? | A first-time user completes signup without help. |
| Security | Is data and access protected from misuse? | A normal user cannot reach admin URLs or read others’ data. |
| Compatibility | Does it work across browsers, OSes, devices? | Works on Chrome, Safari, Edge, and on Android & iOS. |
| Reliability | Does it keep working correctly over time? | Runs for 72 hours of continuous use without crashing. |
A useful nuance: load testing stays within expected demand, while stress testing deliberately pushes past it to find the breaking point. People often say “performance testing” as an umbrella that includes load and stress.
Smoke vs sanity testing
These two short, early checks are constantly mixed up. Both are quick and both decide “is this build worth testing further?” — but they differ in breadth and depth.
Smoke testing is a wide but shallow check performed right after a new build arrives: do the most critical features launch at all? Can I log in, does the home page load, can I open the main modules? If smoke fails, the build is rejected immediately — no point testing deeper. The name comes from hardware: power it on and see if smoke comes out.
Sanity testing is a narrow but deeper check performed on a relatively stable build, usually after a small change or bug fix: does the specific area that changed now behave correctly and rationally? It is focused verification that it is “sane” to proceed with detailed testing of that area.
| Aspect | Smoke testing | Sanity testing |
|---|---|---|
| Purpose | Is the build stable enough to test at all? | Does a specific change/fix work rationally? |
| Breadth vs depth | Wide and shallow | Narrow and deep |
| When | On every new build | After minor changes or bug fixes |
| Scope | Critical paths across the whole app | A few related features only |
| Usually | Can be scripted / automated | Often unscripted, ad hoc |
Regression vs retesting
Another pair that is easy to blur — and getting it right in conversation marks you as someone who understands the craft.
Retesting (also called confirmation testing) means running the same test that previously failed, on a new build, to confirm the specific bug is now fixed. You use the same data and the same steps, and you only care about that one defect. Retesting is always planned — you know which defect you are confirming.
Regression testing means re-running tests on areas that were already working, to make sure a new change (a fix or a feature) did not accidentally break them. A fix in one place can have side effects elsewhere; regression is your insurance against those side effects.
| Aspect | Retesting | Regression testing |
|---|---|---|
| Goal | Confirm a known defect is fixed | Confirm existing features still work |
| Focus | The failed test case only | Related / unchanged areas |
| Uses | Same steps & data that failed | A broad suite of previously passing tests |
| Automation | Rarely automated | Prime candidate for automation |
| On passing tests? | No — only on failed ones | Yes — mostly on passed ones |
Deeper look: why regression grows and why we automate it
Every time a product gains a feature, the regression suite grows — each new area is another thing that a future change might break. After a year, running the full regression suite by hand before every release becomes impractical. This is the single biggest reason teams invest in automation: regression tests are repetitive, stable, and run constantly, so machines should run them while humans focus on exploring new features. When you hear “we automated our regression pack,” this is the economics behind it.
Black-box, white-box, and grey-box testing
This final trio classifies testing by how much of the internal code you can see.
- Black-box testing
- You test purely through the external behaviour — inputs and outputs — without any knowledge of the internal code. You know what it should do, not how it does it. This is the bread and butter of manual functional testing: you work from requirements. The test-design techniques in the next module (equivalence partitioning, boundary values) are black-box techniques.
- White-box testing
- You test with full visibility of the internal code structure, aiming to exercise specific paths, branches, and statements. It requires programming knowledge and is usually done by developers. Coverage goals like “every branch executed at least once” are white-box ideas.
- Grey-box testing
- A blend: you have partial internal knowledge — perhaps the database schema or the API structure — but still test mainly from the outside. Example: knowing the database design so you can verify, after a UI action, that the right row was actually written. Common in integration and API testing.
Analogy: three ways to test a vending machine
Black-box: you insert a coin, press B4, and check a chocolate bar drops — you never open the machine. White-box: you open the back, trace the wiring and the dispensing motor, and verify each internal path works. Grey-box: you have the wiring diagram in hand but still test from the front — you press buttons knowing which internal slot should activate, so you can tell exactly where a failure lies.
Common mistakes to avoid
- Confusing smoke and sanity. Smoke is wide-and-shallow on a fresh build; sanity is narrow-and-deep on a stable one after a change.
- Saying “regression” when you mean “retesting.” Retesting confirms the fixed bug; regression guards the unchanged features.
- Thinking acceptance testing is the QA team’s job. UAT belongs to the users / client — its whole point is an outside-the-build-team perspective.
- Mixing up stubs and drivers. Stubs replace called (lower) modules in top-down; drivers replace calling (upper) modules in bottom-up.
- Treating load and stress as the same thing. Load stays within expected demand; stress deliberately exceeds it to find the breaking point.
- Ignoring non-functional testing until the end. A feature-complete app that is slow, insecure, or unusable is not shippable — plan these early.
Tutor tip: how these fit together on a real project
On a typical sprint, the picture looks like this: developers write unit tests as they code; a new build arrives and you run a quick smoke test to accept it; you then do system-level functional testing of the new feature (black-box, from requirements); you run sanity on any late fixes; before release you run a regression pack to protect existing features and retest each fixed defect; the performance team runs load and stress; and finally the client does UAT. Every term in this module has a slot in that flow.
Hands-on exercise
Imagine an online food-delivery app with these modules: Login, Restaurant Search, Cart, Payment, and Order Tracking. Work through the following on paper:
- For each of the four test levels, write one concrete example of something you would test in this app, and name who would perform it.
- The Payment module is built first but the Cart that calls it is not ready. Which integration approach fits, and would you write a stub or a driver? Explain why.
- List three functional and three non-functional tests for the Payment flow. For each non-functional one, name the type (performance, security, etc.).
- A new build just arrived at 9am. Write a 5-step smoke test. Later, a developer fixes a bug where the cart total was wrong — describe the sanity test you would run and how it differs from the smoke test.
- The fix in question 4 is verified. What regression risk does it create, and which already-working feature would you re-test as a result?
Knowledge check
- Name the four test levels in order and say who typically performs each.
- In top-down integration, do you use stubs or drivers — and what do they stand in for?
- Give a one-sentence difference between functional and non-functional testing.
- What is the core difference between load testing and stress testing?
- Contrast smoke and sanity testing in terms of breadth and depth.
- Explain the difference between retesting and regression testing.
- Which type of testing — black, white, or grey box — is a manual tester working only from requirements doing?
Show answers
- Unit (developer), integration (developer/tester), system (QA team), acceptance/UAT (end users / client).
- Stubs — they stand in for the lower-level called modules that are not built yet.
- Functional checks what the system does (features vs requirements); non-functional checks how well it does it (speed, security, usability, etc.).
- Load tests within the expected peak demand; stress deliberately pushes beyond it to find the breaking point and confirm graceful failure.
- Smoke is wide and shallow across critical features on a fresh build; sanity is narrow and deep on a specific changed area of a stable build.
- Retesting re-runs the exact failed test to confirm a bug is fixed; regression re-runs previously passing tests to confirm a change broke nothing else.
- Black-box testing — behaviour only, no view of the internal code.
Key takeaways
- Levels = scope: unit → integration → system → acceptance, each catching cheaper-to-fix faults earlier.
- Integration approaches: big-bang, top-down (stubs for called modules), bottom-up (drivers for calling modules), sandwich (both).
- Functional = “does it work?”; non-functional = “how well?” (performance, load, stress, usability, security, compatibility, reliability).
- Smoke (wide/shallow, fresh build) vs sanity (narrow/deep, after a fix); retesting (confirm one fix) vs regression (protect everything else).
- Black / white / grey box classify testing by how much internal code you can see — manual functional testing is mostly black-box.
Go deeper
Two things extend this module: a model for how much testing to do at each level, and a handful of non-functional types that did not fit the table but appear on real projects.
The test pyramid. Knowing the four levels is one thing; knowing how much to invest in each is another. The test pyramid advises a wide base of fast, cheap unit tests, a smaller middle of integration tests, and a thin tip of slow, expensive end-to-end/system tests. The common anti-pattern is the inverted “ice-cream cone”: almost no unit tests and a huge pile of brittle UI tests, which is slow to run and painful to maintain. When you hear a team complain that “the regression suite takes six hours and keeps flaking,” an upside-down pyramid is usually the reason.
More non-functional types worth naming. The table covered the big seven; these come up often enough to know:
- Accessibility — can people using screen readers, keyboard-only navigation, or high-contrast modes actually use the product? (Often a legal requirement, e.g. WCAG.)
- Localisation / internationalisation — does it behave correctly across languages, date formats, currencies and right-to-left scripts?
- Recovery — after a crash, power loss or dropped connection, does it restore cleanly without losing or corrupting data?
- Installation / upgrade — does a fresh install, and an upgrade from the previous version, work without wrecking existing user data?
A nuance on “end-to-end”. People sometimes say “E2E” as if it were a fifth level. It is better thought of as a style of system/acceptance testing — following a complete real-world workflow across every integrated component — rather than a separate level of its own.