1. Describe the test pyramid: what are the four layers, and what is the intended proportion of tests at each layer?
2. What is the difference between a unit test and an integration test? What kind of bug can an integration test catch that a unit test cannot?
3. What are consumer-driven contract tests, and which failure mode do they exist to prevent before deployment?
Classify each of the following tests into the correct pyramid
layer and justify: (a) a test that creates a Money object
and verifies that add(Money(10), Money(5)) returns
Money(15); (b) a test that calls
GET /api/orders/123 against a running service and checks
the HTTP status is 200; (c) a test that inserts a row into a test
database through a UserRepository and queries it back; (d)
a test that opens a browser, logs in, adds a product to a cart, and
checks out.
A team’s CI pipeline takes 38 minutes. Analyse this breakdown: unit tests 200, runtime 2 minutes; integration tests 150, runtime 18 minutes; end-to-end tests 80, runtime 18 minutes. Propose three changes to bring the total under 10 minutes without removing coverage. Justify each change using the pyramid.
An orders service changes its GET /orders/:id
response to rename total to totalAmount. There
are no contract tests. List all the downstream systems that might break,
describe when each would discover the breakage, and calculate the total
time-to-detection if deployment to production takes 2 hours.
A complete answer will: (1) define behaviour coverage per layer:
unit tests cover validation logic (invalid card numbers, missing fields,
amount limits) using no test doubles — these are pure functions;
integration tests cover the database interaction (PostgreSQL writes and
reads) using a real test database, not a mock; integration tests for the
card network use a stub server that returns configurable responses;
integration tests for Kafka use an embedded Kafka or a real Kafka
container, (2) specify which test doubles are used where: a stub for the
card network (controls response to test decline, timeout, and approval
scenarios without calling a real network); a spy or recording mock for
Kafka (verifies that the correct event payload is published after a
successful charge); no mocks for PostgreSQL (a real database gives
higher confidence and avoids mock-object drift), (3) design contract
tests using Pact: the payment service is the provider; its consumers
(order service, notification service) define consumer-driven contracts
specifying which fields they read from the PaymentProcessed
event schema — the payment service’s CI runs Pact verification to
confirm the published event matches all consumer contracts before
deployment, and (4) scope end-to-end tests narrowly: cover only the two
highest-risk happy-path scenarios (successful payment and declined
payment) using a real staging environment — estimate test counts: ~50
unit tests (fast, < 1s total), ~20 integration tests (2–5 minutes), 2
end-to-end tests (5–10 minutes); total CI runtime < 15
minutes.
A complete answer will: (1) make the argument for Pact: in a six-service system, API contract breaks are the primary source of integration failures — a provider team that changes an event schema without notifying consumers causes silent runtime failures (FM8 silent semantic drift); Pact makes contracts explicit and catches breaks in CI before deployment, eliminating a class of production incidents, (2) make the argument against: Pact requires ongoing maintenance — every consumer must update its contract when it legitimately adds a new field; every provider must run Pact verification in its CI pipeline; for a team of 6 engineers (one per service), the overhead of maintaining 6 × 5 = 30 potential consumer-provider contracts may exceed the value of the protection, (3) state the break-even conditions: Pact pays off when services are owned by separate teams (different release cycles, no shared planning), the API surface is large (many fields that consumers depend on), and integration incidents are frequent enough to justify the maintenance burden — below 3 services or for a monorepo with coordinated releases, the break-even rarely occurs, and (4) propose an alternative: schema validation using a shared schema registry (e.g., Apache Avro with a Confluent Schema Registry, or JSON Schema validation on both publish and consume sides) protects against schema breaks with less process overhead than Pact — it does not require consumer teams to define explicit contracts, but it does require both sides to validate against a common schema and it catches incompatible schema changes automatically.