QA & Testing · Guide

Unit & Integration Tests

The fast layer: frameworks, test doubles that do not lie, and fixtures that keep a suite deterministic.

— min read QA & Testing

A Suite People Trust

The value of an automated suite is entirely in whether a red build means something. A suite that fails randomly is worse than no suite: it trains the team to click rerun, and the one real failure goes through with the rest.

Three properties make the difference. Tests must be deterministic — same input, same result, every run. Independent — any order, any subset, no shared state. And fast enough that people run them before pushing rather than after.

Test Frameworks

Every ecosystem has settled on the same shape: a runner that finds tests, assertions that state expectations, and hooks that set up and tear down. The names differ; the structure does not.

EcosystemCommon choice
Pythonpytest
JavaScriptJest or Vitest
JavaJUnit 5
C#xUnit
Gothe standard library testing package
def test_expired_token_is_rejected():
    # arrange
    token = make_token(expires_in=-60)

    # act
    result = auth.verify(token)

    # assert
    assert result.ok is False
    assert result.reason == "expired"

Arrange, act, assert is worth following literally. A test with the three phases visible is readable at a glance, and a test that needs two "act" steps is usually two tests.

Name tests after the behaviour, not the function: test_expired_token_is_rejected tells you what broke from the CI output alone. test_verify_2 means opening the file.

Mocking & Test Doubles

A test double stands in for a real collaborator so a test stays fast and deterministic. They are not interchangeable, and using the wrong one is how suites end up asserting their own implementation.

DoubleIs
StubReturns canned answers
FakeA working lightweight implementation — an in-memory store
MockAsserts that it was called in a particular way
SpyRecords calls for you to check afterwards
Over-mocking produces tests that pass while the system is broken. If every collaborator is a mock, the test proves your code calls the methods you told it to call — which it will keep proving after those methods change meaning.

The useful rule: mock what you do not control and what is slow — third-party APIs, the clock, randomness, the network. Use real objects for your own code wherever it is fast enough, and prefer a fake over a mock when you need behaviour rather than a call assertion.

Fixtures & Test Data

Test data decides how much of a suite is readable and how much of it is flaky. The failure mode is always the same: tests that share state and pass alone but fail together.

PracticeWhy
Build data per testShared fixtures couple tests to each other
Factories over literalsSay what matters, default the rest
Reset between testsTruncate or roll back — never rely on ordering
Freeze the clockA test that fails at midnight or in February is a real bug you cannot reproduce
Seed randomnessReproducibility beats variety in a CI run
# a factory says only what the test cares about
user = make_user(plan="pro")          # everything else defaulted

# freeze time rather than computing against "now"
with freeze_time("2026-01-01T12:00:00Z"):
    assert subscription.is_active(user) is True
If a test only passes when run after another, it is not a test — it is a step in a script. Run the suite in random order occasionally; whatever breaks was sharing state.

Interview Questions

What makes an automated suite trustworthy?

Determinism, independence and speed. A suite that fails randomly trains everyone to rerun it, and the one real failure ships along with the noise.

Stub, fake, mock, spy — what is the difference?

A stub returns canned answers, a fake is a working lightweight implementation, a mock asserts how it was called, and a spy records calls for later inspection.

What is the risk of over-mocking?

The test ends up asserting your own implementation. Everything passes while the system is broken, because nothing real was exercised.

What should you mock?

What you do not control or what is slow — third-party APIs, the network, the clock, randomness. Use real objects for your own code while it is fast enough.

Why freeze the clock in tests?

Otherwise behaviour depends on when the suite runs — tests that fail at midnight, at month end, or in a different timezone, and cannot be reproduced on demand.

A test passes alone but fails in the suite. Why?

Shared state — a database row, a module-level cache, an environment variable. Tests must build their own data and reset after themselves.

Quick Quiz

1. A fake differs from a mock in that it…
2. Over-mocking leads to tests that…
3. Arrange-act-assert describes…
4. Tests that fail only when run together usually share…
5. Freezing the clock in tests prevents…