skip to content

Equivalence Partitioning

Splitting the input domain into classes the code should treat identically, then testing one representative from each valid and invalid class. Asked because it is the reasoning behind why five tests can cover more than fifty arbitrary ones.

on this pageshow

questions

4

What is equivalence partitioning, and how do you pick the value you test from each class?

level: juniorimportance: must knowfreq 78%

answer

  1. One test can stand for many values
  2. Blocks of behaviour, not individual values
  3. Valid and invalid classes both count
  4. Pick an ordinary interior member
  5. Uniformity is a hypothesis, not a fact

basics

~20 s

Equivalence partitioning splits the input domain into classes the software is specified to handle identically, then tests one representative from each valid and each invalid class. If a class really is uniform, one value speaks for all of it.

solid answer

~50 s

Equivalence partitioning is a specification-based design technique. You read what the software is supposed to do, work out which ranges or categories of input should receive the same handling, and treat each of those as one class. You then design one test per class, covering the classes that should be accepted and the classes that should be rejected — each distinct rejection reason is its own class, so *requested too soon* and *beyond the booking horizon* do not share a case. Because every member of a class is meant to be interchangeable, the representative should be an ordinary interior value rather than something exotic. The payoff is a small suite that still exercises every distinct behaviour the specification names. The catch is that a class is a **hypothesis** about the software; if the class is not actually uniform, one value from it can pass while a defect hides in the rest.

code

pseudocode · 13 lines
pseudocode
# Field: leadDays on a booking request
# Spec: 1..90 accepted | < 1 -> "too soon" | > 90 -> "beyond horizon" | not a whole number -> "malformed"

partitions = [
  { class: "whole number 1..90", kind: valid,   expect: "booking created",  representative: 37 },
  { class: "whole number < 1",   kind: invalid, expect: "too soon",         representative: -4 },
  { class: "whole number > 90",  kind: invalid, expect: "beyond horizon",   representative: 214 },
  { class: "not a whole number", kind: invalid, expect: "malformed",        representative: "soon" },
]

for p in partitions:
    result = book(leadDays = p.representative)
    assert result.outcome == p.expect

go deeper

for a junior

Be ready to define the technique in one sentence and split a simple field into classes on a whiteboard, then say plainly why one value per class is enough and where the other values went.

for a middle

Explain where classes come from — the specification, not the implementation — and show each distinct rejection reason becoming its own class. Be able to defend choosing an ordinary interior value as the representative.

for a senior

Show that you treat uniformity as a hypothesis. Talk about reviewing a class list against real traffic and defect history, and about deciding when a broad class has earned more than one value.

for a principal

Own the question of how much formal partitioning a team should invest in, who keeps the class list honest when the specification moves, and how partition tables stay a review artefact instead of decaying into stale documentation.

