Cette page est disponible en anglais uniquement. La navigation reste dans votre langue.

Testing & QA · 6 min read

Synthetic Test Data in 2026: Seeded, Safe, Reproducible

Synthetic test data done properly: seeded generation, reserved ranges, referential integrity, volume, and the identifiers a generator should refuse to invent.

By Charlie Simmon ·

On this page
  1. What actually counts as synthetic data
  2. Seeded generation versus committed fixture files
  3. Reserved ranges that cannot collide with reality
  4. Referential integrity across generated entities
  5. What this site refuses to generate
  6. Volume, determinism, and performance
  7. Keeping synthetic data out of real pipelines
  8. A pre-use review checklist
  9. Questions

Synthetic test data is data that was manufactured rather than collected. It describes nobody, it can be regenerated on demand, and it never has to be masked, purged, or subpoenaed. For teams that test address forms, checkout flows, or import pipelines, it is the only kind of data that can be used freely in CI, screenshots, and demos.

The catch is that synthetic data is only useful when it is both plausible and safe. Implausible data misses the bugs. Unsafe data creates them. This guide covers the middle path: seeded generation for reproducibility, reserved value ranges that cannot collide with reality, referential integrity across entities, and the specific identifiers a generator should refuse to produce.

What actually counts as synthetic data

Synthetic data is generated from rules, not sampled from people. A name assembled from a name list, an address assembled from a country fixture, a phone number from a reserved block, an email on a reserved domain: each is manufactured and none refers to a real subject. Reproducibility is what separates it from random noise, because unseeded output cannot be reviewed, diffed, or replayed.

Anonymized production data is not synthetic. Masking a real row leaves the same distribution and often the same re-identification risk, and a masked EMAIL column that keeps the domain still leaks the employer. If a test needs production-shaped data, that is a separate conversation about aggregation and access, not something a generator can honestly provide.

  • Generated from rules, not sampled from people
  • Reproducible from a seed
  • Reviewable: a diff tells you what changed
  • Never derived from a production row

Seeded generation versus committed fixture files

A committed fixture file has one advantage: it is easy to inspect. It has several problems. It drifts, because nobody updates it when the schema changes. It bloats the repository. It gets copied between projects until nobody knows which version is current. And it quietly becomes the only data anyone tests against, so the suite covers exactly the values someone typed in 2021.

Seeded generation keeps the input tiny and the output large. The seed plus the generator version is the fixture. Regenerate on demand, snapshot only the first and last record for review, and treat any change in generator output as a deliberate change that requires a re-baseline. The one requirement is that the generator must be pinned, because an upgraded generator with a changed algorithm is a new data set even at the same seed.

ApproachRepo costReproducibleDrift risk
Committed JSON fixturesHighYes, but frozenRises with age
Seeded generator at test timeLowYes, with a pinned versionLow
Unseeded random dataLowNoHigh, and flaky
Masked production dumpHighNoHigh, plus privacy risk

Reserved ranges that cannot collide with reality

Several standards reserve values for documentation and testing. Using them is not a style preference; it is the difference between a fixture that is guaranteed harmless and one that might belong to someone. The reserved blocks are stable, widely documented, and accepted by most validators as syntactically normal.

For email, use a domain under .test, such as example.test, which is reserved and cannot be registered. For phone numbers in the North American Numbering Plan, the 555-0100 through 555-0199 block is reserved for fictional use. For IP addresses, the documentation ranges are 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 in IPv4 and 2001:db8::/32 in IPv6. For MAC addresses, the documentation range is 00-00-5E-00-53-00 through 00-00-5E-00-53-FF, and locally administered addresses with the U/L bit set are also safe for fixtures.

Value typeReserved for testingAuthority
Email domainexample.test, example.com, example.orgRFC 2606 and RFC 6761
Web domain.test, .invalid, .localhostRFC 2606
US and Canada phoneNANP 555-0100 to 555-0199NANP fiction range
IPv4 documentation192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24RFC 5737
IPv6 documentation2001:db8::/32RFC 3849
MAC documentation00-00-5E-00-53-xxRFC 7042
Locally administered MACFirst octet 0x02, I/G bit clearIEEE 802 U/L bit

Referential integrity across generated entities

Most systems need more than one entity. An order has a customer, a shipping address, and a billing address, and the three must agree in ways the schema enforces. If the billing address is in the United States and the shipping address is in Japan, the tax calculation and the carrier selection have to accept that combination, or the test fails for a reason unrelated to the behaviour under test.

Generate relationships, not rows. Derive child seeds from the parent seed so the order and its addresses are reproducible as a unit. Decide deliberately which fields must agree: country between shipping and billing is usually the same, postal code must match the city in the fixture, currency must match the country. Encode those constraints in the factory so a broken fixture fails at generation time rather than in an assertion three layers down.

  • Derive child seeds from the parent seed, not from the clock
  • Keep country consistent across billing, shipping, and tax
  • Keep postal code consistent with the city fixture
  • Keep currency consistent with the country
  • Fail at generation time when a constraint is violated

