# Why passing tests was the problem

> We did everything right: defined clear scenarios, built realistic datasets, and kept them under version control. Yet every time we hit production-like data, new bugs surfaced. We could no longer say when we would be done. The lesson: synthetic data at scale, built from real profiles, fixes the most expensive form of unpredictability.

Source: https://www.bluewise.fr/why-passing-tests-was-the-problem  
Author: Marc Morel  
Published: 2026-03-02  
Category: Engineering  
Series: Synthetic Data Factory, part 1

## Our tests were green. That was the problem.

On a previous large project, we did what most teams do. We wrote our test cases by hand. Each scenario described a customer, a product, a journey and the expected outcome. We built small, carefully curated datasets to support them, reviewed them, and ran them on every build.

> They passed. Almost always, and almost immediately.

It took us a while to understand what that green really meant. Our scenarios described the world as we imagined it: tidy customers, common products, journeys that followed the happy path or the handful of exceptions we had thought of. 

> The real world is far more diverse. 

Real customers have long histories, unusual combinations of products, names and addresses our validators had never seen, and states that only years of activity produce. None of that was in our datasets, so none of it was tested.

The consequence was not a single dramatic outage. 
It was something slower and more expensive: **unpredictability**. Every time the system met realistic data, in integration, in pre-production or during a migration rehearsal, new defects surfaced. Each one reopened work we had considered done. Estimates slipped, then slipped again, because the real work only appeared once real-world diversity showed up. We could no longer say with confidence when something would be finished.

The lesson was uncomfortable but clear. A green test was measuring our scenarios, not our system. Hand-written cases cannot keep up with the combinatorial diversity of production, no matter how carefully they are written. We needed data that carries the shape of reality by construction, at the volume reality produces.

## Three ways test data lies

Looking back, our datasets failed in three recurring ways. I have since seen the same pattern in performance campaigns elsewhere.

- **Weak volume and shape.** A few thousand rows cloned a million times. Every customer looks like every other customer, so caches hit far too often and indexes never meet their real cardinality.
- **Unrealistic values.** Ages spread uniformly from 18 to 99, every product category equally popular, every amount a round number. Query plans, partition pruning and hot keys all behave differently than they will in production.
- **Inconsistent states.** An account closed before it was opened. A card issued to a customer who does not exist. A refund larger than the payment it reverses. These records either crash the run or, worse, route traffic through code paths real users never take.

The good news: we already own the best possible specification of realistic data. It is our production system, described not row by row, but statistically.

## Synthetic data as a solution

Commercial platforms such as Tonic.ai solve this problem well. They connect to your databases, learn their structure, and produce de-identified or synthetic copies with referential integrity preserved. If your need is broad, urgent and spread across dozens of schemas, buying is often the right call. The price, however, is significant, and it grows with data sources and usage.

Building has a cost too, and it deserves the same honesty: engineering time to deliver the first critical journey, then a lasting commitment to maintain profiles, rules and dictionaries as the business evolves. Open-source libraries such as SDV lower that cost, but none of them encodes your business rules or your market for you.

An in-house solution is worth it only if it delivers four things a vendor would:

| Requirement | What it means in practice |
| --- | --- |
| Fidelity | Distributions and correlations match production within agreed tolerances |
| Consistency | Every record respects business invariants and lifecycle rules |
| Privacy by construction | No production row ever leaves its perimeter; only vetted aggregates do |
| Scale and repeatability | Hundreds of millions of rows, regenerated identically from a random seed |

If you cannot commit to all four, buy. If you can, the approach below is deliberately small, built from open-source parts, and focused on the domain you know better than any vendor.

## The key insight: let the LLM write the generator, not the rows

The most tempting design is also the worst: prompt an LLM for "10,000 realistic customers" and repeat until you have enough. It fails every requirement above. It is slow and costly per row, it cannot reproduce a target distribution reliably, it drifts between batches, and it cannot be replayed from a random seed.

The design that works inverts the roles. Statistics come from production. Volume comes from a fast, deterministic engine. The LLM works upstream, at design time, and in the feedback loop when something fails. It never produces a single row.

| Task | Who does it | Why |
| --- | --- | --- |
| Measure distributions and correlations | Profiler on production, inside the perimeter | Numbers must be measured, not imagined |
| Produce hundreds of millions of rows | Statistical engine, vectorised and seeded | Speed, cost and exact reproducibility |
| Translate schemas and code into business rules | LLM drafts, engineers review | Reads DDL, enums and validation code faster than any analyst |
| Write lifecycle state machines and invariants | LLM drafts, engineers approve | Turns tribal knowledge into executable, testable specifications |
| Build seed dictionaries (names, companies, merchant labels, free-text templates) | LLM curates once; the engine samples | Realistic language at near-zero marginal cost |
| Explain validation failures and propose fixes | LLM, in the feedback loop | Shortens the iteration from days to minutes |

In short, the LLM is a compiler for domain knowledge. Its output is rules and dictionaries that are versioned, reviewed and then executed millions of times. That is where it creates leverage at scale, and it is exactly what our hand-written scenarios were missing: diversity encoded once, then reproduced everywhere.

## What changes

Understanding the tests limits is the first step. The next is to decide how far you want to go. You can start small, by improving your existing scenarios with the principles above, and then gradually increase the volume and diversity of your test data. You can also start big, by implementing a full test data generation pipeline, with all the features described above. The choice is yours, it depends on your specific needs and resources, it the next article I'll cover the second part: the method to generate the data.