## Why the technique exists A single input field usually holds more values than anyone can enumerate. A hospital appointment scheduler that accepts a lead time in days already admits every integer a client can encode, plus everything that is not an integer at all. Exhaustive testing is impossible, and picking values at random gives you no argument that you covered anything. Equivalence partitioning is the argument. It says: the specification does not describe behaviour value by value, it describes behaviour **in blocks**, and the blocks are the real unit of test design. ## The definition An equivalence class (or partition) is a set of inputs that the software is specified to process in the same way. The technique has three moves: 1. **Partition the domain.** Split the whole input domain — including malformed and missing input — into classes, so that every possible value belongs to exactly one class and the classes together cover everything. 2. **Classify each class as valid or invalid.** A valid class is one the software should accept and act on; an invalid class is one it should reject. A rejection is a behaviour, not an absence of behaviour, so it deserves classes of its own. 3. **Take one representative per class.** Any member is supposed to be as good as any other, so pick an unremarkable one from the interior of the class. The underlying claim is a coverage claim: if the classes are correct, then a suite with one value per class has exercised every distinct behaviour the specification defines for that input, and adding a second value from the same class adds no information. ## Working it through Take the scheduler's booking endpoint. The specification says: a lead time of 1 to 90 days is accepted; a lead time below 1 is rejected as *requested too soon*; above 90 it is rejected as *beyond the booking horizon*; anything that is not a whole number of days is rejected as *malformed*. That is four classes on one field — one valid, three invalid — and they are three separate invalid classes because the software owes three different answers, not one generic refusal. A representative for the valid class might be 37 days. Nothing about 37 is special, which is exactly the point: it sits in the middle of the class so the test asserts the class's normal behaviour rather than the behaviour of a special case. Choosing edge values deliberately is a different technique with its own reasoning, and mixing the two by reflex costs you the ordinary case entirely. Partitions are not only numeric. An appointment type drawn from a fixed list of clinic categories partitions by category, and if the specification gives one category distinct handling — say a category that requires a referral — that category is its own class rather than a member of a general *any valid category* class. A free-text reason field partitions by whether it is present, empty or absent. A file upload partitions by accepted format versus rejected format. The question is never *what shape is the data*, it is always *where does the specified behaviour change*. ## What it buys and what it costs The payoff is leverage. Twelve fields with three or four classes each yield a test set in the dozens rather than the millions, and each case is justified by a sentence from the specification, which makes the suite reviewable: a reader can check the class list against the spec without reading a single assertion. The cost is that the class list is an assumption. Two things break it. The first is a specification that is silent or wrong, in which case your classes describe an intention nobody implemented. The second is an implementation that splits a class you believed whole — a cache, a retry path, a size threshold, a locale-dependent branch — so one member of the class behaves differently from another. Neither is a reason to abandon the technique; both are a reason to keep the class list under review and to treat every escaped defect as a report that a class needs splitting. ## Doing it well Write the classes down before the cases, as a short table of *class, valid or invalid, expected behaviour, representative*. Check the table for completeness (does every conceivable value land somewhere, including absent and malformed?) and for disjointness (does any value land in two classes, which means the classes are ambiguous and one of them is misdescribed?). Assert the specific expected behaviour, not merely that something happened — a rejection test that only checks *it was refused* passes for the wrong reason as soon as two rejection rules are confused. And keep the representative boring, so that when it fails you know the class is broken rather than wondering whether you picked a value the software was always going to trip over.

  • How do you know your set of equivalence classes is complete?
    Two checks. Completeness: every conceivable value in the domain lands in exactly one class, including absent, empty and malformed input — if you cannot say which class a value belongs to, a class is missing. Disjointness: no value belongs to two classes, because that means one of them is described ambiguously and the two will disagree about the expected result. Running the table past the specification, rather than past the code, is the review that catches both.
  • How does the technique change when the input is a category list rather than a numeric range?
    It does not change, only the way you read the specification does. You group categories by the handling they receive: categories that are treated identically form one class, and any category the specification singles out — one that demands a referral, say — becomes its own class. You still add at least one class for a category value the system does not recognise at all, since rejecting an unknown category is specified behaviour that deserves a test.
  • If a class is genuinely uniform, why do some teams still run more than one value from it?
    Because uniformity is an assumption about the implementation, and cheap tests are a hedge against that assumption being wrong. It is a defensible choice when a class is broad, is fed by untrusted data, or has a defect history. It is not a substitute for splitting a class you have evidence is not uniform: if you know two members behave differently, they belong to two classes with two expected results, not to one class tested twice.

Sorting mail into sacks by destination country: once a sack is bound for one country, checking the postage rule on any single letter in it tells you about the whole sack — as long as the sacks really were sorted correctly.

saying these in an interview costs you the question

  • Says partitions cover only accepted input, never rejected input
  • Lumps every rejection reason into one invalid class
  • Reaches for an edge value as the default representative
  • Claims a passing test proves the class is uniform
  • Derives classes from the code branches and calls it black-box
  • Confuses the technique with running the same test on random values

context

open as a page

Why should each invalid equivalence class get its own test case rather than one combined case?

level: middleimportance: should knowfreq 58%

basics

~20 s

Because a rejected input usually stops at the first failing check, so a second invalid value in the same case is never reached. Isolating one invalid class per case keeps every rejection path exercised and every failure diagnostic.

open as a page

How do you detect that an equivalence class you assumed was uniform actually hides sub-partitions?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Treat uniformity as a hypothesis and look for evidence against it: unexecuted branches under a one-value-per-class suite, outcomes that vary with retries, caching, size, locale or load, and every escaped defect, which is a report that some class holds two behaviours.

open as a page

How do you apply equivalence partitioning to the output domain rather than to input values?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Partition the set of distinct results the software can produce, then work backwards to find an input that lands in each one. It covers outcomes that many different inputs share, and outcomes the input fields do not obviously name.

open as a page