A developer needs a few thousand customers to test a new report. The quickest source is the production database, and that is exactly the problem: real names, e-mail addresses and purchase histories end up on laptops, in CI logs and in screenshots attached to tickets. Generated data avoids all of that, but only if it is realistic enough to exercise the code. This article shows how to build such a dataset, with a real example from our random data generator, and the pitfalls that make fake data less useful than it should be.
Why not "just anonymise" production
Removing the name column does not make a dataset anonymous. Combinations of ordinary fields identify people: Latanya Sweeney's well-known study from 2000 estimated that about 87% of the US population could be uniquely identified by ZIP code, gender and date of birth alone. A table of "anonymised" orders with a postcode, a birthday and a delivery time can often be matched back to individuals by anyone with a second dataset.
Personal data copied into test systems also usually falls under the same legal obligations as the original, while being protected far less carefully. Data that was never about a real person has none of those problems.
A worked example
Here is a seven-column schema: a row number, a full name, an e-mail, a plan drawn from a weighted list (free|70, pro|25, team|5), a balance between 0 and 500 with two decimals and 20% of cells left empty, a sign-up date in 2025, and a private IPv4 address. With the seed demo-2026, the first five rows as CSV are:
id,name,email,plan,balance,signup,ip
1,Nadia Pham,[email protected],free,165.05,2025-08-27,10.112.167.30
2,Leila Ivanov,[email protected],free,67.37,2025-03-29,192.168.228.38
3,Åsa Petrov,[email protected],free,12.61,2025-05-05,172.30.59.194
4,Chen Ivanov,[email protected],free,473.97,2025-12-24,192.168.207.39
5,Quang Wang,[email protected],free,285.30,2025-11-24,172.28.28.54A few things are deliberate:
- E-mail domains are `example.com`, `example.net` and `example.org`. These are reserved for documentation by RFC 2606 and can never belong to a real mailbox, so a test that accidentally sends mail cannot reach anyone.
- IP addresses come from the private ranges of RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) when you ask for them, so they cannot point at a real server on the internet.
- Names are combined at random from short lists of first and last names common in many countries. A match with a real person is a coincidence, not a leak.
- The same seed gives the same rows. Generating again with
demo-2026produced an identical file, which makes a failing test reproducible: put the seed in the bug report.
Note one honest limitation: each column is generated independently, so Nadia Pham's e-mail is jose.gonzalez84. If your code checks that an e-mail matches a name, generate the e-mail from the name in your own fixture code.
Do the distributions look right?
The weights are relative, not exact counts. Over 10,000 rows the plan column came out as 7,043 free, 2,488 pro and 469 team, against the expected 7,000, 2,500 and 500. The balance column had 2,002 empty cells, against 2,000 expected for 20%. That is normal sampling noise and it is useful: a report that only works when a category has exactly 5% of rows is a fragile report.
Realistic proportions matter for performance testing too. A query plan that is fine with 50 "team" customers may change completely when an index meets 7,000 "free" ones.
The cases that find bugs
Pleasant data finds few bugs. Make sure your dataset also includes:
- Empty values. The 20% empty balances become
nullin JSON,NULLin SQL and an empty field in CSV, which is exactly where code that assumes a number breaks. - Names outside ASCII.
Åsa,HươngandNguyễntest your database collation, your CSV export's encoding and your PDF fonts. Our e-mail column folds them to ASCII (Josébecomesjose, as in row 1) because the local part of an address usually is; your name column should not. - Quotes and backslashes. A surname like
O'Brienmust survive SQL insertion. The generator's SQL output doubles the quote ('O''Brien'), additionally doubles backslashes for MySQL ('C:\\temp'), and writesN'Nguyễn'for SQL Server so that Unicode text is stored correctly. - Boundaries. The first and last day of the date range, zero balances, the maximum value. Set the ranges to include them.
- Volume. Ten rows hide pagination bugs and slow queries. Generate thousands.
Locale pitfalls
- Decimal separators. The generator writes
165.05with a dot, as CSV and JSON expect. A spreadsheet set to a Vietnamese or German locale may read it as text, or even as a date. Import it with the right locale rather than changing the data; converting CSV to JSON without breaking data covers the traps. - Dates.
2025-03-29is ISO 8601 and unambiguous. If your UI shows29/03/2025, test that conversion separately, including the days above 12 that break day-month swaps. - Name order. In Vietnamese, the family name comes first: in "Nguyễn Văn An" the family name is Nguyễn and the given name is An. A field called
first_namemeans different things in different countries, which is a design problem no generator can solve for you.
Common mistakes
- Copying "just one table" from production into a test database.
- Using real-looking domains such as
gmail.comin fake e-mail addresses. Use the reserved example domains. - Fake phone numbers that are real. Many countries reserve ranges for fiction and testing; use those, or leave phones out.
- Random data with no seed, so a failure cannot be reproduced.
- Only happy-path data: no nulls, no accents, no quotes.
Build your schema in the random data generator, export CSV, JSON or SQL, and use the CSV and JSON converter if you need to reshape the result. Everything is generated in your browser.