Test Case Design Techniques Explained (With Examples)
Testing without a systematic approach leads to redundant checks, missed defects, and wasted effort. When a tester relies solely on intuition, the resulting suite often covers obvious paths while ignor
Why Test Case Design Matters
Testing without a systematic approach leads to redundant checks, missed defects, and wasted effort. When a tester relies solely on intuition, the resulting suite often covers obvious paths while ignoring edge conditions that cause failures in production. Structured test‑case design techniques transform requirements into a compact, high‑signal set of scenarios that maximize defect detection per executed test. By applying equivalence partitioning, boundary value analysis, decision tables, state transition models, pairwise combinations, and error guessing, teams can:
- Reduce the number of test cases needed to achieve a target coverage level.
- Ensure that each test targets a distinct class of input or system state.
- Provide a repeatable basis for peer review and traceability to specifications.
- Facilitate automation because the derived cases are explicit and deterministic.
The techniques described below are black‑box; they treat the system as a specification‑driven entity and do not require knowledge of internal code. They are most effective when applied early in the test‑design phase, before test scripts are written.
Core Black‑Box Techniques Overview
What Is Black‑Box Testing?
Black‑box testing derives test cases from the external behavior of a system—its inputs, outputs, and state transitions—without inspecting source code or internal architecture. The tester assumes the specification is correct and focuses on verifying that the implementation conforms to it.
Goals of Technique‑Based Design
Each technique serves a specific purpose:
| Technique | Primary Goal | Typical Input Domain | Typical Output Domain |
|---|---|---|---|
| Equivalence Partitioning | Reduce redundant input tests by grouping equivalent values | Finite or infinite sets (e.g., numeric ranges, enumerations) | Pass/Fail based on specification |
| Boundary Value Analysis | Target errors that occur at the edges of input domains | Same as EP, with emphasis on min, max, just inside/outside | Pass/Fail |
| Decision Table Testing | Capture complex business rules with multiple conditions | Discrete condition combinations (True/False) | Action outcomes |
| State Transition Testing | Model system behavior as a sequence of states and events | States and events (e.g., UI screens, protocol phases) | Valid/invalid transitions |
| Pairwise Testing | Exercise interactions of pairs of parameters to catch most interaction defects | Multi‑parameter configuration spaces | System response |
| Error Guessing | Leverage tester experience to target likely fault locations | Heuristic‑based, often ad‑hoc | Defect detection |
Understanding when each technique shines helps testers build a balanced suite that is both efficient and thorough.
Equivalence Partitioning (EP)
Fundamentals
EP divides the input domain of a component into partitions where the system is expected to behave identically for all members of a partition. If one value in a partition passes, the assumption is that all other values in that partition will also pass; similarly, a single failure indicates the whole partition is suspect. Partitions can be valid (values the system should accept) or invalid (values the system should reject).
The process:
- Identify each input variable and its specification (range, set, format).
- Create one valid partition for each specified condition and one invalid partition for each condition’s complement.
- Select a representative value from each partition to form a test case.
Worked Example: Login Form
Consider a web login with two fields: username (alphanumeric, 3‑15 characters) and password (minimum 8 characters, at least one digit).
| Input | Specification | Valid Partition(s) | Invalid Partition(s) |
|---|---|---|---|
| Username | alphanumeric, length 3‑15 | “abc123”, “User9” | “ab” (too short), “user_name” (underscore), “averylongusernameexceedinglimit” (too long) |
| Password | ≥8 chars, ≥1 digit | “Passw0rd”, “12345678” | “password” (no digit), “123” (too short), “Pass!” (no digit, special char allowed? assume not) |
From these partitions we derive test cases by picking one representative from each valid and invalid set. A minimal EP suite might look like:
| TC ID | Username | Password | Expected Result |
|---|---|---|---|
| 1 | abc123 (valid) | Passw0rd (valid) | Login success |
| 2 | ab (invalid) | Passw0rd (valid) | Username error |
| 3 | averylongusernameexceedinglimit (invalid) | Passw0rd (valid) | Username error |
| 4 | abc123 (valid) | password (invalid) | Password error |
| 5 | abc123 (valid) | 123 (invalid) | Password error |
| 6 | abc123 (valid) | Pass! (invalid) | Password error |
Only six test cases are needed to cover all partitions, versus testing every possible username/password combination, which would be infeasible.
Deriving Test Cases
When multiple inputs exist, the naive Cartesian product of partitions explodes. EP alone does not address interaction effects; it assumes independence. To keep the suite small, testers often combine EP with other techniques (e.g., decision tables or pairwise) for multi‑parameter scenarios.
Limitations and Mitigations
- Assumption of independence – if a defect only appears when a specific username length coincides with a particular password character set, EP may miss it. Mitigation: follow EP with pairwise testing for the remaining risk.
- Partition identification errors – ambiguous specifications lead to overlapping or missing partitions. Mitigation: conduct a specification walkthrough with developers and product owners before partitioning.
- Invalid partition overload – many invalid partitions can produce a large number of negative tests. Mitigation: prioritize invalid partitions based on risk (e.g., those most likely to be entered by users).
Boundary Value Analysis (BVA)
Fundamentals
BVA focuses on the values at the edges of input domains because defects frequently cluster there. For each input range [min, max], BVA generates test values: min, min+1, max‑1, max, and optionally values just outside the range (min‑1, max+1). When dealing with output boundaries or internal calculations, the same principle applies.
Worked Example: Age Field
A registration form accepts an age integer between 18 and 65 inclusive.
| Boundary Type | Values |
|---|---|
| Valid min | 18 |
| Valid min+1 | 19 |
| Valid max‑1 | 64 |
| Valid max | 65 |
| Invalid min‑1 | 17 |
| Invalid max+1 | 66 |
A robust BVA suite adds the “just outside” values to catch off‑by‑one errors in both directions. Test cases:
| TC ID | Age | Expected Result |
|---|---|---|
| 1 | 18 | Accept |
| 2 | 19 | Accept |
| 3 | 64 | Accept |
| 4 | 65 | Accept |
| 5 | 17 | Reject (underage) |
| 6 | 66 | Reject (overage) |
If the system also validates that age must be numeric, we add non‑numeric tests (e.g., “eighteen”, “18.5”) as separate invalid partitions.
Robust BVA
Robust BVA extends the basic set by including multiple invalid values on each side (e.g., min‑2, min‑1, max+1, max+2) and sometimes worst‑case combinations when more than one input variable is present. For a single variable, the robust set is: {min‑2, min‑1, min, min+1, max‑1, max, max+1, max+2}.
When to Prefer BVA Over EP
- When the specification emphasizes numeric ranges, timestamps, or ordered enumerations.
- When historical defect data shows a high concentration of faults near limits.
- When the input domain is large or continuous, making EP partitions too coarse.
BVA is often applied after EP to refine the test set around the borders of each partition.
Decision Table Testing
Fundamentals
Decision tables excel when business rules involve multiple conditions that combine to produce distinct actions. Each column of the table represents a unique combination of condition values (True/False) and the corresponding expected actions. The technique ensures that every relevant condition combination is considered, reducing the chance of missing a rule.
Steps:
- List all conditions (inputs) that influence the outcome.
- List all actions (outputs) the system may perform.
- Determine the number of possible condition combinations (2ⁿ for n binary conditions).
- Fill the table, marking each condition as T or F and specifying the resulting action(s).
- Eliminate impossible or irrelevant columns (e.g., mutually exclusive conditions).
- Derive one test case per remaining column.
Worked Example: Discount Eligibility
An e‑commerce site applies discounts based on three factors:
- C1 – Customer type: *Regular* (R) or *Premium* (P)
- C2 – Order amount: *Low* (<$50) or *High* (≥$50)
- C3 – Coupon code present: *Yes* (Y) or *No* (N)
Actions:
- A1 – Apply 5% discount
- A2 – Apply 10% discount
- A3 – Apply 15% discount
- A4 – No discount
The decision table (showing only feasible columns) is:
| C1 | C2 | C3 | A1 | A2 | A3 | A4 |
|---|---|---|---|---|---|---|
| R | L | N | X | |||
| R | L | Y | X | |||
| R | H | N | X | |||
| R | H | Y | X | |||
| P | L | N | X | |||
| P | L | Y | X | |||
| P | H | N | X | |||
| P | H | Y | X |
(Explanation: Premium customers never receive a discount on low orders; high‑order premium customers get 10%; coupon adds an extra 5% up to a maximum of 15%.)
Each column yields a test case:
| TC ID | Customer Type | Order Amount | Coupon | Expected Discount |
|---|---|---|---|---|
| 1 | Regular | Low | No | 0% |
| 2 | Regular | Low | Yes | 5% |
| 3 | Regular | High | No | 10% |
| 4 | Regular | High | Yes | 15% |
| 5 | Premium | Low | No | 0% |
| 6 | Premium | Low | Yes | 5% |
| 7 | Premium | High | No | 10% |
| 8 | Premium | High | Yes | 0% (coupon not applicable) |
Building the Table
When conditions are not strictly binary (e.g., three‑level priority), expand each condition into multiple binary sub‑conditions or use extended decision tables that allow multiple values per condition cell. Tools like DTable or simple spreadsheets can automate the generation of all combinations and the removal of infeasible rows.
Automation Tips
- Export the decision table to CSV and feed it into a data‑driven test framework (e.g., TestNG @DataProvider, JUnit 5 @ParameterizedTest).
- Each row becomes a test iteration; assertions verify that the observed actions match the table’s action columns.
- Keep the table under version control; changes to business rules are reflected by editing the table and re‑generating tests.
State Transition Testing
Fundamentals
State transition testing models a system as a finite set of states and events (or triggers) that cause transitions between states. Each transition may have an associated action or output. The technique is ideal for protocols, UI workflows, and any component where behavior depends on prior history.
A state transition diagram (or state table) captures:
- States – stable conditions the system can reside in (e.g., LoggedOut, LoggedIn, PasswordReset).
- Events – inputs that may cause a change (e.g., SubmitLogin, Timeout, Cancel).
- Transitions – directed arcs labeled with event and optionally guard conditions.
- Actions – outputs performed during a transition (e.g., ShowHomePage, SendEmail).
Test cases are sequences of events that traverse specific paths, including valid transitions, invalid transitions, and loops.
Worked Example: Shopping Cart Lifecycle
Consider a simplified cart with states: Empty, ItemsAdded, CheckoutStarted, PaymentPending, OrderConfirmed, OrderCancelled. Events: AddItem, RemoveItem, ProceedToCheckout, SubmitPayment, CancelOrder, Timeout.
State Table (excerpt):
| Current State | Event | Guard | Next State | Action |
|---|---|---|---|---|
| Empty | AddItem | itemId valid | ItemsAdded | Show item in cart |
| ItemsAdded | RemoveItem | cart not empty | Empty if last item | Update cart |
| ItemsAdded | ProceedToCheckout | cart not empty | CheckoutStarted | Show checkout page |
| CheckoutStarted | SubmitPayment | payment valid | PaymentPending | Call payment gateway |
| PaymentPending | SubmitPayment | payment invalid | CheckoutStarted | Show error |
| PaymentPending | Timeout | – | OrderCancelled | Release inventory |
| PaymentPending | SubmitPayment | payment valid | OrderConfirmed | Send confirmation email |
| AnyState | CancelOrder | – | OrderCancelled | Clear cart |
From this table we can derive test sequences:
- Normal path – Empty → ItemsAdded (AddItem) → CheckoutStarted (ProceedToCheckout) → PaymentPending (SubmitPayment) → OrderConfirmed (SubmitPayment).
- Invalid payment – … → PaymentPending (SubmitPayment with invalid card) → CheckoutStarted (error).
- Timeout – … → PaymentPending (wait > threshold) → OrderCancelled.
- Cancel during checkout – CheckoutStarted → CancelOrder → OrderCancelled.
Each sequence becomes a test case; assertions verify that the system ends in the expected state and performs the expected actions (e.g., email sent, inventory updated).
Drawing the Diagram
Tools such as draw.io, Microsoft Visio, or PlantUML enable quick creation of state diagrams. PlantUML snippet for the cart:
[*] --> Empty
Empty --> ItemsAdded : AddItem
ItemsAdded --> Empty : RemoveItem[last]
ItemsAdded --> CheckoutStarted : ProceedToCheckout
CheckoutStarted --> PaymentPending : SubmitPayment[valid]
PaymentPending --> OrderConfirmed : SubmitPayment[valid]
PaymentPending --> CheckoutStarted : SubmitPayment[invalid]
PaymentPending --> OrderCancelled : Timeout
CheckoutStarted --> OrderCancelled : CancelOrder
OrderConfirmed --> [*]
OrderCancelled --> [*]
Generating Test Sequences
- Depth‑first search (DFS) of the state graph yields all possible paths up to a given length.
- Transition coverage aims to exercise each transition at least once.
- State coverage ensures each state is visited.
- Boundary testing in state machines involves sequences that trigger a transition just before or after a guard condition changes (e.g., adding the last item to cause a state shift from ItemsAdded to Empty).
Automated test generation can be performed with model‑based testing tools (e.g., GraphWalker, Yakindu) that consume the state model and emit executable scripts.
Pairwise (Combinatorial) Testing
Fundamentals
Pairwise testing assumes that most defects are triggered by interactions of at most two parameters. Instead of testing the full Cartesian product of all parameter values, it selects a subset of combinations that guarantees every pair of parameter values appears together in at least one test case. This often reduces the test count dramatically while preserving high defect detection power.
The problem is NP‑hard, but greedy algorithms (e.g., PICT, Allpairs) produce near‑optimal results quickly.
Worked Example: Configuration Matrix
A mobile app must be tested across three dimensions:
- OS: Android 11, Android 12, iOS 14, iOS 15
- Network: Wi‑Fi, 4G, 5G
- Language: English, Spanish, Japanese
Full factorial: 4 × 3 × 3 = 36 combinations.
Using a pairwise tool (e.g., pip install pairwise or PICT), we obtain a set of 12 test cases that cover all pairs:
| TC | OS | Network | Language |
|---|---|---|---|
| 1 | Android 11 | Wi‑Fi | English |
| 2 | Android 11 | 4G | Spanish |
| 3 | Android 11 | 5G | Japanese |
| 4 | Android 12 | Wi‑Fi | Spanish |
| 5 | Android 12 | 4G | Japanese |
| 6 | Android 12 | 5G | English |
| 7 | iOS 14 | Wi‑Fi | Japanese |
| 8 | iOS 14 | 4G | English |
| 9 | iOS 14 | 5G | Spanish |
| 10 | iOS 15 | Wi‑Fi | English |
| 11 | iOS 15 | 4G | Japanese |
| 12 | iOS 15 | 5G | Spanish |
Verification: each OS value appears with each Network value at least once, each OS with each Language, and each Network with each Language.
Tools and Algorithms
- PICT (Microsoft) – command‑line, accepts a model file with parameters and values, outputs pairwise combinations.
- Allpairs (open‑source Python) – simple implementation based on the algorithm by Cohen et al.
- ACTS (NIST) – supports higher‑order strengths (t‑way) beyond pairwise.
Example using Allpairs in Python:
from allpairs import allpairs
params = [
["Android 11", "Android 12", "iOS 14", "iOS 15"],
["Wi-Fi", "4G", "5G"],
["English", "Spanish", "Japanese"]
]
for combo in allpairs(params):
print(combo)
Limitations
- Higher‑order interactions – defects that require three or more specific values may be missed. Mitigation: apply triwise (3‑way) testing for high‑risk areas or combine pairwise with risk‑based selection.
- Parameter dependence – if certain values are invalid together (e.g., iOS with a specific Android‑only feature), the algorithm may generate infeasible pairs. Pre‑filter the model or use constrained pairwise tools.
- Ordinal vs. nominal – treating ordered values as nominal can produce redundant pairs; consider value‑wise weighting if some values are more critical.
Error Guessing and Experience‑Based Techniques
Fundamentals
Error guessing relies on the tester’s knowledge of common failure modes, past defects, and system specifics to devise targeted tests. While less formal than the other techniques, it excels at catching edge cases that specifications overlook, such as off‑by‑one loops, race conditions, or usability pitfalls.
Common sources for error guessing:
- Bug databases – recurring defect types (e.g., null pointer on empty list).
- Technology‑specific pitfalls – Android Activity lifecycle, iOS memory warnings, browser caching quirks.
- Checklists – OWASP Top 10 for security, WCAG 2.1 for accessibility, performance heuristics.
Worked Example: File Upload
A web service lets users upload profile pictures. Specification: accept JPEG/PNG, max size 5 MB, dimensions 200 × 200 px to 4000 × 4000 px.
From experience, a tester might guess the following failure points:
- Empty file – zero‑byte upload.
- File with correct extension but wrong content – rename a .txt to .jpg.
- File exactly at size limit – 5 MB + 1 byte.
- File with dimensions just outside bounds – 199 × 199 px, 4001 × 4001 px.
- Concurrent uploads – two simultaneous requests from the same session.
- Filename with special characters –
..,/, Unicode emojis. - MIME type spoofing – set
Content-Type: image/jpegfor a PDF.
Each guess becomes a test case, often combined with other techniques (e.g., boundary values for size).
Combining with Structured Techniques
Error guessing is most effective when used after systematic techniques have covered the main partitions. The tester reviews the generated test set, identifies gaps (e.g., missing concurrency or security checks), and adds focused guesses. This hybrid approach yields a suite that is both comprehensive and agile.
Selecting the Right Technique
Decision Matrix
Choosing a technique depends on the nature of the input domain, the presence of complex business rules, and the importance of stateful behavior. The following matrix helps map scenario characteristics to recommended techniques:
| Scenario Characteristic | Best Fit Technique(s) | Rationale |
|---|---|---|
| Simple numeric or enumerated input, independent fields | EP + BVA | Partitions and boundaries capture most faults. |
| Multiple interdependent conditions affecting output | Decision Table | Ensures every condition combination is examined. |
| Behavior depends on prior actions or events | State Transition | Models histories and validates transitions. |
| Many configurable parameters (OS, device, locale, etc.) | Pairwise (or higher‑order t‑way) | Controls combinatorial explosion. |
| No clear specification, high reliance on tester intuition | Error Guessing + Exploratory | Targets known failure patterns. |
| Mixed (e.g., a form with fields, business rules, and workflow) | Combine EP/BVA → Decision Table → State Transition → Pairwise → Error Guessing | Layered approach addresses each aspect. |
Technique Combination Patterns
- EP → BVA → Pairwise – Start with EP to identify partitions, refine each partition’s borders with BVA, then apply pairwise across the resulting parameters to catch interaction defects.
- Decision Table → State Transition – Use a decision table to define actions for each condition set, then map those actions onto state transitions to verify that the system reaches the correct state after processing the rules.
- Pairwise → Error Guessing – Generate a pairwise set for configuration testing, then add error‑guessing tests for known platform‑specific bugs (e.g., Android WebView quirks).
The key is to avoid redundancy: each added technique should target a risk not already covered by the previous ones.
From Design to Execution: Manual and Automated Approaches
Manual Test Scripts
When tests are low‑volume or require human judgment (e.g., usability, exploratory), manual execution remains valuable. A manual test script derived from EP/BVA might look like:
Test ID: LOGIN-03
Precondition: User is on login page.
Steps:
1. Enter username "ab" (2 chars, invalid).
2. Enter password "Passw0rd".
3. Tap Login.
Expected Result: Error message "Username must be at least 3 characters."
Manual execution allows the tester to observe subtle UI feedback, such as toast messages or focus shifts, that automated scripts might overlook unless explicitly asserted.
Automated Script Generation (Appium, Playwright)
For regression and CI pipelines, converting designed cases into automated scripts ensures repeatability. Below are concise examples for the login form using Appium (Android) and Playwright (Web).
Appium Java (Android)
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import org.openqa.selenium.By;
import org.testng.annotations.*;
public class LoginTest {
private AppiumDriver<MobileElement> driver;
@BeforeMethod
public void setUp() {
// capabilities omitted for brevity
driver = new AppiumDriver<>(new URL("http://localhost:4723/wd/hub"), caps);
}
@Test(dataProvider = "loginData")
public void testLogin(String user, String pass, boolean shouldPass) {
driver.findElement(By.id("username")).sendKeys(user);
driver.findElement(By.id("password")).sendKeys(pass);
driver.findElement(By.id("loginBtn")).click();
if (shouldPass) {
assert driver.findElement(By.id("homeScreen")).isDisplayed();
} else {
assert driver.findElement(By.id("errorMsg")).isDisplayed();
}
}
@DataProvider
public Object[][] loginData() {
return new Object[][]{
{"abc123", "Passw0rd", true},
{"ab", "Passw0rd", false},
{"averylongusernameexceedinglimit", "Passw0rd", false},
{"abc123", "password", false},
{"abc123", "123", false},
{"abc123", "Pass!", false}
};
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}
Playwright TypeScript (Web)
import { test, expect } from '@playwright/test';
test.describe('Login form', () => {
const testCases = [
{ user: 'abc123', pass: 'Passw0rd', pass: true },
{ user: 'ab', pass: 'Passw0rd', pass: false },
{ user: 'averylongusernameexceedinglimit', pass: 'Passw0rd', pass: false },
{ user: 'abc123', pass: 'password', pass: false },
{ user: 'abc123', pass: '123', pass: false },
{ user: 'abc123', pass: 'Pass!', pass: false }
];
for (const {user, pass, pass: shouldPass} of testCases) {
test(`login with user=${user}, pass=${pass}`, async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#username', user);
await page.fill('#password', pass);
await page.click('#loginBtn');
if (shouldPass) {
await expect(page.locator('#homeScreen')).toBeVisible();
} else {
await expect(page.locator('#errorMsg')).toBeVisible();
}
});
}
});
Both snippets illustrate how a data‑driven approach maps directly to the EP-derived test matrix. The same principle applies to decision tables (iterating over rows), state transitions (executing event sequences), and pairwise configurations (varying OS/network/language parameters).
Integrating with CI
- Store test scripts in a version‑controlled repository (e.g., Git).
- Configure a CI job (GitHub Actions, GitLab CI, Jenkins) to run the test suite on every pull request.
- Publish results as JUnit XML or TestResult JSON for trend analysis.
- Flaky tests can be quarantined and investigated separately.
Edge Cases That Appear Only in Production
Even the most meticulously designed test suite can miss defects that only manifest under real‑world load, timing, or environmental conditions. Recognizing these categories helps testers augment their suites with targeted probes.
Timing and Concurrency
- Race conditions – e.g., two users attempting to claim the last coupon simultaneously.
- Resource exhaustion – file handles, database connections, or memory leaks that surface after prolonged operation.
- Network latency spikes – timeouts that differ from lab emulators.
Mitigation: incorporate stress and soak tests, use tools like Gatling or k6 to simulate concurrent users, and inject artificial delays (e.g., Thread.sleep or network throttling) in automated scripts.
Localization and Data Variants
- Character encoding – UTF‑8 vs. ISO‑8859‑1 causing garbled text in non‑Latin languages.
- Date/time formats – mm/dd/yyyy vs. dd/mm/yyyy leading to validation errors.
- Currency handling – rounding differences across jurisdictions.
Mitigation: include locale‑specific data sets in EP/BVA (e.g., test with “JP” locale, Japanese characters, and yen symbol). Use pseudo‑localization to uncover hard‑coded strings.
Device‑Specific Quirks
- Screen density – assets appearing blurry on xxhdpi devices.
- Hardware buttons – back button intercepting gestures.
- OS‑level restrictions – iOS limiting background location, Android battery optimizations killing services.
Mitigation: run a subset of tests on a device farm (e.g., Firebase Test Lab, AWS Device Farm) covering a matrix of OS versions, screen sizes, and manufacturers. Pairwise testing can help select a representative device set.
Autonomous Exploration as a Complement
How SUSA Explores
SUSA (SUSATest) is an autonomous QA agent that, given an APK or a web URL, builds a model of the application’s reachable states by interacting with UI elements, filling forms, handling dialogs, and navigating flows. It employs a blend of guided randomness and heuristic-driven exploration (e.g., favoring unexplored elements, respecting accessibility hints). Each run produces:
- A state graph of screens and transitions.
- Detected crashes, ANRs, dead clicks, WCAG violations, and security issues.
- Regression scripts in Appium (Android) and Playwright (Web) that reproduce the observed interactions.
Feeding Discovered Flows Back into Designed Cases
The output of SUSA can be used to enrich manually designed test suites in two ways:
- Gap Analysis – Compare the set of flows discovered by SUSA against the flows covered by existing test cases. Any
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free