Data pipelines fail in two ways: the code is wrong (unit-test territory) or the code is fine and the data changed (validation territory). Test generation is among the highest-yield AI uses in data work — but generated tests default to tautologies that assert the code does what the code does.
Tier: Foundation · Workflow area: Testing & data validation
The skill is prompting for behavior-anchored tests and property-based thinking, then auditing that the tests can actually fail.
Skip exhaustive testing for throwaway analysis code; test what will run unattended.
amount can legitimately be negative for refunds — you must say so.Generate unit tests from stated behavior, not from the code alone:
Write pytest tests for build_features(orders, snapshots) below. Behavior
contract (test THIS, not the implementation):
- Output has exactly one row per (customer_id, snapshot_date) input pair.
- Events with event_ts >= snapshot_date never influence any feature.
- Customers with zero history yield NaN features, not 0, and are not dropped.
- Refund rows (amount < 0) reduce spend_30d.
- Column dtypes match this schema: [paste].
Use small hand-built fixture DataFrames (5-10 rows) with hand-computed
expected values in comments. No mocking pandas. Include the temporal
boundary case: one event exactly at snapshot_date.
Supplying the contract is the difference between tests that catch bugs and tests that enshrine them.
Audit the tests for falsifiability. For each generated test ask: what bug would make this fail? Then prove it — mutate the code (flip < to <=, drop the refund handling) and confirm the suite goes red. A test suite you have never seen fail is a decoration. Assistants help mechanically: Flag any test that would pass even if the function returned its input unchanged.
Add property-based tests for the invariants that scale. Beyond fixtures, ask for Hypothesis tests on genuine properties: row-count preservation through non-filtering transforms, idempotence (f(f(x)) == f(x) for cleaners), permutation invariance (input row order must not change aggregates), no-NaN-introduction guarantees. Review the generated strategies — overly narrow strategies (st.integers(1, 10)) test nothing.
Generate the data-validation suite from profile + contract. Feed pb-01's profiling output and let the assistant draft a pandera schema:
From this profiling summary [paste] and these business rules [amount in
paise, >0 except refunds; status in {placed,shipped,cancelled}; order_ts
within last 3 years; customer_id never null; <2% null mrr], generate a
pandera DataFrameSchema with Checks. Distinguish HARD failures (schema,
nulls in keys) from WARN-level drift checks (null-rate ranges, category
sets, value quantiles within historical p1-p99 bands).
The hard/warn split is essential: a new country code should page nobody at 3am; a null primary key should stop the pipeline.
Add distribution-shift sentinels. For model-feeding pipelines: PSI or KS distance of key features vs a stored reference window, positive-class rate bounds, and volume bounds (row count within ±3σ of the day-of-week norm). Assistants generate competent PSI code; verify the binning against the reference sample yourself.
Wire placement and failure behavior. Validation runs at ingestion (raw contract), post-transform (feature contract), and pre-scoring (model input schema). Decide per check: block, quarantine, or log-and-continue — and generate CI hooks plus a runtime entry point so the same schema guards tests and production runs.
Generated tests are only as good as the behavior contract you feed them — and only proven when you have watched each one fail against a mutated implementation.
< vs <= decision.result = f(x); assert result.equals(f(x)) and mock-everything tests are the default failure mode of test generation; the falsifiability audit exists precisely for this.col >= 0 on a column that "should" be positive misses the day it becomes 99% zeros; pair range checks with distribution sentinels.