Acceptance Test
An acceptance test verifies that a feature satisfies a business requirement from the user's point of view, expressed in terms of observable behaviour rather than internal implementation. It is often written in a Given/When/Then form and run against a deployed or fully wired system. It answers "did we build the right thing?" rather than "did we build the thing right?".
Arrange-Act-Assert (AAA)
Arrange-Act-Assert is a structural convention for a test body: arrange the inputs and preconditions, act by invoking the unit under test exactly once, and assert the expected outcome. Keeping the three phases visually distinct makes a test easy to read and signals when a test is doing too much (multiple act steps). It is the procedural cousin of the Given/When/Then phrasing used in BDD.
Assertion
An assertion is a statement in a test that declares an expected condition; if the condition is false the test fails. Good assertions are specific and produce a clear failure message that names the expected and actual values. A test should generally assert one logical behaviour, even if that requires several physical assert calls describing the same outcome.
Black-Box Testing
Black-box testing exercises a system through its public interface without knowledge of its internal structure, deriving cases from the specification, inputs, and outputs. It complements white-box testing, which uses knowledge of the code to target paths and branches. Equivalence partitioning and boundary-value analysis are classic black-box techniques.
Boundary-Value Analysis
Boundary-value analysis is a test-design technique that focuses cases on the edges of input ranges, where defects cluster, rather than on typical interior values. For a range valid from 1 to 100 it tests 0, 1, 2, 99, 100, and 101. It pairs naturally with equivalence partitioning, which first groups inputs that should behave identically.
Code Coverage
Code coverage measures the proportion of production code executed by a test suite, reported as line, branch, statement, or condition coverage. It is a proxy for thoroughness, not a measure of test quality: 100% line coverage can still miss behaviours and error paths. Treat coverage as a tool to find untested code, not as a target to game.
Contract Test
A contract test verifies that a service honours the request/response contract a consumer relies on, so a provider can evolve safely. In consumer-driven contract testing the consumer publishes its expectations (a pact), and the provider verifies it can satisfy them in isolation. Contract tests catch integration breakage without the cost and flakiness of full end-to-end environments.
Cyclomatic Complexity
Cyclomatic complexity counts the number of linearly independent paths through a piece of code, roughly one plus the number of decision points. It indicates the minimum number of test cases needed to cover every branch and is a useful signal for where bugs and untested paths hide. High complexity is a refactoring trigger as much as a testing one.
Defect (Bug)
A defect is a flaw in code or design that causes the system to behave differently from its specification or the user's reasonable expectation. A defect found in production is far more expensive to fix than one found in development, which is the economic case for shifting testing left. Defects are distinct from failures (the observable symptom) and errors (the human mistake that introduced them).
End-to-End Test (E2E)
An end-to-end test exercises a complete user journey through the fully integrated system, typically driving the real UI and backing services as a user would. E2E tests give the highest confidence that the assembled product works but are the slowest, most brittle, and most expensive to maintain, so they sit at the narrow top of the test pyramid. Keep them few and reserved for critical paths.
Equivalence Partitioning
Equivalence partitioning divides the input domain into classes that the system should treat identically, so that testing one representative value per class approximates testing them all. It reduces the number of cases without sacrificing meaningful coverage and is usually combined with boundary-value analysis at the edges of each partition. It applies to valid and invalid input classes alike.
Exploratory Testing
Exploratory testing is simultaneous learning, test design, and test execution, where the tester actively investigates the system and uses results to steer the next action. It is structured (often time-boxed into "sessions" with a charter), not random, and excels at finding defects that scripted tests miss. It complements rather than replaces automated regression checks.
Fake
A fake is a test double with a working but simplified implementation, such as an in-memory repository standing in for a database. Unlike a mock or stub, a fake actually does something useful, which makes it well suited to integration-style tests where you want real behaviour without real infrastructure. It is one of the five test-double types named by Gerard Meszaros.
Flaky Test
A flaky test is one that passes and fails non-deterministically on the same code, eroding trust in the suite and masking real regressions. Common causes are timing and race conditions, reliance on wall-clock time, shared mutable state, test-order dependence, and unstable external dependencies. The fix is to make the test deterministic (control time, wait on conditions not sleeps, isolate state), not to retry it blindly.
Fixture
A fixture is the fixed baseline state and data a test needs before it runs, set up in advance and torn down afterward. A good fixture is minimal and explicit so the reader sees exactly what the test depends on. Setup/teardown hooks (such as JUnit's @BeforeEach/@AfterEach) build and dismantle fixtures around each test.
Fuzz Testing
Fuzz testing feeds a program large volumes of random, malformed, or unexpected input to surface crashes, hangs, memory errors, and unhandled exceptions. Coverage-guided fuzzers (such as those built on libFuzzer or Go's native fuzzing) mutate inputs to maximise code paths reached. It is especially effective for parsers, decoders, and any code at a trust boundary.
Given-When-Then
Given-When-Then is a structured way to phrase a behaviour example: Given a starting context, When an action occurs, Then an expected outcome holds. It comes from Behaviour-Driven Development and underpins Gherkin scenarios used by tools like Cucumber. It maps directly onto the Arrange-Act-Assert structure of a test body.
Golden Test (Snapshot Test)
A golden or snapshot test compares current output against a previously approved reference ("golden") output, failing on any difference. It is efficient for large structured outputs (rendered UI, serialized data) but can entrench bugs if snapshots are blindly re-approved. Review snapshot diffs as carefully as code, and keep snapshots small and meaningful.
Integration Test
An integration test verifies that several components work together correctly, often with real infrastructure such as a database, message broker, or HTTP dependency. It catches wiring, mapping, SQL, and serialization defects that unit tests with mocks cannot. Tools like Testcontainers make integration tests reliable by spinning up real dependencies in disposable containers.
Load Test
A load test measures how a system behaves under an expected or peak level of concurrent demand, reporting latency percentiles, throughput, and error rate. It answers "does the system meet its performance targets at this volume?" and is distinct from a stress test, which pushes beyond limits to find the breaking point. Load tests should assert on tail latency (p95/p99), not just averages.
Mock
A mock is a test double pre-programmed with expectations about the calls it should receive, and it can fail the test if those expectations are not met. Mocks verify interactions (behaviour verification) rather than final state, which makes them powerful but prone to coupling tests to implementation details. Over-mocking is a common smell that yields tests passing on broken code.
Mutation Testing
Mutation testing measures the quality of a test suite by introducing small faults ("mutants") into the code and checking whether tests catch them. A surviving mutant reveals a behaviour your tests do not actually verify, even if coverage is high. The mutation score (killed mutants over total) is a far stronger quality signal than line coverage, at the cost of longer run times.
Negative Test
A negative test verifies that the system handles invalid input, error conditions, and misuse gracefully - rejecting bad data, returning the right error, and not corrupting state. It complements the "happy path" positive test and is where many production defects actually live. Skipping negative cases is a frequent cause of fragile, security-relevant bugs.
Page Object Model (POM)
The Page Object Model is a UI-test design pattern that wraps each page or component behind an object exposing intention-revealing methods, hiding selectors and interaction details. It localises UI changes to one place, so a changed selector is fixed once rather than across every test. It is the standard way to keep Selenium and Playwright suites maintainable.
Property-Based Testing
Property-based testing asserts that a property holds for many automatically generated inputs, rather than checking a few hand-picked examples. The framework (such as Hypothesis, jqwik, or fast-check) generates cases and "shrinks" a failing one to a minimal counter-example. It is excellent for finding edge cases in pure functions, serializers, and invariants like "decode(encode(x)) == x".
Quality Assurance (QA)
Quality assurance is the set of process-oriented activities that build quality into how software is produced, as opposed to quality control, which inspects the finished product for defects. QA spans requirements clarity, code review, testing strategy, and continuous improvement. Modern teams treat quality as a whole-team responsibility rather than a phase owned by a separate group.
Regression Test
A regression test guards against the reintroduction of a previously fixed defect or the breaking of existing behaviour by a new change. Every bug fix should add a test that fails before the fix and passes after, so the bug cannot silently return. The accumulating regression suite is run on every change to keep the system from sliding backward.
Smoke Test
A smoke test is a small, fast set of checks confirming that the most critical functionality of a build works at all, run first to decide whether deeper testing is worthwhile. The name comes from hardware testing - power it on and see if it smokes. In CI/CD it acts as a quick gate after deployment before fuller suites or traffic are admitted.
Stub
A stub is a test double that returns canned answers to calls made during the test, supplying the indirect inputs a unit needs without any real logic. Unlike a mock, a stub does not assert on how it was called; it only provides state. Stubs keep state-based tests simple and decoupled from collaborator behaviour.
System Under Test (SUT)
The system under test is the specific code being exercised by a given test - a single class for a unit test, a slice of services for an integration test, or the whole application for an E2E test. Naming the SUT precisely clarifies the test's scope and what its collaborators (real or doubled) are. A blurry SUT boundary is a frequent cause of tests that are slow, brittle, or hard to interpret.
Test Case
A test case is a single, repeatable scenario with defined preconditions, inputs, execution steps, and an expected result used to determine whether a feature behaves correctly. Good test cases are independent, deterministic, and traceable to a requirement or behaviour. A suite is a curated collection of test cases organised by feature, risk, or layer.
Test Double
A test double is any object that stands in for a real collaborator during a test; the umbrella term covers dummies, stubs, fakes, spies, and mocks. Choosing the right double matters: stubs and fakes support state verification, while mocks and spies support interaction verification. The taxonomy was popularised by Gerard Meszaros in xUnit Test Patterns.
Test Pyramid
The test pyramid is a heuristic for the ideal shape of a test suite: many fast unit tests at the base, fewer integration tests in the middle, and very few slow end-to-end tests at the top. It optimises for fast, reliable feedback by pushing testing down to the cheapest layer that can catch a given defect. Inverting it (an "ice-cream cone" of mostly E2E tests) produces slow, flaky suites.
Test-Driven Development (TDD)
Test-Driven Development is a workflow of writing a failing test first (red), writing the minimal code to pass it (green), then refactoring with the test as a safety net (refactor). Driving design from tests tends to produce small, decoupled, testable units and a regression suite as a by-product. The discipline is about design and feedback, not merely about having tests.
Test Fixture
A test fixture is the prepared environment - objects, data, and configuration - in a known state that one or more tests run against. Shared fixtures speed up suites but risk hidden coupling and order dependence; per-test fixtures are isolated but slower. The trade-off between sharing and isolation is a core test-design decision.
Test Oracle
A test oracle is the mechanism that decides whether the observed output of a test is correct. It can be an exact expected value, a property or invariant, a comparison against a reference implementation, or a human judgement. The "oracle problem" - knowing the right answer for complex or non-deterministic systems - is a fundamental challenge in testing, including AI systems.
Test Plan
A test plan is a document describing the scope, approach, resources, schedule, and risk-based priorities for testing an effort. It records what will and will not be tested, the environments and data needed, and the entry/exit criteria for each phase. Lightweight, living test plans suit agile teams better than heavyweight upfront documents.
Test Suite
A test suite is a named collection of test cases grouped for execution - by feature, by layer (unit/integration/E2E), or by run cadence (smoke, nightly, full). Organising suites lets a pipeline run fast checks on every commit and slower, broader checks less often. A well-structured suite gives developers feedback at the right speed for each stage.
Acceptance Criteria
Acceptance criteria are the predefined conditions a feature must satisfy to be considered done and accepted by stakeholders. Written before or during development, they make "done" objective and become the basis for acceptance tests. Clear, testable criteria reduce rework caused by ambiguous requirements.
Spy
A spy is a test double that records information about how it was called (arguments, call count, order) so the test can make assertions about those interactions afterward. It sits between a stub (provides answers) and a mock (verifies expectations set in advance): a spy captures first and verifies later. Spies are useful when you want loose interaction verification without scripting strict expectations upfront.
Stress Test
A stress test deliberately pushes a system beyond its expected capacity to find the breaking point and observe how it degrades and recovers. Unlike a load test, which validates behaviour at a target volume, a stress test seeks the failure mode - resource exhaustion, cascading timeouts, or data corruption. The goal is graceful degradation and a clean recovery, not survival of impossible load.
Soak Test
A soak (endurance) test runs a system under a sustained, moderate load for an extended period to reveal problems that only appear over time, such as memory leaks, connection-pool exhaustion, and slow resource creep. It answers "does the system stay healthy for hours or days?", which short load tests cannot. It is essential before promising a high uptime SLO.
Continuous Integration (CI)
Continuous Integration is the practice of merging developers' work into a shared mainline frequently, with each integration verified by an automated build and test run. Fast, reliable CI catches integration defects early and keeps the mainline releasable. The testing strategy must keep CI feedback fast, which is the practical motivation for the test pyramid.
Shift-Left Testing
Shift-left testing moves quality activities earlier in the delivery lifecycle - writing tests with code, reviewing requirements for testability, and running checks on every commit - rather than testing only at the end. Because defects get exponentially more expensive the later they are found, catching them early lowers cost and lead time. It reframes testing from a gate into a continuous, built-in activity.
Code Review
Code review is the practice of having one or more engineers examine a change before it merges, focusing on correctness, security, design, and test adequacy. It is a quality-assurance activity that catches defects and spreads knowledge that automated tests cannot. Effective reviews are timely, small in scope, and check that the tests actually assert meaningful behaviour.
Defect Density
Defect density is the number of confirmed defects divided by a size measure of the component, such as defects per thousand lines of code or per function point. It helps compare the relative quality of modules and identify hotspots that warrant more testing or refactoring. Like all such metrics it is a signal to investigate, not a verdict on its own.
Happy Path
The happy path is the default scenario in which everything goes as expected, with valid input and no errors. Testing only the happy path is a common gap, because most production defects and security issues live in error handling, edge cases, and unexpected input. A balanced suite pairs every happy-path test with negative and boundary cases.
Test Isolation
Test isolation means each test runs independently of the others, with no shared mutable state or ordering assumptions, so it can run alone, in any order, and in parallel with the same result. Isolation is what makes a suite trustworthy and parallelisable; its absence is a leading cause of flaky, order-dependent failures. Achieve it by resetting or recreating fixtures per test and avoiding global state.
Regression Suite
A regression suite is the accumulated body of automated tests run on every change to confirm that existing behaviour still works. It grows as bug fixes and features each contribute their own guarding tests. Its value depends on staying fast and reliable; flaky or slow tests get ignored or disabled, which silently removes the protection.