The approach taken#
Most test-data tools ask you to describe your data before they will produce any: a configuration file, a schema definition, a set of rules about which column means what.
Look at that request directly and it is strange. The database already contains a complete, authoritative, machine-readable description of its own structure. Tables, columns, types, nullability, primary keys, and the foreign-key relationships between them all sit in the catalog, maintained by the database itself and guaranteed to be current, because the engine enforces it. Asking a developer to write a second description is asking them to duplicate something that already exists, by hand, and then keep the copy synchronised forever. It drifts within the first week.
Test Data Workbench inverts the order of operations. The schema is treated as the specification, because it already is one.
What that looks like in practice#
Reflection rather than declaration#
The tool connects to the database and reads its catalog: what tables exist, what columns they hold, which are nullable, which are keys, and how they reference one another. This is a standard capability, not a trick, and it means the starting point is always accurate and always current.
Inference over naming and structure#
Structure alone does not say what a column means. A VARCHAR(255) could
hold an email address, a product name, or a postcode, and the generator that
produces a plausible value differs in each case. The tool classifies tables
and columns by name and shape, so a column called email_address gets
addresses and one called created_at gets timestamps.
This is heuristic, and heuristics are sometimes wrong. That admission is not a footnote; it drives the next decision.
Code as the output#
The tool emits generator source code rather than rows from behind an API. This is the design decision everything else rests on.
Because the output is ordinary readable Python, you can read it, diff it, review it, edit it, and commit it alongside the tests it feeds. When the inference guesses wrong, and on an unfamiliar schema it eventually will, the correction is one obvious line of Python rather than an argument with a configuration language about how to express an exception. The tool’s judgment becomes a first draft you own, not a black box you negotiate with.
Ordering derived, not guessed#
Because foreign keys form a directed graph, generation order comes from a topological sort of that graph rather than from anyone’s memory of which tables depend on which. Parents are generated before children, so what comes out satisfies the constraints the database will actually enforce.
Privacy by construction#
No real data is read to produce values. The schema supplies structure and the generators supply plausible values, so there is no anonymisation step to get wrong, because there was never anything personal in the pipeline. For the stricter case, a metadata-only mode issues no queries against table contents at all. Privacy and Data Access covers the guarantee and its limits.
The three ideas underneath#
Examined in full in Design Principles:
Generated code is the product. Source you own, not rows behind an API.
Degrade, never fail. Every generation path has a fallback ladder, so an unrecognised table produces a plainer generator rather than a stack trace.
Determinism on demand. A seed makes a run reproducible byte for byte, so a fixture that fails in CI fails identically on a developer machine.
Architecture follows the pipeline end to end. Scope and boundaries sets out where this approach stops being the right one.