Test Levels & Test Types
Unit, integration, system and acceptance testing, functional vs non-functional testing, and the test pyramid.
Test levels
Test levels describe what size of thing you're testing. Each level catches different defects.
Unit testing
Tests a single function or class in isolation, usually written by developers.
# discount.py
def apply_discount(total: float, percent: int) -> float:
if not 0 <= percent <= 100:
raise ValueError("percent must be between 0 and 100")
return round(total * (100 - percent) / 100, 2)
# test_discount.py — run with: pytest
import pytest
from discount import apply_discount
def test_applies_percentage():
assert apply_discount(200, 10) == 180.0
def test_zero_and_full_discount():
assert apply_discount(50, 0) == 50.0
assert apply_discount(50, 100) == 0.0
def test_rejects_invalid_percent():
with pytest.raises(ValueError):
apply_discount(50, 120)
Unit tests are fast (milliseconds), precise about where a bug is, and run on every commit.
Integration testing
Tests that components work together — a service and its database, two microservices, your app and a payment API. Integration bugs are about contracts: wrong field names, wrong date formats, timeouts, transactions that don't roll back.
System testing
Tests the complete, deployed application end to end against the requirements — both functional behaviour and non-functional qualities like performance and security. Usually owned by QA.
Acceptance testing
Confirms the system meets business needs and is ready for release.
- User Acceptance Testing (UAT): real users or the product owner try real workflows.
- Alpha testing: internal users test before release.
- Beta testing: a limited group of real customers use it in production.
Functional vs non-functional testing
Functional testing checks what the system does — features against requirements: "a user with the right password can log in."
Non-functional testing checks how well it does it:
| Type | Question it answers | Typical tools |
|---|---|---|
| Performance / load | Is it fast enough under expected traffic? | k6, JMeter, Locust |
| Stress | Where does it break, and does it recover? | k6, JMeter |
| Security | Can it be attacked? | OWASP ZAP, Burp Suite |
| Usability | Can people use it easily? | User sessions, heuristics |
| Accessibility | Can people with disabilities use it? | axe, Lighthouse, screen readers |
| Compatibility | Does it work on all target browsers and devices? | BrowserStack, Playwright projects |
| Reliability | Does it keep working over time? | Soak tests, chaos testing |
Black-box, white-box and grey-box
- Black-box: you test through the interface without knowing the code — most QA testing.
- White-box: you design tests from the code structure (branches, paths) — most unit testing.
- Grey-box: partial knowledge — for example, knowing the database schema helps you design API tests.
Other test types you'll hear about
- Smoke testing — a quick check that a build's critical paths work before deeper testing.
- Sanity testing — a narrow check that a specific fix or change works.
- Regression testing — re-testing existing features after a change to make sure nothing broke.
- Exploratory testing — simultaneous learning, test design and execution.
- Retesting — running the failed test again after a fix (different from regression).
The Regression, Smoke & Sanity Testing lesson covers these in practice.
The test pyramid
The test pyramid is a guide for how many tests to have at each level:
/\ End-to-end UI tests — few, slow, test whole journeys
/ \
/----\ Integration / API tests — some, medium speed
/ \
/--------\ Unit tests — many, fast, cheap
Most checks belong at the bottom where they're fast and reliable; a small number of end-to-end tests confirm the main user journeys work. Teams that invert the pyramid (mostly UI tests) end up with slow, flaky suites. See Test Automation for how this shapes an automation strategy.