Testing an age field that accepts 18–65 with random values like 25, 42, and 60 tells you nothing that the first value didn't already tell you. The bugs hide at the edges: 17, 18, 65, and 66, the exact values where an off-by-one error in the condition shows up. This article covers the five systematic techniques for deciding which inputs to test: equivalence partitioning, boundary value analysis, decision tables, state transition testing, and pairwise combinatorial testing, with a guide for choosing among them based on the feature type.
Equivalence Partitioning: Stop Testing the Same Thing Twice
The core idea is simple: if two inputs will produce the same behavior, you only need to test one of them. You divide the full input space into groups (called partitions or equivalence classes) where every value in a group behaves the same way. Then you test one representative value from each group.
Consider a registration form with an age field. The requirement says users must be between 18 and 65. You could test every integer from 0 to 120, but that's 121 test cases. Equivalence partitioning tells you there are only four groups that matter:
| Partition | Range | Example value |
|-----------|-------|---------------|
| Valid | 18–65 | 35 |
| Too young (invalid) | Below 18 | 10 |
| Too old (invalid) | Above 65 | 80 |
| Wrong type (invalid) | Non-numeric | "twenty" |
Four test cases instead of 121. The logic is that if 35 passes and 36 also passes, testing 36 gives you no additional information. If 10 fails correctly, 11 will too.
Equivalence partitioning applies to any input with a defined valid range: text fields with length limits, dropdowns with allowed values, file uploads with size restrictions, API parameters with enumerated valid values.
Boundary Value Analysis: Where Bugs Actually Hide
Developers make mistakes at edges. An off-by-one error looks like if (age > 18) instead of if (age >= 18). Equivalence partitioning finds the partitions; boundary value analysis (BVA) tests the walls between them.
For the same 18–65 age field, BVA produces these test cases:
| Value | Partition boundary | Expected result |
|-------|--------------------|-----------------|
| 17 | Just below lower bound | Rejected |
| 18 | Lower bound | Accepted |
| 19 | Just above lower bound | Accepted |
| 64 | Just below upper bound | Accepted |
| 65 | Upper bound | Accepted |
| 66 | Just above upper bound | Rejected |
Six test cases that cover both boundaries on both sides. Combined with equivalence partitioning (which handles the middle and the wrong-type case), you have a complete test suite for this field with around 8-10 test cases total.
BVA works anywhere there's a boundary: maximum character lengths in text fields, minimum order quantities in e-commerce, file size limits, date ranges, pagination page counts, API rate limits. The pattern is always the same: test the value at the boundary, one below, and one above.
65 but accepts 64 and 66 reveals an off-by-one error. Name this specifically in your bug report. "Boundary condition failure at upper bound" helps the developer find it faster.Decision Tables: Making Business Logic Visible
Some features don't have a single input with a range; they have multiple conditions that combine to produce different outcomes. Checkout discounts, content access rules, subscription upgrades, notification preferences: all of these have complex combinatorial logic that's hard to reason about from the requirements alone.
Decision tables make that logic explicit. Each column is a test case. Each row is either a condition or an outcome.
Take a discount feature: users get a discount if they have a loyalty card or if their purchase total is over $100. The combinations look like this:
| | Case 1 | Case 2 | Case 3 | Case 4 |
|---|---|---|---|---|
| Has loyalty card | No | Yes | No | Yes |
| Purchase > $100 | No | No | Yes | Yes |
| Gets discount | No | Yes | Yes | Yes |
This is an OR rule, so three out of four cases result in a discount. Four test cases, one per column. If the rule were AND (must have loyalty card AND purchase over $100), only Case 4 would qualify. The table would show that immediately, and you'd know to test all four combinations to confirm the logic.
Decision tables shine when requirements contain words like "if," "when," "unless," "except when," "but only if." Those words signal that conditional logic needs to be mapped out. The table forces you to enumerate every combination, which often reveals cases the requirements didn't specify. What happens if the user has no loyalty card and their order is exactly $100? The table makes that ambiguity visible before development, when it's cheap to resolve.
For a feature with three binary conditions, you get 2³ = 8 combinations. For four conditions, 16. Decision tables don't always require testing all combinations (some may be impossible or irrelevant), but they help you see the full space before deciding which to cut.
State Transition Testing: Features That Remember Where They've Been
Some features don't just respond to inputs; they maintain state, and the valid actions depend on what state they're currently in. Authentication flows, order status workflows, subscription management, user account lifecycle: these all behave differently depending on what already happened.
State transition testing maps the states a feature can be in, the events that cause transitions between states, and the outputs or behaviors expected during each transition.
Take user account management. An account can be in one of three states: Active, Locked, and Suspended. The transitions between them are driven by specific events:
| Current state | Event | Next state | Expected behavior |
|---------------|-------|------------|-------------------|
| Active | 5 failed logins | Locked | Show lockout message, block further attempts |
| Active | Admin suspends | Suspended | Show suspension notice, log out active sessions |
| Locked | 15 minutes pass | Active | Allow login attempts again |
| Locked | Admin unlocks | Active | Immediate access restored |
| Suspended | Admin reinstates | Active | Account restored, user notified |
| Suspended | 5 failed logins | Suspended | Stay suspended (no transition) |
From this table, you derive two types of test cases. The first type verifies valid transitions: confirm that "Active + 5 failed logins → Locked" works as documented. The second type verifies invalid transitions: confirm that a Suspended account cannot transition to Locked by attempting login, and that the system handles that edge case correctly.
The table also reveals gaps. What happens if a Locked account receives an "Admin suspends" event? The requirement might not say. State transition diagrams make these gaps visible as empty cells.
Combinatorial Testing: When the Combinations Explode
Some features have so many input variables that a full decision table is impractical. A search filter with 5 options, each with 3-4 possible values, produces thousands of combinations. You can't test all of them.
Pairwise testing (also called all-pairs testing) is the practical answer. The research shows that most bugs are caused by interactions between two variables, not three or more. Pairwise testing guarantees that every pair of values from different variables appears in at least one test case.
Imagine testing a travel booking form with these options:
- Destination: Europe, Asia, Americas (3 values)
- Trip type: One-way, Round-trip (2 values)
- Class: Economy, Business, First (3 values)
- Departure month: Summer, Winter (2 values)
Full factorial testing: 3 × 2 × 3 × 2 = 36 combinations. Pairwise testing covers all pairs in roughly 9 test cases.
You don't construct pairwise test sets by hand. Tools do it for you. PICT (Pairwise Independent Combinatorial Testing) is a free command-line tool from Microsoft. AllPairs is another. You define your parameters and values, run the tool, and get a minimal test set.
Pairwise testing is the right choice when you have 4 or more variables with multiple values each, when full combinatorial coverage is not feasible in the time available, and when you need a defensible method for reducing test scope without just guessing which combinations to skip.
Which Technique to Use: A Decision Guide
The techniques aren't mutually exclusive. Most real features use more than one. Here's how to pick:
Equivalence partitioning fits any input with a defined valid range: age fields, price ranges, character length limits, enumerated valid values. Use it to organize inputs before writing individual test cases. Boundary value analysis pairs with equivalence partitioning for the same situations. Any time equivalence partitioning identifies a boundary, add BVA test cases around that boundary. Always. Decision tables fit features with multiple independent conditions that combine to produce different outcomes. If the requirements use "if/and/or/unless" logic, build a decision table before writing test cases. State transition testing fits features that have a defined lifecycle or workflow. If an object (user, order, ticket, subscription) can be in different states and transitions between them, map the states first, then derive test cases. Pairwise testing fits configuration testing and features with many independent variables. If you have more than 3-4 variables and full coverage is impractical, reach for a pairwise tool.For a simple input field with no state and no multi-condition logic, equivalence partitioning plus BVA is everything you need. For a complex checkout flow with discounts, user tiers, and multiple payment methods, decision tables plus state transition testing will give you more complete coverage than gut feel ever could.
How to Apply This on Monday Morning
You don't need to overhaul your testing process all at once. Pick one feature you're testing this week and apply one technique.
If you're testing an input field with min/max constraints: write out the equivalence partitions on paper before opening the application. Identify the boundaries. Write the BVA test cases. You'll have a complete field test in five minutes instead of typing random values for twenty.
If you're testing a business rule with multiple conditions (discount logic, access control, notification triggers): build the decision table before writing any test cases. Each column becomes a test case. Check whether any combinations are ambiguous in the requirements and ask before you test.
If you're testing a workflow with status changes (order management, user lifecycle, support ticket routing): draw the state diagram, even roughly. List the valid transitions and the events that trigger them. Then write two test cases per transition: one that confirms it works, one that tries an invalid transition from a similar state.
The goal isn't to apply every technique to every feature. It's to stop relying on memory and instinct for decisions that have correct, systematic answers. The techniques turn "what should I test?" from a question you answer differently each time into a process you can repeat, defend, and teach.
→ See also: How to Write a Test Case: Format, Examples, and Common Mistakes | Risk-Based Testing: Prioritizing What to Test When You Can't Test Everything | Equivalence Partitioning & Boundary Value Analysis: A Practical Guide | Test Case vs. Test Scenario: What's the Difference and When to Use Each