Most test data is a lie: three users named "test1", every order for $10.00, every timestamp at midnight UTC. Then production arrives with a name containing an apostrophe, an order at a currency boundary, and a timestamp in IST — and the bug that ships was untestable with your fixtures.
Tier: Foundation · Workflow area: Testing & Quality
AI is excellent at generating data that honors your real schema constraints and deliberately covers the edge distributions, provided you feed it the schema and forbid it from inventing one.
factory_boy, Fishery, FactoryBot) for a new test suite or a module that never had them.User("a", "b@c.com") inline in tests and the duplication is rotting.Skip when a handful of hand-written literals is genuinely clearer — a fixture layer for two tests is ceremony.
factory_boy + Faker, Fishery + faker-js) — all free.Feed the real schema, forbid invention. Paste the DDL or ORM models:
Here are our SQLAlchemy models for User, Subscription, Invoice (below).
Generate factory_boy factories. Rules:
- Respect every constraint you can see: nullability, unique email, CHECK
(amount_minor >= 0), FK from Invoice.subscription_id.
- Do not add fields that are not in the models. Do not guess enum values —
use exactly the ones defined.
- Timestamps: timezone-aware UTC, spread across the last 400 days, never
all-midnight.
- Names/emails via Faker with locale mix en_IN + en_US; include names with
apostrophes, hyphens, and non-ASCII.
Add the business rules the schema cannot express. Follow up: Additional invariants: a Subscription in state 'past_due' must have >=1 unpaid Invoice; trial users have no Invoice rows. Encode these as factory traits/params, not comments. This is the step that separates usable factories from pretty noise.
Request an edge-case catalog, then bake it in. List 15 edge cases production data would contain for these models (boundary amounts, leap-day birthdays, max-length strings, unicode, concurrent-period subscriptions). Emit a pytest fixture edge_users that returns all of them. Review the list — you will usually add two domain-specific ones it missed and cut two irrelevant ones.
Verify against the real database. Insert a generated batch into a scratch DB and let the constraints be the judge: INSERT failures mean the factories drifted from the schema. For JSON-schema'd payloads, validate every generated object with the actual validator (Pydantic/Zod/jsonschema), not by eye.
Wire distribution realism for seed/load data. For volume seeding, ask for a generator script with explicit distributions ("80% of users have 1–3 invoices, 1% have 200+; amounts log-normal around ₹1,200") so aggregate queries and pagination behave like production.
The schema validates structure; only your written business rules validate meaning. Feed both, or the factories will produce rows that insert cleanly and mean nothing.
Faker.seed(42)), so CI failures reproduce.Schemas are usually shareable when table/column names are not client-identifying — but check the NDA; some client schemas are themselves confidential. If so: rename tables/columns to neutral equivalents before prompting, keep a local mapping, and rename back in the generated code. Under DPDP Act 2023, personal data in logs or dumps must never be the seed source — synthetic-only is not just hygiene, it is compliance. Local models via Ollama handle factory generation from pasted DDL well; this is one of the tasks where the frontier gap barely matters.