Ask a QA team why their suite is flaky and test data comes up within two answers. A test passed yesterday and fails today because somebody changed a seed record. The staging database drifted from production months ago and nobody noticed. A regression suite cannot run in parallel because two tests fight over the same customer account.
These are not testing problems. They are data problems wearing testing clothes, and they do not get fixed by rewriting the tests.
This guide covers the four strategies for supplying test data, how to pick between them, and how to handle the privacy constraint that rules out the easiest option for most regulated teams.
The four ways to get test data, and what each costs
Most teams end up using two or three of these together rather than picking one. The practical pattern is synthetic data for the bulk of functional tests, a masked subset for anything needing realistic shape, and on-the-fly creation for tests that must own their data outright.
Why copying production is harder than it looks
Copying production into a test environment is the default instinct because the data is realistic by definition. Two things make it expensive.
The first is legal. If your data includes personal information, copying it into an environment with weaker access controls is a processing activity you probably cannot justify. GDPR and similar regimes do not contain a testing exemption, and "it is only staging" is not a defence anyone has won with.
The second is size. A production database that takes forty minutes to restore turns every environment refresh into a scheduled event, which means environments drift between refreshes and nobody trusts them.
Masking addresses the first problem but introduces its own. Masking must preserve referential integrity across tables, or your test data becomes internally inconsistent in ways that produce false failures. If you mask a customer identifier in one table you must mask it identically everywhere it appears, including in denormalised columns and audit logs people forget exist.
Sizing the data set to the job
Volume is a separate decision from strategy and it is routinely skipped.
A functional suite needs the smallest data set that exercises the paths, because every extra record slows setup and teardown. A performance suite needs a volume set large enough that query plans behave the way they will in production. A ten row table and a ten million row table produce different execution plans, so a query that looks fast in testing can collapse on release.
Build them separately. Trying to serve both needs from one data set produces a functional suite that is slow and a performance set that is unrealistic.
For the volume set, match the distribution rather than just the row count. A million customers who each have exactly one order will not surface the pagination bug that appears for the customer with four thousand. Generate a realistic spread, including the outliers, because the outliers are where the bugs live.
Making tests own their own data
The most reliable pattern, and the one worth defaulting to for new tests, is that each test creates what it needs and cleans up after itself.
This costs more setup time per test and it is worth it. A test that creates its own customer cannot be broken by another test, cannot fail because a seed record drifted, and can run in parallel with anything else.
Two practical constraints. Creation must go through your API rather than direct database inserts, or your test data will not satisfy the invariants your application assumes. And cleanup must run even when the test fails, which means a fixture or teardown hook rather than a line at the end of the test body.
Where creating data through the API is genuinely too slow, create it once per test run rather than once per test, and give each test a unique identifier prefix so two tests never collide on the same record.
Cleaning up reliably
Cleanup is where test-owned data usually goes wrong. Three rules make it dependable.
Put the cleanup in a fixture or teardown hook, never at the end of the test body, because a failing assertion skips everything after it and leaves the record behind. Make the deletion idempotent, so a cleanup that runs twice does not error. And prefix every generated record with a recognisable marker, so a scheduled sweep can remove anything a crashed run left behind.
The sweep matters more than it sounds. Test runs get cancelled, CI agents get killed mid-job, and over months the orphaned records accumulate until somebody wonders why staging has two hundred thousand customers nobody created deliberately.
The problems that show up in practice
The date problem is the one that catches almost everyone eventually. A fixture created with a hardcoded date works until that date passes, then fails in a way that looks like an application bug. Generate dates relative to the current time, or freeze time explicitly in the test.
Referential integrity when masking
If you mask, the single technical requirement that determines success is deterministic mapping: the same input value must always produce the same output value, everywhere it appears.
Get this wrong and a customer identifier becomes one value in the customers table and a different value in orders, so the relationship breaks and your tests fail for reasons that have nothing to do with the application.
The places people forget are denormalised columns, audit and history tables, cached JSON blobs, and free text fields where somebody's name or email was written into a note. Search for the value across every table before you declare the masking complete, rather than masking the columns you remembered.
Keeping environments honest
Environment drift is the slow failure. Staging starts as a copy of production, then somebody manually fixes a record to unblock a demo, then a migration runs in one place and not the other, and eighteen months later staging tests nothing meaningful.
Two habits prevent it. Refresh on a schedule rather than on request, so drift has a bounded lifetime. And make the refresh automated and fast enough that nobody avoids it, which usually means a subset rather than a full copy.
Track what your test environments actually contain. A documented, versioned definition of the reference data set is what lets you tell whether a failure is a real regression or an environment that has wandered.
Who owns the data
Test data fails most often for organisational reasons rather than technical ones. Nobody owns it, so everybody works around it.
The symptom is recognisable: each engineer has a personal set of records they know work, nobody can run the suite on a fresh environment without a day of setup, and the knowledge of which account has which permissions lives in a private message thread.
Name an owner for the reference data definition. Their job is not to create every record, it is to keep the definition of what an environment should contain accurate and versioned. Without that, environment refreshes become guesswork and drift resumes immediately after each one.
Keep the definition in the repository alongside the tests, not in a wiki page. A wiki page describing your seed data will be wrong within two sprints and nothing will flag it. A file in the repository at least changes through review, and can be validated by a script that checks the environment matches.
Where to start if this is a mess today
Start with the tests that fail most often. Pull the last month of CI results, find the tests with the highest false failure rate, and check how each one gets its data. In most suites a small number of shared fixtures are responsible for a large share of the flakiness.
Convert those specific tests to create their own data. Do not attempt a full strategy rewrite first: the incremental conversion demonstrates the value and gives you the argument for the larger piece of work.
Then handle the privacy question before it is handled for you. If production data is currently sitting in a test environment, that is a live compliance exposure regardless of how useful it is.
Cost of getting it wrong
It is worth naming what poor test data actually costs, because the work to fix it competes for priority against features.
The direct cost is engineer time spent re-running suites, investigating failures that turn out to be data problems, and manually preparing environments before a release. That time is spread across many people in small increments, which is why it rarely appears in any estimate.
The indirect cost is larger and harder to see. A suite that fails intermittently stops being read. Once a team routinely re-runs rather than investigates, the suite no longer prevents anything, and a real regression passes through unnoticed because it looked like the usual noise. At that point you are paying to maintain a suite that provides no protection.
The compliance cost is the one that arrives suddenly. Production personal data sitting in a test environment is an exposure that exists every day until it is removed, and it is generally discovered at the least convenient moment.
Related reading
For adjacent practice, see our guides on test automation frameworks and smoke testing.
If you want help designing a data strategy against your own constraints, our QA consulting team can work through it with you.