Boundary Value Test Data: How to Generate the Right Cases
Boundary Value Test Data: How to Generate the Right Cases
Boundary value analysis (BVA) is one of the oldest, most reliable techniques in a tester’s toolbox. Yet many teams still treat it as a checkbox exercise—“add the min, max, and one‑off values” —and move on. The result is often a test suite that misses the subtle off‑by‑one bugs that surface in production. This post walks through a repeatable process for generating the right boundary test data, from requirement analysis to automated data creation, with concrete examples, tool guidance, and a checklist you can drop into your next sprint.
Why Boundary Values Matter
| Defect class | Typical symptom | Why BVA catches it |
|---|---|---|
| Off‑by‑one loops | Loop runs once too many / too few | The loop condition is usually expressed as <= max or < max. The boundary is the exact point where the condition flips. |
| Integer overflow / underflow | Crash or wrap‑around on extreme inputs | The max/min of the data type is a hard boundary; values just beyond it expose undefined behaviour. |
| Validation logic errors | “Invalid” accepted or “Valid” rejected | Business rules (e.g., “age must be 18‑120”) are implemented as range checks. The edges of the range are where the logic changes. |
| UI limits (field length, dropdown size) | Truncation, scroll‑bar appears, submit fails | The UI enforces a maximum length or item count; the boundary is the last allowed character / item. |
If you only test “happy‑path” values (e.g., age = 30), you never exercise the decision points that cause the defects above. Boundary test data forces the system to evaluate those decision points.
Core Concepts
| Concept | Definition | Typical notation |
|---|---|---|
| Equivalence partition | A set of inputs that should be treated identically by the system. | [min … max] |
| Boundary value | The extreme values of an equivalence partition plus the immediate neighbours outside the partition. | min‑1, min, min+1, max‑1, max, max+1 |
| Inclusive vs. exclusive bounds | Whether the limit itself is accepted (≤) or rejected (<). | age ≥ 18 (inclusive) vs. age > 18 (exclusive) |
| Multi‑dimensional boundaries | When two or more fields interact (e.g., start‑date ≤ end‑date). | Cross‑product of each field’s boundaries, filtered by the relational rule. |
| Data‑type limits | Hard limits imposed by the programming language or database (e.g., INT32_MAX). | 2 147 483 647 for signed 32‑bit integer. |
Understanding the inclusivity of each bound is the single biggest source of missed cases. A requirement that says “age must be between 18 and 120” is ambiguous until you confirm whether 18 and 120 are valid.
Decision Criteria for Selecting Boundaries
Not every field deserves a full six‑value set. Use the following criteria to decide the depth of coverage:
| Criterion | High coverage (6 values) | Medium coverage (3 values) | Low coverage (1 value) |
|---|---|---|---|
| Business criticality | Core revenue‑impacting fields (price, quantity) | Supporting fields (discount code) | Cosmetic fields (tooltip text) |
| Regulatory / compliance | Mandated ranges (age, dosage) | Internal policy ranges | Optional preferences |
| Historical defect density | Fields with past off‑by‑one bugs | Fields with few defects | New fields with no history |
| Data‑type risk | Near language limits (INT_MAX, DATE_MAX) | Well‑within type range | Far from limits |
| Automation cost | Low (API, DB) | Medium (UI) | High (manual only) |
Apply the table per field, then aggregate. A typical checkout flow might end up with high coverage for quantity, price, shipping‑zip; medium for discount‑code length; low for gift‑message.
Workflow for Generating Boundary Test Data
- Collect requirements – Pull acceptance criteria, API contracts, UI specs, and any regulatory docs.
- Identify equivalence partitions – For each input, write the valid range and any invalid partitions (e.g., negative numbers, non‑numeric strings).
- Determine inclusivity – Confirm with product owner or developer whether the limits are inclusive. Document the decision.
- Select coverage level – Use the decision‑criteria table.
- Produce the raw value list – Apply the six‑value pattern (or three‑value for medium) to each partition.
- Apply cross‑field constraints – Filter or expand the Cartesian product using relational rules (start ≤ end, password ≠ username).
- Encode as test data artefacts – CSV, JSON, SQL INSERT scripts, or a data‑factory method.
- Version‑control the data set – Store alongside test code; tag with the requirement version.
- Integrate into CI – Feed the data to automated tests (API, UI, contract) on every build.
- Review & prune – After each release, retire values that no longer map to a partition (e.g., a deprecated max‑length).
Worked Example: E‑commerce Checkout
1. Requirements snapshot
| Field | Valid range | Inclusivity | Business criticality |
|---|---|---|---|
quantity | 1 – 99 | inclusive | High |
unit_price (cents) | 1 – 10 000 | inclusive | High |
shipping_zip | 5‑digit US ZIP | inclusive (exact 5 digits) | High |
discount_code | alphanumeric, 4‑12 chars | inclusive | Medium |
gift_message | 0‑200 chars | inclusive | Low |
2. Equivalence partitions
| Field | Valid partition | Invalid partitions |
|---|---|---|
quantity | 1 – 99 | ≤0, ≥100, non‑numeric |
unit_price | 1 – 10 000 | ≤0, >10 000, non‑numeric |
shipping_zip | 5‑digit numeric | <5 digits, >5 digits, non‑numeric |
discount_code | 4‑12 alnum | <4, >12, special chars |
gift_message | 0‑200 chars | >200, non‑UTF‑8 |
3. Inclusivity confirmed
All limits are inclusive (e.g., quantity = 1 and = 99 are accepted).
4. Coverage level
| Field | Level |
|---|---|
quantity | High |
unit_price | High |
shipping_zip | High |
discount_code | Medium |
gift_message | Low |
5. Raw boundary values
| Field | Values (high) | Values (medium) | Values (low) |
|---|---|---|---|
quantity | 0, 1, 2, 98, 99, 100 | – | – |
unit_price | 0, 1, 2, 9 999, 10 000, 10 001 | – | – |
shipping_zip | 00000, 00001, 00002, 99998, 99999, 100000 | – | – |
discount_code | – | abc (3), abcd (4), abcdefghijkl (12), abcdefghijklm (13) | – |
gift_message | – | – | "" (empty), "a"*200, "a"*201 |
6. Cross‑field constraints
quantity * unit_pricemust not exceedINT32_MAX(2 147 483 647).discount_code“FREE100” forcesunit_priceto 0 for the discounted line.
We generate the Cartesian product of the three high‑coverage fields (6 × 6 × 6 = 216 rows) then filter out any row where quantity * unit_price > 2 147 483 647. The remaining rows are paired with each discount‑code variant (4) and the three gift‑message variants (3), yielding a final data set of ≈ 2 600 rows—small enough for fast CI runs, large enough to hit every boundary.
7. Artefact example (JSON Lines)
{"quantity":1,"unit_price":1,"shipping_zip":"00001","discount_code":"abcd","gift_message":""}
{"quantity":1,"unit_price":1,"shipping_zip":"00001","discount_code":"abcd","gift_message":"a"*200}
{"quantity":99,"unit_price":10000,"shipping_zip":"99999","discount_code":"abcdefghijkl","gift_message":""}
...
The file lives at testdata/checkout_boundary.jsonl and is referenced by the API test suite:
import jsonlines, requests, pytest
@pytest.mark.parametrize("row", jsonlines.open("testdata/checkout_boundary.jsonl"))
def test_checkout_boundary(row):
resp = requests.post("/api/checkout", json=row)
assert resp.status_code in (200, 400) # 400 only for known invalid combos
8. Version control & CI
- Commit
checkout_boundary.jsonlwith the story ticketCHK‑1234. - In GitHub Actions, a job
boundary-testsruns the parametrised test on every PR. - A nightly job regenerates the file from the source‑of‑truth spreadsheet (see Tool Considerations) and fails if the diff is non‑empty, forcing a review.
Tool Considerations
| Category | Typical tools | When to use |
|---|---|---|
| Spreadsheet‑driven | Excel / Google Sheets + custom script | Small number of fields, business analysts own the ranges. |
| Data‑factory libraries | Factory Boy (Python), Faker.js (JS), Go‑fakeit | Unit / integration tests where you need programmatic variation. |
| Domain‑specific generators | QA3 free test data generator – /tools/test-data-generator | Quick, zero‑setup generation of boundary sets for REST APIs, CSV, SQL, JSON. |
| Model‑based test tools | SpecFlow + SpecFlow+Excel, TOSCA, ModelJUnit | When you have a formal model (state machine, BPMN) and want exhaustive cross‑field combos. |
| Database seeding | Flyway / Liquibase + SQL scripts | End‑to‑end tests that require persisted data. |
Practical tip: Keep the source of truth in a single place (e.g., a markdown table in the repo). All generators—whether a spreadsheet macro or the QA3 generator—read that source. This avoids drift between the documented requirements and the actual test data.
Using the QA3 Test Data Generator
- Paste the equivalence‑partition table (Markdown or CSV) into the UI.
- Choose “Boundary Value” mode.
- Select coverage level per column (high/medium/low).
- Export as JSONL, CSV, or SQL INSERT.
The generator respects inclusivity flags and can apply simple cross‑field filters (e.g., quantity * unit_price <= 2147483647). It does not replace a full model‑based engine, but for most micro‑service APIs it produces a ready‑to‑commit data set in seconds.
Validation Checks Before You Run
| Check | How to automate | Failure action |
|---|---|---|
| Schema conformity | JSON Schema / OpenAPI validator on each row | Block CI, raise ticket |
| Range compliance | Simple script asserting each value ∈ declared partition | Block CI |
| Cross‑field rule satisfaction | Custom predicate per rule (e.g., start <= end) | Block CI |
| Duplicate detection | sort -u on primary key fields | Warn, deduplicate |
| Size budget | Count rows; fail if > 10 000 (adjust per pipeline) | Trim or split |
| Determinism | Hash of generated file compared to committed hash | Fail if drift |
Add these as a pre‑test stage in your pipeline. They cost milliseconds and catch generator regressions early.
Common Pitfalls
| Pitfall | Symptom | Remedy |
|---|---|---|
| Assuming inclusivity | Tests pass but production rejects valid max value | Explicitly document inclusivity; add a “boundary‑confirmation” checkbox in the requirement template. |
| Ignoring type limits | Overflow crashes only appear on 32‑bit builds | Add INT_MAX, LONG_MAX, DATE_MAX as explicit invalid partitions. |
| Over‑generating | CI runtime > 30 min, flaky due to resource exhaustion | Apply the coverage‑level table; prune low‑risk fields to a single representative value. |
| Static data sets | New requirement adds a field; old data set no longer covers it | Regenerate on every requirement change (automated via the generator). |
| Missing cross‑field constraints | Invalid combos (e.g., start‑date > end‑date) slip through | Encode constraints in the generator or filter step; unit‑test the filter itself. |
| Treating all fields equally | Wasteful coverage on cosmetic fields | Use the decision‑criteria table; revisit quarterly. |
| No versioning | Team unsure which data set matches which spec | Store data files in the same repo as the spec; tag with requirement IDs. |
Checklist – Ready‑to‑Run Boundary Data
- Requirements captured with explicit inclusive/exclusive bounds.
- Equivalence partitions listed for every input (valid + invalid).
- Coverage level assigned per field using the decision‑criteria table.
- Boundary value list generated (six‑value for high, three‑value for medium, one‑value for low).
- Cross‑field constraints expressed as code or generator filters.
- Data artefact produced in the format consumed by the test harness.
- Schema, range, constraint, duplicate, size, and determinism checks pass in CI.
- Artefact committed alongside the requirement version (e.g.,
CHK‑1234). - CI job runs the parametrised boundary tests on every PR.
- Nightly regeneration job compares generated output to committed file; fails on drift.
Next Steps
- Pick a single high‑risk feature (e.g., payment amount validation) and run the workflow end‑to‑end this sprint.
- Commit the generated boundary file and add the pre‑test validation stage to your pipeline.
- Measure the defect escape rate for off‑by‑one bugs over the next two releases; adjust coverage levels based on the data.
- Automate regeneration by wiring the QA3 free test data generator at
/tools/test-data-generatorinto your nightly job, so the data set always reflects the current with the latest requirements.
Start small, prove the cycle, then expand to the rest of the API surface. The payoff is a test suite that actually exercises the decision points where bugs live—without exploding your test‑run time.
Read more
How to Measure Test Data Quality Before a Test Run
A step-by-step guide for “How to Measure Test Data Quality Before a Test Run,” covering prerequisites, implementation choices, validation, and common failure modes.
Test Data Generation Mistakes That Make Tests Flaky
A practical risk review of “Test Data Generation Mistakes That Make Tests Flaky,” with warning signs, safeguards, and fixes for real QA workflows.
Test Data Factories vs Fixtures vs Seed Scripts
A buyer-focused guide to “Test Data Factories vs Fixtures vs Seed Scripts,” with concrete selection criteria, trade-offs, and an evaluation path QA teams can use.