Equivalence partitioning is a test design technique that groups a system's possible inputs into equivalence classes, sets of inputs the system is supposed to treat identically, then tests one representative value from each class instead of every value in it. Every class is either a valid partition, inputs the system should accept, or an invalid partition, inputs it should reject for a specific, identifiable reason. The technique's real value isn't the sampling. It's the claim buried inside each class boundary.
What follows tests that claim against a real validation function: six branches, one partition per branch, until a seventh partition turns up that no requirements document ever mentioned. It's built for whoever needs to name equivalence partitioning correctly rather than decide whether to use it, an interview question, a QA hire who needs "equivalence class" defined once and properly, an auditor who wants a paragraph they can quote back correctly. (A QA lead drafting a testing strategy, or a team asking whether an AI-generated test actually asserts anything real, will want a different page.)
Equivalence partitioning is one of seven techniques covered in our test design techniques guide; this page stays on just this one.
Worth saying where the angle comes from. Deriving partitions by reading a validator's branches, rather than by reading a document about them, is the same move Autonoma makes before it plans a single test case, so the worked example below is the by-hand version of something we run against real codebases. The technique stands on its own whether or not you ever automate it, and the definitions here are the standard ones.
How equivalence partitioning works, with the partitions enumerated
Start with the requirements document: a specification turned into a small number of classes. Say the feature is username registration, and the document states four rules: three to twenty characters, letters, digits, and underscores only, must start with a letter, and must not already belong to another account.
Unlike decision table testing, which encodes a full specification as rules rather than sampling representative values, equivalence partitioning samples first. Four rules produce five invalid partitions and one valid partition, six total: too short, too long, disallowed characters, invalid start, already taken, and valid. The claim underneath it: every member of one class is interchangeable for testing, so a two-character string is as informative as any other for the too-short partition.
The table below is the prescribed test set: one input per partition, six cases standing in for an unbounded input space.
| Partition | Valid or invalid | Example input |
|---|---|---|
| Too short | Invalid | ab |
| Too long | Invalid | 21-character string |
| Disallowed characters | Invalid | ab-cd |
| Invalid start | Invalid | 1alice |
| Already taken | Invalid | alice99 |
| Valid | Valid | bob_2024 |
Each dot is one sampled input standing in for an unbounded class of strings, and the count is six because the requirements document stated four rules.
Six cases instead of an unbounded input space, and the justification for stopping at six is entirely about cost: a person testing by hand can afford six cases, not every string of disallowed length. That justification is doing real work, and the next section takes it away.
A partition is a claim, not a shortcut
Every explanation of equivalence partitioning gets to the same place: pick one input per class, because testing every member of a class already declared "behaves the same" is wasted effort. That's true, and it's not the interesting part. The interesting part is the sentence that had to be true before the shortcut made sense: these inputs are supposed to behave the same. That isn't an efficiency observation, it's a claim about what the specification requires. It says the valid partition, three to twenty characters, starting with a letter, made of letters, digits and underscores, not already registered, is one behavior, not a pile of individually verified accidents. It says invalid start and disallowed characters are two different failures, not one blurry "bad input" bucket, because the specification cares about the difference: one message tells a user to start with a letter, the other tells them which characters are off limits.
The textbook only ever cashes that claim one way: run one member of each class, skip the rest, because a person at a keyboard could only run so many cases a day. That was a sampling economy that made sense while the alternative was exhausting a tester's afternoon. It stops making sense once running every member of a class costs about the same as running one; the class boundary was never saving time, it was the only informative part of the exercise. This technique picked a few test cases when running them was expensive. Running them is no longer expensive. What survives is the part that says what the answer should be.
"No longer expensive" is doing enough work in that sentence to deserve a specific answer about where the cost went. For a pure function like the validator below, it went to a unit test runner years ago, and it has been cheap to run a hundred usernames through one for as long as most readers have been working. For the registration form a real person actually types into, the part where a partition either does or doesn't reach the user, it went somewhere newer: Autonoma plans cases against the running application in a live preview environment and reports back which ones are genuine failures, so exercising a whole class rather than one representative of it is a scheduling question now instead of a staffing one. The selection rule got cheap. The claim about which inputs are supposed to behave alike did not.
If you're settling a definition for an interview or a study guide, that reframe beats the sampling explanation. It's also the more defensible answer for an auditor: not "so we don't have to test everything," but "so we've stated, in writing, which differences between inputs are supposed to be irrelevant." Grey box testing makes the same move at the level of an entire testing posture; the validator above makes it concrete at the level of one function's branches.
How to identify equivalence classes from the code
The section above enumerated partitions the way most teams do it: read four rules in the requirements document, derive six partitions from four sentences. That's the correct exercise, and it's incomplete in a way careful reading can't fix. The document doesn't contain the whole specification. The code does.
Here's the validator behind the partition table above. Six branches, checked in a fixed order, each one a partition waiting to be read directly off the code:
Read the branches in order and a seventh partition falls out that no sentence in the document produced: a check against a short reserved list, admin, root, support, help, api, rejecting a username that is structurally valid, unclaimed, and would pass every rule the document stated. Reserved names exist for reasons unrelated to those four rules: routing needs certain words to resolve to a human, not the fifth person to register them, and impersonation risk means nobody should be able to claim "admin" and have it look official. A tester working only from the document would never write a case for it. A tester, or an agent, reading the function finds it in the time it takes to read one more condition.
That single step is the whole of what Autonoma does differently here, and it's worth separating from anything cleverer sounding. There's no inference about what the product manager meant and no guess at an unwritten rule; there's a branch sitting in the file returning reserved_word, and a partition follows from it the same way the other six do. The techniques on this page don't change when an agent applies them. What changes is which document the classes get derived from.
The seventh partition is a reserved-word check against admin, root, support, help, and api, and reading the validator's branches rather than the requirements document is what surfaced it.
| Branch in the source | Partition it derives | Reason returned |
|---|---|---|
| Length below 3 | Too short | too_short |
| Length above 20 | Too long | too_long |
| Character outside letters, digits, underscore | Disallowed characters | disallowed_characters |
| First character not a letter | Invalid start | invalid_start |
| Matches reserved list | Reserved word | reserved_word |
| Already in registered set | Already taken | already_taken |
Order matters here, worth stating plainly. An input violating two rules at once, a leading symbol like "$bob", reports disallowed characters and never reaches the invalid-start check; the earlier branch always wins. And the reserved-word check runs before the already-taken lookup on purpose: rejecting "admin" by name costs nothing, so there's no reason for a database round trip confirming what the name check already ruled out.
Each input stops at the first branch that matches it. "$bob" breaks two rules and only ever reports the earlier one; "admin" is the right length, well formed, and unclaimed, and only branch 5 rejects it.
That ordering is the part a partition table drawn from a requirements document cannot represent. The document says "must start with a letter" and "letters, digits, and underscores only" as two independent rules, and a table derived from it will happily list two separate partitions for an input that breaks both. The code decides which one an actual user sees.
Here's the partition test file that turns that table into something that runs: one case per partition, seven total, each asserting the exact reason string returned rather than a bare pass or fail, the distinction our guide to writing good test assertions argues matters more than the pass count:
Every count here agrees with what that file proves: six partitions from the document, seven once the reserved-word branch is read from the code, and every example above lands in exactly the partition it's assigned to, checked rather than assumed.
How Autonoma derives partitions from your code
Everything above, reading four sentences into six partitions, then reading six branches into seven, was done by hand for one function to make the mechanism visible. A real codebase has usernames, promo codes, shipping rules, and dozens of other validators shaped the same way, and no team re-derives partitions from every one of them by hand every time a branch changes. What tends to happen instead looks like the requirements-document version from the first section: someone writes a handful of cases from memory, and whatever that codebase's equivalent of the reserved-word check turns out to be gets covered by luck, or not at all.
That's the gap our Planner agent closes. It reads a codebase's validators, the length checks, character patterns, and lookups that decide whether an input is accepted, and derives from that reading exactly the kind of partition this article walked through by hand: a class off a length comparison, a class off a character pattern, a class off a lookup against a list that never made it into a requirements document. Verification then happens by running the actual function, or, for a full registration flow, by driving the running application in a live preview environment and checking the specific outcome each partition is supposed to produce, not a guess at what the document implies. When that validator's branches change on a later pull request, the same reading happens again against the new structure, rather than against a stale assumption about what the old one used to look like.
Map it onto the worked example directly. The document read gets six partitions and a plausible test set. The code read gets the seventh, the one a real user eventually finds by trying to register "admin" and wondering why a perfectly good, unclaimed name got rejected.
Equivalence partitioning vs boundary value analysis
Equivalence partitioning answers one question: which classes exist, and which single value from each is worth running. It doesn't say where the edges of the valid partition actually sit, only that the class from three to twenty characters is one behavior. Boundary value analysis is the sibling technique that tests exactly those edges: two characters, three characters, twenty characters, twenty-one characters, on the theory that an inequality operator (>= versus >) is one of the most common places a boundary gets implemented one integer off from what the specification intended.
The two techniques aren't competing descriptions of the same test set, and neither substitutes for the other. Partitioning says which classes exist and which reason each rejection carries. Boundary value analysis says where each class actually ends, the better place to look for the off-by-one error the length check above, three characters to twenty, hasn't actually been checked for. A username validator that only ran the six-case partition table above could still have its length comparison implemented as > instead of >=, silently rejecting a valid three-character name, and nothing in the seven-case test set above would have caught it. That's boundary value analysis's job, not partitioning's.
None of this required a bigger vocabulary than the one every testing textbook already teaches: valid partition, invalid partition, derived partition, equivalence class. Some teams call the whole exercise equivalence class testing, the same technique under a second name. What changed is which sentence in that vocabulary was ever the point. "These inputs behave the same" was always a claim about the specification, sitting underneath a sampling trick that made sense for exactly as long as running a test case was expensive. It no longer is, for most of what a form validator does. What's left is the claim, and reading it straight out of a function's branches, reserved words included, beats guessing it from a document every time, whether the document was written by a product manager or reconstructed from memory by whoever's writing the test.
Verifying that claim by asserting a specific rejection reason, rather than a bare pass or fail, is also what separates a real test from a generated test that passes without checking anything. A test that only confirms valid: false for the "admin" case has confirmed almost nothing; a test that confirms the reason is specifically reserved_word, not already_taken or any of the other five, has confirmed the actual partition. That's the specific habit Autonoma is built around: read the code first, and let the specification it implies come from there instead of from whatever a requirements document happened to remember to mention.
Frequently Asked Questions
Equivalence partitioning is a test design technique that groups a system's possible inputs into equivalence classes, sets of inputs the system is supposed to treat identically, then tests one representative value from each class instead of every value in it. Each class is either a valid partition, inputs that should be accepted, or an invalid partition, inputs that should be rejected, usually for a specific, identifiable reason.
A username registration rule requiring three to twenty characters, letters, digits, and underscores only, and a letter as the first character produces several equivalence classes: too short, too long, disallowed characters, invalid start, and the valid partition itself, plus already taken as a separate rejection reason checked once the format passes. Testing one representative input from each class, rather than every possible string, is equivalence partitioning in practice.
Equivalence partitioning identifies which classes of input exist and treats every member of a class as interchangeable for testing purposes. Boundary value analysis assumes those classes already exist and tests specifically at, and just beside, the edges between them, since an inequality operator implemented one integer off from the specification is one of the most common places a bug hides. The two techniques are usually applied together: partitioning finds the classes, and boundary value analysis finds where each one actually ends.
The traditional approach reads a requirements document or specification and infers classes from the stated rules: a length requirement implies a too-short and too-long class, an allowed-character rule implies a disallowed-character class, and so on. A more complete approach reads the validating function's actual branches directly, since a codebase frequently contains rejection reasons, like a reserved-word check, that a requirements document never mentions but that still define a real equivalence class.
Classically, equivalence partitioning is taught as a black box technique: classes are derived from a specification or observed behavior, without looking at the source code. In practice, reading the actual implementation to derive partitions, rather than guessing them from a document, is a grey box move, and it reliably finds classes a specification-only reading misses.
Yes, for the codebase-level version of the exercise this article walked through by hand. Our Planner agent reads a codebase's validators, the length checks, character patterns, and lookups that decide whether an input is accepted, and derives equivalence classes directly from those branches, including ones like a reserved-word check that a requirements document never mentions. It doesn't replace unit tests written against the validator itself. It's the layer that verifies the same rule through the running application a real user would use.




