Skip to main content

FAQ

What is the difference between a unit test and an integration test?

A unit test isolates one small piece of code, such as a class or function, and verifies its behaviour without touching external systems, using fakes or mocks for collaborators. An integration test exercises several components together, often with real infrastructure such as a database or message broker, to confirm they collaborate correctly. Unit tests are fast and numerous and form the base of the test pyramid; integration tests are fewer and slower but catch wiring, mapping, SQL, and serialization defects that mocked unit tests cannot.

What is the test pyramid and why does it matter?

The test pyramid is a heuristic for the shape of a healthy 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 matters because it pushes each defect down to the cheapest layer that can catch it, keeping feedback fast and the suite reliable. The anti-pattern is the "ice-cream cone" - mostly end-to-end tests - which produces slow, flaky suites that teams eventually stop trusting and stop running.

How much code coverage should I aim for?

There is no universally correct number; coverage is a proxy for thoroughness, not a measure of test quality. A figure around 80% is a common, pragmatic floor that flags large untested areas, but 100% coverage can still miss behaviours, edge cases, and error paths if the tests do not assert meaningful outcomes. Use coverage to find untested code worth testing, and pair it with review (and ideally mutation testing) that checks the tests actually verify behaviour rather than just executing lines.

What is the difference between a mock, a stub, and a fake?

All three are test doubles that stand in for real collaborators, but they serve different goals. A stub returns canned answers to provide indirect inputs and does not assert on calls; a fake has a working but simplified implementation, such as an in-memory database; a mock is pre-programmed with expectations and can fail the test if the expected interactions do not occur. Use stubs and fakes for state-based testing and mocks sparingly for interaction verification, because over-mocking couples tests to implementation details and lets tests pass on broken code.

How do I fix a flaky test?

First make the failure reproducible by identifying the source of non-determinism: timing and race conditions, reliance on the wall clock, shared mutable state, test-order dependence, or unstable external dependencies. Then remove the cause - inject a fixed clock instead of reading the current time, wait on an observable condition rather than sleeping a fixed duration, isolate or reset state per test, and stub external services. Avoid the temptation to mask flakiness with blind retries, which hides real intermittent bugs and slowly erodes trust in the entire suite.

When should I write an end-to-end test versus a unit test?

Reserve end-to-end tests for a small number of critical user journeys where the confidence of exercising the fully integrated system justifies their slowness and brittleness, such as login, checkout, or core data flows. Push everything else down the pyramid: test business rules and edge cases as unit tests, and test wiring, persistence, and contracts as integration tests. A defect that a unit or integration test can catch should be caught there, because those tests are faster, more stable, and pinpoint the failure more precisely.

What is test-driven development and what are its benefits?

Test-driven development is a cycle of writing a failing test first (red), writing the minimal code to make it pass (green), and then refactoring with the test as a safety net (refactor). Its main benefit is design: writing the test first forces you to define the unit's interface and behaviour from the caller's perspective, which tends to produce small, decoupled, testable code. As a by-product you accumulate a fast regression suite and gain immediate feedback, though the discipline takes practice and is most valuable where behaviour is well understood.

How should I test code that depends on the current date or time?

Never read the system clock directly inside the code under test, because that makes behaviour non-deterministic and time-dependent tests flaky. Instead inject an abstraction for time - such as a Clock in Java, a clock interface, or a function returning "now" - so a test can supply a fixed instant. This lets you assert behaviour at exact moments (expiry boundaries, scheduling windows, time-zone transitions) deterministically and run the same test on any day without surprises.

What is the difference between QA and testing?

Testing is the activity of executing software to find defects and verify behaviour; quality assurance is the broader, process-oriented discipline of building quality into how software is produced. QA spans requirements clarity, code review, test strategy, environment management, and continuous improvement, of which testing is one important part. Reframing quality as a whole-team responsibility - rather than a phase owned by a separate group at the end - is a defining feature of modern, agile-and-DevOps-style delivery.

How do I test a system that calls external APIs?

For fast, deterministic unit tests, replace the external call with a stub or a fake that returns controlled responses, including error and timeout cases. For integration tests, prefer a realistic substitute over the live third party: a contract test (for example with Pact) to verify the agreed request/response shape, or a local mock server such as WireMock or MSW to simulate the API including failures. Reserve hitting the real service for a small number of monitored end-to-end or smoke checks, because live dependencies make suites slow, flaky, and dependent on external availability.

What is mutation testing and is it worth the cost?

Mutation testing evaluates how good your tests are by introducing small faults ("mutants") into the production code and checking whether the tests catch them; a surviving mutant marks a behaviour your tests do not truly verify. It is a far stronger quality signal than line coverage, which only proves code ran, not that its behaviour was asserted. The trade-off is run time, so it is usually worth applying to critical or complex modules rather than the entire codebase on every commit.

How do I make my test suite run faster?

Start by getting the suite shape right: most defects should be caught by fast, isolated unit tests, with integration and end-to-end tests kept proportionally few. Make tests independent so they can run in parallel, avoid unnecessary I/O and sleeps, share expensive setup safely (for example reusing a single containerised database across a class), and split slow suites so quick checks run on every commit and slower ones less often. Profile the suite to find the slowest tests, because a small number of them usually dominate the total run time.

What is the difference between load testing and stress testing?

A load test measures how the system behaves under an expected or peak level of concurrent demand and asserts that latency, throughput, and error rate meet their targets - answering "is it fast and reliable enough at this volume?". A stress test deliberately pushes beyond expected capacity to find the breaking point and observe how the system degrades and recovers. Use load tests to validate performance against SLOs and stress tests to understand failure modes and confirm graceful degradation.

Should I delete tests that are slow or annoying?

Do not delete a test that protects real behaviour just because it is inconvenient; instead diagnose why it is slow or noisy and fix the underlying problem. If a test is flaky, make it deterministic; if it is slow, push the assertion down to a faster layer or optimise its setup; if it is redundant with stronger tests, you can remove it deliberately and note why. Silently disabling or deleting tests removes safety without anyone deciding the risk is acceptable, which is how regressions slip back in.

How do I evaluate the quality of an AI or LLM feature?

Treat it as a measurement problem: build an evaluation set of representative inputs with reference answers or properties, and score outputs with automated metrics plus targeted human review. For retrieval-augmented generation, frameworks such as RAGAS, promptfoo, and DeepEval measure faithfulness (is the answer grounded in the retrieved context?), answer relevancy, and context recall/precision. Gate releases on these metrics in CI so a prompt, model, or retrieval change cannot silently regress quality, and keep the golden eval set fixed so you are measuring the system, not editing the target.

What are acceptance criteria and how do they relate to acceptance tests?

Acceptance criteria are the predefined, testable conditions a feature must satisfy to be accepted by stakeholders, ideally written before or during development so "done" is objective. Acceptance tests are the executable checks that verify those criteria from the user's point of view, often phrased as Given/When/Then scenarios. Agreeing on the criteria up front - the heart of acceptance test-driven development and specification by example - reduces rework caused by ambiguous requirements and gives the team a shared, verifiable target.

What makes a good bug report?

A good bug report is reproducible and specific: it states the exact steps to trigger the problem, the expected result, the actual result, and the environment (version, OS, browser, data). It includes the minimal case that demonstrates the issue, relevant logs or screenshots, and the severity and impact so the team can prioritise. The single most valuable element is a reliable reproduction, because a defect that cannot be reproduced usually cannot be fixed with confidence - and ideally it is captured as a failing regression test.