How to Generate Realistic Test Data for QA
Realistic fixtures find real bugs: build test data with real-world distributions, boundary cases, and synthetic records that stay fake.
The single most common test-environment failure is data that is too clean.
A signup form works fine with test@test.com - and then breaks
on the first real user whose email is j.smith+work@sub.example.co.uk.
Realistic test data is not a cosmetic preference; it is how edge cases get
exercised before production does it for you.
Identifiers: make them look real
Every system needs unique IDs, and hand-typed ones are a trap: developers
tend to reuse 1234 or test-id everywhere, which
silently defeats any uniqueness check in your code. The
UUID Generator produces
proper v4 UUIDs in bulk - generate a hundred at once and paste them into
your seed script. If your stack stores stripped (no-dash) UUIDs, the
generator has an option for that too, because converting them by hand is
exactly the kind of error you are trying to catch. When a UUID format
question comes up in review, the current rules live in
RFC 9562, the IETF UUID specification.
People-shaped data
For names, emails, addresses, and company details, the Fake Data Generator produces values that look like real people filled them in. Use it for demo environments, UI screenshots, and load-test payloads where you need volume without real personal data - and pair it with the Credit Card Generator for payment-shaped payloads and the User Agent Generator for realistic device strings in header-heavy tests. One honest caveat: generated data is plausible, not real. Never use it to validate identity flows, and never treat it as production-like for anything regulated.
Match the distribution, not just the shape
A generator that picks uniformly at random from a pool of ten thousand surnames produces data that is the right shape and the wrong distribution - and distribution is where the bugs are. Real name data clusters hard: a few hundred surnames cover a large share of the population, which means real batches contain repeats, and repeat-heavy batches are what expose duplicate detection, merge paths, and "already exists" screens.
| Real-world evidence | The numbers | What a fixture should copy |
|---|---|---|
| Most common US surnames | Smith 2,442,977 · Johnson 1,932,812 · Williams 1,625,252 · Brown 1,437,026 · Jones 1,425,470 (2010 Census) | Repeats. A fixture of a hundred unique surnames can never exercise a duplicate-detection screen or an account-merge path. |
| Size of the surname tail | 162,253 surnames occur 100 or more times in the 2010 Census | A thin but real tail. Include both a four-letter name and a long hyphenated or apostrophe-bearing one in the same fixture. |
| Most popular baby names | Liam, Noah, Oliver, Theodore, Henry (boys); Olivia, Charlotte, Emma, Amelia, Sophia (girls) - Social Security Administration, 2025 | Clustering. A real signup batch contains several Olivias; a uniformly generated batch contains none. |
Both datasets are free and downloadable, which makes them a good seed for your own fixture generator: take a real frequency list, keep the top few hundred entries, and weight your sampler by the counts. If you need volume rather than authenticity, the Lorem Ipsum Generator covers layout work - but do not use placeholder text where a field has validation rules to test.
Never build fixtures from production data
The fastest way to get realistic data is to copy a table out of production. It is also the way teams accidentally create a second, unmanaged copy of personal data with none of the controls the original had. The rules that apply to that copy are worth knowing before someone suggests it in a stand-up.
| Source | What it says | What it means for a fixture |
|---|---|---|
| UK GDPR Article 5(1)(c), data minimisation (ICO guidance) | Personal data must be "adequate, relevant and limited to what is necessary" for the purpose it is processed for. Breaches of the basic principles sit in the highest fine tier - up to £17.5 million or 4% of worldwide annual turnover. | A production copy taken "just in case" is not limited to what a single test needs. Generate the minimum fields the test exercises instead of restoring the whole schema. |
| HIPAA Privacy Rule de-identification (HHS), 45 CFR §164.514(b) | Safe Harbor requires removing 18 listed identifier categories - names, all date elements except the year, contact details, account and device identifiers, IP addresses, full-face photographs, and "any other unique identifying number, characteristic, or code". | Deleting the name column is not de-identification. ZIP codes, dates, and device IDs re-identify on their own, so a "masked" copy can still be personal data. |
| NIST SP 800-188, De-Identifying Government Datasets (September 2023) | De-identification techniques group into three families - removing identifiers, transforming quasi-identifiers, and generating synthetic data from models - and the guide warns that "not all tools that merely mask personal information provide sufficient functionality for performing de-identification". | Know which family you used. Masking is a transform, not a guarantee; synthetic records are the only option that removes the link altogether. |
The practical rule most QA teams converge on is production-shaped, never production-sourced: keep the volumes, the column widths, and the awkward values, and generate the values themselves. When a real row is genuinely needed for a reproduction, copy as little as you can and run it through the Data Masker first - it anonymises PII fields in JSON and CSV in the browser, so the copy never leaves your machine.
A fixture sanity check that fails when the data is too clean
Fixtures drift toward cleanliness because clean data generates clean screenshots. A five-line assertion catches that: sample the fixture the way your seeder will, then assert it can actually produce the awkward cases.
import json, random
people = json.load(open("fixtures/surnames.json")) # [{"surname": "Smith", "weight": 2442977}, ...]
surnames = [p["surname"] for p in people]
weights = [p["weight"] for p in people]
batch = random.choices(surnames, weights=weights, k=500)
assert len(set(batch)) < len(batch), "no duplicates: uniqueness checks never fire"
assert min(map(len, batch)) <= 4, "no short names: minimum-length validation untested"
assert max(map(len, batch)) >= 12, "no long names: truncation and wrapping untested"
assert any(" " in s or "-" in s or "'" in s for s in batch), "no punctuation cases"
json.dump(batch, open("fixtures/batch.json", "w")) Run it in CI next to the fixture file. It is not a test of your product; it is a test of your test data, and it fails the moment somebody regenerates the fixture from a neat, unique list.
Boundary and shape checklist
Distribution gets you close to production; boundaries are what actually break forms. For every text field in the fixture, plan one row per case:
- Length extremes - one character, exactly the maximum, and one character over it (which should be rejected).
- Alphabet - accents, apostrophes, hyphens, and an emoji or CJK character in any field that claims to accept "text".
- Case - all caps, all lowercase, and mixed, including two records that differ only by case.
- Whitespace - leading and trailing spaces, doubled spaces, and a newline inside a single-line field.
- Emptiness -
null, an empty string, a whitespace-only string, and the literal textN/A. - Volume - a batch of one, and the batch size that trips pagination or a rate limit.
- Time - leap day, the last day of a month, a DST transition, and a timestamp in the future.
The published limits are wider than your validator
Boundary cases get missed because validators are written from intuition, not from the specification everyone else implements. Three fields where the gap is wide enough to break a form:
| Field | What the standard allows | The fixture row it demands |
|---|---|---|
| Email address | RFC 5321, section 4.5.3.1 caps the local part at 64 octets and the domain at 255, and caps the whole path - brackets included - at 256 octets. That is where the familiar 254-character address ceiling comes from. |
One address with a 64-character local part, one carrying a
+tag, and one past 250 characters. A
varchar(100) column passes every short test and
truncates two of them.
|
| Password | NIST SP 800-63B, Password Verifiers, section 3.1.1.2 sets a 15-character minimum for single-factor passwords, says verifiers should accept at least 64 characters and every printing ASCII character, spaces included, and rules out composition rules and forced rotation. | A 64-character passphrase containing spaces, plus a long all-lowercase password. If either is rejected, the validator failed - not the user. |
| Recipients per message | RFC 5321 section 4.5.3.1.8 requires a server to buffer at least 100 recipients - rejecting a message with fewer than 100 is itself a specification violation. | A send-to-100 fixture, then a send-to-101. Notification and digest features are where this one surfaces. |
The general rule: where a field has a standard behind it, the standard is where its boundary values come from - and every one of them belongs in the fixture library.
Placeholder content with a purpose
Lorem Ipsum is fine for layout checks, but content-heavy features need something closer to the real thing. Generate realistic paragraph counts and lengths, and vary them: a bio field that renders fine with one sentence may break with four hundred words. Test the extremes as well as the average.
Keeping fake data out of real systems
Test data has a habit of leaking into production-shaped environments. Two habits help. First, make test data visibly fake where it matters - the Fake Data Generator's personas are ideal because they are clearly generated, which is a feature when someone later finds them in a log. Second, keep generated data in its own schema or prefix so a query can always tell the two apart.
Build a fixture library
The end goal is a small, versioned folder of fixtures: valid rows, invalid rows, boundary cases, and empty states - each one named for the behaviour it exercises, so a failing test tells you which case broke. Generate the initial batch with these tools, then curate it by hand: a fixture library that is 80% generated and 20% hand-crafted edge cases is the sweet spot. It makes every future test faster to write and every demo more believable.
Related reading
- How to Test JSON APIs Without Writing Code - API response samples make excellent starting points for a fixture library, and that guide shows how to collect them cleanly.
- Regex Testing for Beginners: A Practical Guide - once fixtures exist, patterns will need to verify them; that guide covers testing patterns against realistic input without surprises.
- Bug Report Template with Examples - the evidence section of a good bug report is where a masked sample row and an environment string end up.