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 text N/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