Equivalence Partitioning: Test Design Technique Explained With Worked Examples
You cannot test every possible input, so which values should you choose? Equivalence partitioning gives you a clear answer. Learn the method step by step, work through real examples and see where it needs help.
- Equivalence partitioning divides inputs into groups that should behave the same way.
- You test one representative value from each group rather than every value.
- Always include invalid partitions, since error handling is where many bugs hide.
- It works best alongside boundary value analysis, because bugs often sit at the edges of a group.
An age field that accepts 18 to 60 has an enormous number of possible inputs, once you count negatives, huge numbers, text and blanks. Nobody can try them all. Equivalence partitioning solves this, by grouping inputs that the software should treat the same way, and then testing one value from each group.
It is one of the first test design techniques every tester should learn. In this guide we work through it step by step, and test one example in Python. We ran that code on Python 3.13, and its output is included. The password and coupon rules below are illustrative examples, not taken from a real system.
What is equivalence partitioning?
Equivalence partitioning, also called equivalence class partitioning (ECP), is a black box test design technique. It divides the possible inputs of a feature into partitions, or classes. Every value inside a partition is expected to produce the same kind of behaviour.
Because you do not need to see the code, you can apply it directly from a requirement. To learn more about this style of testing, read black box vs white box testing.
Why it works
The technique rests on one assumption: if one value in a partition finds a bug, the others in that partition would probably find it too. And if one value passes, the others would probably pass too. Testing 25, 26 and 27 in an adult age field is likely to tell you nothing that testing 25 alone did not.
| Approach | Tests for an age field | Result |
|---|---|---|
| Exhaustive | Every possible value | Impossible in practice |
| Random guessing | Whatever comes to mind | Duplicates and gaps |
| Equivalence partitioning | One per partition | Small, with deliberate coverage |
It is an assumption, though, and not a guarantee. Software can have special cases hidden inside a partition, a limitation we return to later.
How to do it in five steps
- Read the requirement and identify the input and its rules.
- Find the valid partitions, which are the groups of values that should be accepted, and note if different ranges behave differently.
- Find the invalid partitions, including values too low, too high, the wrong type and blank.
- Choose one representative value from each partition, usually somewhere in the middle.
- Write down the expected result for each, as in any good test case.
Worked example: an age field
Requirement: Users aged 18 to 60 can register.
| Partition | Type | Test value | Expected result |
|---|---|---|---|
| Below 18 | Invalid | 10 | Rejected |
| 18 to 60 | Valid | 35 | Accepted |
| Above 60 | Invalid | 75 | Rejected |
| Not a number | Invalid | “abc” | Error |
| Blank | Invalid | (empty) | “Required” message |
Five tests give you coverage of every behaviour in the requirement. Many tutorials stop at the first three rows, but the last two are the ones that real users trigger by accident. You may also add rows for negative numbers or decimals, if those could reasonably be handled differently.
Testing it in code
Here is that rule as a function, with one unit test per partition, using Python’s built-in unittest:
import unittest
def can_register(age):
if not isinstance(age, int) or isinstance(age, bool):
raise TypeError("age must be a whole number")
return 18 <= age <= 60
class EquivalenceTests(unittest.TestCase):
def test_valid_partition(self):
self.assertTrue(can_register(35))
def test_too_young_partition(self):
self.assertFalse(can_register(10))
def test_too_old_partition(self):
self.assertFalse(can_register(75))
def test_wrong_type_partition(self):
with self.assertRaises(TypeError):
can_register("abc")
Our run passed:
test_too_old_partition ... ok
test_too_young_partition ... ok
test_valid_partition ... ok
test_wrong_type_partition ... ok
In a real project, pytest’s parametrize feature is a neat way to run a table of partition values through a single test.
Worked example: a password
Requirement: A password must be 8 to 20 characters long and contain at least one digit.
This one has two rules, length and content, so we consider partitions for both:
| Partition | Type | Test value | Expected result |
|---|---|---|---|
| Valid length, has a digit | Valid | sunrise42 |
Accepted |
| Too short | Invalid | abc1 |
Error: too short |
| Too long | Invalid | A 30-character value with a digit | Error: too long |
| Valid length, no digit | Invalid | sunriseday |
Error: digit needed |
| Blank | Invalid | (empty) | Error: required |
Notice that each invalid test breaks only one rule. If a value were both too short and missing a digit, a passing test could not tell you which check worked. One check might be broken, while the other hides the fact.
Worked example: non-numeric inputs
The technique is not limited to number ranges. Discrete options, categories and states form partitions as well. Suppose a shop has this rule:
Requirement: Delivery is free for Gold members, 50 for Silver and 100 for everyone else. Unknown tier values are errors.
| Partition | Test value | Expected fee |
|---|---|---|
| Gold | gold |
0 |
| Silver | silver |
50 |
| No membership | (none) | 100 |
| Unknown tier | platinum |
Error |
Here, each option is its own partition, as each one should behave differently. Other common partitions include file types, such as valid and invalid uploads, and output-based groups, such as a loan being approved or declined.
Tip: Think about outputs as well as inputs. If a calculation can produce a discount, no discount or a warning, make sure at least one test reaches each of those outcomes.
Several inputs at once
With multiple fields, the number of combinations grows fast. Three fields with four partitions each give 64 combinations. Two strategies help:
- Weak coverage: make sure each partition appears in at least one test, combining several valid values per test. For invalid values, test one at a time so that errors do not mask each other.
- Rule-based coverage: when combinations actually change the result, such as member status with order value, use decision table testing to list them properly.
Pairing with boundary value analysis
Partitioning tells you which groups to test. It says little about where in a group bugs like to hide, and many hide right at the edges. To show this, we wrote a buggy version with the classic off-by-one mistake:
def buggy(age):
return 18 < age <= 60 # should be 18 <= age
[buggy(a) for a in (35, 10, 75)] # [True, False, False]
buggy(18) # False
Our partition values, 35, 10 and 75, all gave the correct answers, so those tests would pass. Yet an 18-year-old is wrongly rejected. This is why the two techniques belong together:
| Technique | Chooses | Values for age 18 to 60 |
|---|---|---|
| Equivalence partitioning | One value inside each group | 10, 35, 75 |
| Boundary value analysis | Values at the edges of each group | 17, 18, 60, 61 |
Use partitioning to find the groups, and then boundaries to test where groups meet. In our code, a separate test for 17, 18, 60 and 61 passed on the correct function, and would catch that bug. Our boundary value analysis guide covers this in full.
Advantages and limitations
| Advantages | Limitations |
|---|---|
| Greatly reduces the number of tests | Relies on partitions being correct |
| Gives clear, explainable coverage | Can miss bugs at boundaries |
| Needs no access to code | Can miss hidden special cases |
| Applies early, even from requirements | Does not model sequences or combinations well |
The biggest risk is an incorrect assumption. If the code has a hidden rule for one specific value, your chosen test may simply not reach it. Exploratory testing and code coverage help you find what the partitions missed.
Common mistakes
- Forgetting invalid partitions. Testing only happy paths leaves error handling unchecked.
- Testing several values from one partition. This adds cost without new information.
- Combining several invalid values in one test. One error can hide another.
- Skipping boundaries. Partition tests alone often miss off-by-one bugs.
- Guessing the rules. If a requirement is unclear, ask. Should 60 include people who are 60 and a half?
Final thoughts
Equivalence partitioning replaces “how many tests should I write?” with a much better question: “which behaviours exist, and have I covered each one?” To try it, take a form you are testing today and list its partitions on paper, including the invalid ones. Add boundaries next. You will often end up with fewer tests that find more bugs.
Frequently asked questions
What is equivalence partitioning in testing?
It is a black box technique that divides inputs into groups expected to behave alike. You then test one representative value from each group, not every value.
What is the difference between equivalence partitioning and boundary value analysis?
Partitioning picks one value from inside each group. Boundary value analysis tests the values at the edges of those groups. They are usually used together.
How many test cases does equivalence partitioning need?
Usually at least one per partition, both valid and invalid. An age range field commonly needs about three to five tests.
Is equivalence partitioning black box or white box?
It is a black box technique, since partitions come from requirements and do not need the code. Developers can apply it to unit tests too, however.
What are valid and invalid partitions?
Valid partitions contain values the system should accept. Invalid partitions contain values it should reject, such as out-of-range numbers, wrong types and blanks.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.