Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing in Software Engineering

Table of Contents


Why Testing Matters

Testing is the engineering practice of verifying that software behaves as intended and meets its requirements. It is not a phase tacked on at the end of a project — it is a continuous activity that runs alongside development.

Testing matters because it:

  • Catches regressions — a change in one module must not silently break another.
  • Documents behavior — tests act as an executable specification of the system.
  • Enables refactoring — you can restructure code confidently when tests guard its behavior.
  • Reduces cost — a bug found in development is orders of magnitude cheaper to fix than one found in production.
  • Builds confidence — teams ship faster when they trust their test suite.

“Testing shows the presence, not the absence of bugs.” — Edsger Dijkstra

Testing Levels

LevelScopeSpeedTypical volumeExample
UnitSingle function/class in isolationms70–80%calculateDiscount(100, 10) == 90
IntegrationMultiple components togetherseconds15–25%Order service writes to and reads from a DB
SystemThe whole system against requirementsminutes5–10%Full workflow across all subsystems
End-to-End (E2E)Full user journey through the UI/APIminutes5–10%Signup → browse → checkout

The Testing Pyramid

        /  \
       / E2E \         ← few, slow, expensive
      /-------\
     / Integr. \       ← moderate
    /-----------\
   /   Unit      \     ← many, fast, cheap
  /_______________\

The pyramid (Mike Cohn) says: write many fast unit tests and few slow E2E tests. The Testing Trophy (Kent C. Dodds) shifts the emphasis to integration tests as the best confidence-to-effort trade-off, with static analysis as the base.

Testing Types

TypePurpose
Functional testingVerify the system does what requirements say
Non-functional testingVerify performance, security, usability, reliability
Smoke testingQuick check that critical paths work before deeper testing
Sanity testingVerify a specific fix or change works as expected
Regression testingEnsure previously working features still work
Load testingBehavior under expected load
Stress testingBehavior beyond expected load until failure
Soak testingBehavior under sustained load over time
Property-based testingVerify invariants hold for many generated inputs (Hypothesis, QuickCheck)
Fuzz testingFeed random/malformed input to find crashes and leaks
Mutation testingIntroduce deliberate bugs to check whether tests catch them
Contract testingVerify the agreed API contract between services (Pact)
Snapshot testingCompare rendered output against a stored snapshot

Test-Driven Development (TDD)

TDD is a discipline where tests are written before production code, in a short cycle:

RED → GREEN → REFACTOR
  1. Red — write a failing test that expresses the desired behavior.
  2. Green — write the minimum code to make the test pass.
  3. Refactor — clean up the code while tests stay green.
# RED: the test defines the API we want
def test_stack_pop_returns_last_pushed():
    s = Stack()
    s.push(1)
    s.push(2)
    assert s.pop() == 2          # fails: Stack doesn't exist yet

# GREEN: minimal implementation
class Stack:
    def __init__(self): self.items = []
    def push(self, x): self.items.append(x)
    def pop(self): return self.items.pop()

# REFACTOR: improve without changing behavior

Benefits and Costs

Benefits: forces a clear API, drives design, guarantees every behavior is tested, gives a fast feedback loop, and results in a regression suite you trust.

Costs: slower in the very short term, requires discipline, can tempt over-testing trivial code, and is hard to apply to exploratory/UI-heavy work.

Behavior-Driven Development (BDD)

BDD extends TDD by expressing scenarios in business-readable language so that developers, testers, and non-technical stakeholders share a common vocabulary.

The classic format is Given–When–Then (Gherkin):

Feature: Withdraw money
  Scenario: Withdraw within balance
    Given an account with balance 100
    When I withdraw 30
    Then the balance should be 70

Tools: Cucumber (Ruby/Java), SpecFlow (.NET), Behave (Python), Gauge.

The value of BDD is not the tool — it is the shared understanding of what success looks like before implementation starts.

Test Strategy

A test strategy describes what will be tested, how, and to what depth, given the project’s risk profile and constraints.

Key elements:

  • Scope — which components and behaviors are in/out of scope.
  • Levels — the mix of unit, integration, and E2E tests.
  • Environment — local, CI, staging, production canaries.
  • Data — test data strategy, fixtures, anonymization.
  • Entry/exit criteria — when testing starts and when it is “done”.
  • Risk-based prioritization — test the riskiest paths most heavily.

Test Isolation

Tests must be independent: each test sets up its own state and cleans up after itself. Shared state between tests is the leading cause of flaky, order-dependent suites.

Test Doubles

DoubleWhat it does
DummyPassed around but never used (satisfies parameters)
StubReturns canned answers to calls made during the test
FakeWorking implementation with a shortcut (in-memory DB)
SpyRecords calls so you can assert on them
MockPre-programmed expectations; fails if calls differ

Use doubles to isolate the unit under test from slow or non-deterministic dependencies (databases, networks, clocks). Over-mocking leads to brittle tests that verify implementation instead of behavior.

Common Mistakes

MistakeConsequence
Testing implementation, not behaviorTests break on every refactor
One giant test for 20 behaviorsHard to locate failures
Shared state between testsFlaky, order-dependent results
No assertions (smoke-only “tests”)False confidence
Ignoring flaky testsTeam stops trusting the suite
Chasing 100% coverageLow-value tests on trivial code

Interview Questions

Beginner

  • What is the difference between unit, integration, and end-to-end testing?
  • What is a regression test, and why is it important?
  • What does the testing pyramid suggest about test distribution?

Intermediate

  • Explain the TDD cycle and one benefit and one drawback.
  • What is the difference between a mock, a stub, and a fake?
  • What is BDD, and how does Given–When–Then help a team?

Advanced

  • What is property-based testing, and when is it more valuable than example-based tests?
  • What is mutation testing, and what problem does it solve?
  • How would you design a test strategy for a payment-processing service?

Common Traps

  • “It has 100% coverage, so it’s well tested” — coverage measures execution, not assertion quality.
  • Confusing smoke and sanity testing.
  • Over-mocking to the point that tests only verify the mocks themselves.

References


For a deeper, dedicated treatment (including mocking, contract testing, and CI integration), see the Testing section of this book.