What this site refuses to generate

The site does not generate Social Security numbers, payment card numbers, IMEI or IMSI values, Bluetooth device addresses, or serial numbers. Each refusal has a reason. SSNs are legally restricted identifiers in the United States. Card numbers carry a Luhn checksum, so a plausible one can pass a naive validator and then be pasted into a support ticket. IMEIs have a check digit and an allocation registry. Bluetooth addresses share the MAC format but carry no vendor meaning, and a generated one invites exactly the confusion the MAC tools are designed to avoid.

The same reasoning applies to any identifier with a checksum or a registry. If a test genuinely needs one, use the officially published test values from the processor or the issuing authority, not a generator. That keeps the value obviously fake to anyone who recognizes the test pattern, and it keeps your screenshot out of a compliance review.

IdentifierGenerated hereCorrect alternative
Social Security numberNoNever use a real or realistic SSN
Payment card numberNoProcessor test cards for the sandbox
IMEI or IMSINoVendor test values, if one exists
Bluetooth addressNoNot needed for address form tests
Device serial numberNoAn opaque fixture id you control
Email addressYes, example.test onlySafe, non-resolving domain
Phone numberYes, NANP 555-0100 to 555-0199Reserved fiction range

Volume, determinism, and performance

Generating a hundred records at test time is cheap. Generating ten million is not, and doing it inside a test setup will dominate the runtime. Precompute large volumes once into an isolated store, with the seed recorded in the manifest, and keep the generator out of the hot path. For medium volumes, generate in a single batch rather than per row, because per-row setup is usually the actual cost.

Determinism has a performance dimension too. Parallel workers must not share a single sequential generator, because the interleaving changes which worker gets which record. Give each worker its own seed derived from the run seed and the worker index, so the dataset is stable regardless of scheduling. That also makes a failure on worker three reproducible without rerunning the whole suite.

  • Small volumes: generate inline, seeded
  • Large volumes: precompute to an isolated store with a manifest
  • Parallel workers: derive a seed per worker index
  • Never share a single generator instance across threads
  • Record the generator version with the seed

Keeping synthetic data out of real pipelines

The hazard of good synthetic data is that it looks real. Put a marker on every generated record: a fixture flag, a dedicated tenant, an email on example.test, a phone in the 555-01xx range. Block those records from sending mail, capturing payments, or entering analytics. A demo customer that reaches a production CRM is a support incident waiting for a salesperson to notice.

Also keep generated data out of logs that are retained for compliance, and out of screenshots that will be published. Even though the values describe nobody, a reader cannot tell that from the image, and explaining it later is more expensive than labeling it now. If a screenshot must show a realistic address, use a documentation-range value and add a visible "sample data" caption.

  • Tag every generated row with a fixture flag or tenant
  • Block fixture tenants from email, payments, and analytics
  • Use example.test and 555-01xx, never a plausible real domain
  • Caption screenshots that show generated values
  • Purge fixtures from shared staging on a schedule

A pre-use review checklist

Before a generated dataset is used in a shared environment, four questions need answers. Where did it come from, and can it be regenerated? Is every value in a reserved or locally administered range? Does it satisfy the schema constraints and the referential rules? And is it labeled so that a human reader cannot mistake it for a customer?

If any answer is no, the dataset is not ready. This is deliberately strict, because the cost of a wrong answer is asymmetric: regenerating takes minutes, while a synthetic customer in a revenue report can distort decisions for a quarter. Run the checklist as part of the review, not as a comment in a wiki.

QuestionPass conditionEvidence
OriginSeed and generator version recordedManifest file
SafetyAll values in reserved or local rangesValidator run on the dataset
IntegritySchema and cross-entity rules satisfiedFactory tests green
LabelingFixture flag on every rowQuery returns zero unlabeled rows

Questions

Is synthetic test data safe to commit?+
Yes, when every value is synthetic and reserved, for example example.test emails and NANP 555-01xx phones. Prefer regenerating from a seed over committing large datasets.
Why does the generator refuse to produce card or SSN values?+
Those identifiers are regulated or checksummed, and a realistic fake can pass naive validation and end up in a screenshot or a ticket. Use official test values from the processor or authority instead.
How do I keep parallel tests reproducible?+
Derive a seed per worker from the run seed and the worker index. A single shared generator produces a different sequence depending on scheduling.
Is masked production data the same thing?+
No. Masking keeps the original distribution and often the re-identification risk. Synthetic data is generated from rules and describes no real subject.
How many countries does the synthetic address data cover?+
38 countries with five city fixtures each. Hong Kong and the United Arab Emirates return no postal code, because neither has one.

Examples are synthetic. Do not use a generated address for identity checks, account registration, or delivery.

Reference standards

Read next

All guides