Development

Why you shouldn't test with real customer data

Copying the production database into staging solves a real problem badly. It also quietly multiplies every place a breach could happen.

· 7 min read

Every development team needs realistic data. Empty forms and three rows of "test test test" hide entire categories of bug, so at some point somebody suggests the obvious shortcut: take a dump of the production database and load it into staging.

It is understandable, it works, and it is one of the most common serious privacy mistakes in software.

What a production copy actually does

A production database is protected by a lot of accumulated work: restricted access, audit logging, encryption at rest, network isolation, formal review of who can query it. Staging and development environments have almost none of that, by design — they exist to be convenient.

Copying real customer records into them does not move the data into a slightly less secure place. It creates a second full copy of your most sensitive asset in an environment where:

That last set is the one people underestimate. Even if the staging database itself is never breached, real personal data leaks steadily outward into tickets, Slack threads, screen recordings and support conversations — none of which are covered by your data retention policy, and none of which anyone thinks to purge.

The email case is worth stating bluntly, because it has happened to well-run teams: a staging environment with real addresses and a misconfigured mail setting has sent genuine customers test notifications, duplicate order confirmations and password resets. It is embarrassing at best and a reportable incident at worst.

The regulatory position

Under GDPR and similar regimes, personal data is personal data wherever it sits. The rules do not have a staging exemption. Two principles bite immediately.

Purpose limitation: data collected to provide a service has not been collected to debug that service. Using it for development is processing for a new purpose, and needs its own legal basis.

Data minimisation: you should hold the minimum personal data needed for the task. Almost no debugging task genuinely requires real names and addresses — it requires data of the right shape.

There is a practical dimension too. If a customer exercises their right to erasure, you must delete their data everywhere. If copies have been scattered across staging databases, developer laptops and month-old snapshots, "everywhere" is a problem you cannot honestly answer. Generated data has no such obligation attached.

Fake data finds bugs that real data hides

The privacy argument is usually enough, but there is a quality argument that is more persuasive to engineers: production data is a poor test set, because it is dominated by the ordinary case.

Your live database is 95% unremarkable records. Real customers who break your layout are rare, which means a production copy exercises the happy path over and over while leaving the edges untested. Deliberately generated data lets you aim directly at the edges:

A generated address that is deliberately awkward tells you more in one test than a thousand tidy real ones. This is the part teams miss when they treat fake data purely as a compliance concession: done properly it is a better test set, not a worse one.

The options, roughly in order of effort

Generate from scratch. Build records from name, street, city and postcode components. Fastest to set up, zero privacy risk, and gives full control over the edge cases you include. Best for most testing, form work, demos and screenshots.

Anonymise a production extract. Take real data and strip or replace the identifying fields. Preserves realistic distributions — the true spread of order values, the real ratio of active to dormant accounts — which matters for performance testing. The catch is that anonymisation is genuinely hard to do properly: re-identification from combinations of "non-identifying" fields is a well-established attack, and partial anonymisation gives false confidence.

Synthesise from production statistics. Measure the distributions in real data, then generate new records that match them without containing any original record. The best of both, and the most work.

For the vast majority of everyday development, the first option is the right one, and the tooling is trivial.

Making generated data useful

Seed your randomness. Random data that changes on every run produces tests that fail intermittently and cannot be reproduced. Use a fixed seed so the same "random" dataset is generated every time; keep unseeded randomness for exploratory testing only.

Make it obviously fake on inspection. If a record escapes into a support ticket or a screenshot, it should be immediately clear it is not a real person. Plausible formatting, implausible specifics.

Keep relationships consistent. A generated order should point at a generated customer who actually exists. Referential integrity is where naive generation falls down and where bugs hide.

Cover the boundaries deliberately. Include the empty case, the single record, the maximum-length field and the duplicate. These are the cases production data will almost never hand you.

One clear line

Generated identities are for testing, demos, documentation and placeholder content. They are not for creating accounts on real services, bypassing identity or address verification, or misrepresenting anyone. Data that is fictional is not thereby harmless — the same realism that makes it a good test fixture makes it a plausible tool for deception, and the distinction is about intent, not the data itself.

Used as intended, generated data does something quietly valuable: it lets a team build and test thoroughly without ever putting real people's details somewhere they were never meant to go. That is a better outcome for the customer and, when the security questionnaire arrives, a much easier answer for you.

Tools mentioned in this guide