Most migrations get validated with a row count and some hope. The row count matches, someone says “looks fine,” and the cutover happens. Three months later a balance doesn’t tie and nobody can say whether the old system or the move caused it.
The question to answer is narrower and harder: is this the same data I started with, except for the changes I meant to make?
Why health checks pass on a bad migration
DataKitchen tested this on purpose in New in TestGen: Prove Your Data Migration Worked. We moved the Jaffle Shop demo database, split one table in two, fixed some hygiene problems, and left 483 orders behind. The standard 55-test health suite on the target found zero failures. The data looked healthy. It just wasn’t all there.
One company DataKitchen wrote about found its 6 billion-row database had only partly transferred to the cloud. A row-count test would have caught that one. It wouldn’t catch a trimmed code or a shifted date.
Five pieces a small team needs
| Piece | What it is | In the Jaffle Shop example |
|---|---|---|
| Source snapshot | A dated profile of every source column, kept as the baseline | Six tables profiled before the move, with value hashes |
| Mapping | A file of renamed, moved, or retired tables and columns | An 11-row CSV: raw_orders.tax_paid became order_amounts.tax_amount, and so on |
| Reconciliation | Tests generated from the snapshot, run on the target | 345 Table Comparison tests on counts, lengths, sums, patterns, and hashes |
| Expected differences | The written list of changes you meant to make | 483 orders quarantined, header rows removed, two strings trimmed, 13 columns retyped |
| Acceptance criteria | Every failure traces to the list, and nothing else fails | 61 failures, every one matched to a planned change |
The snapshot comes first for the same reason it does in any handover. Your legacy data has problems, and a dated record of them is what separates what you inherited from what the migration broke.
The mapping is the only part a person writes, and only when names changed. TestGen’s transfer_tests.py reads the CSV, renames every test, and writes a plain-text report of every rename and dropped test so the approver can read it. Rewriting 345 tests by hand is how a migration suite gets its own bugs.
Reconciliation is the step that gets cut when the schedule slips. Validation needs comparison: tests that check completeness, accuracy, and consistency between source and target, not either one alone. Our 345 tests ran in five seconds, so they can run after every mock load instead of once at cutover.
On cost: TestGen open source includes Table Comparison and covers one user, one connection, and one project. Enterprise is $100 per user and $100 per connection per month, per the pricing page.
What the tests can’t decide
The tests find differences. They can’t tell you which ones you meant. Your migration script and the people who know the data decide that, which is why the expected-differences list has to exist before cutover. Without it, 61 failures is a panic. With it, 61 failures is a sign-off sheet. That works the way a data contract works as a receipt: a list you can check.
Know the limits too. Table Comparison is not a row-by-row diff. It profiles the source where it lives, runs queries on the target where it lives, and compares the results, which is why it works across engines such as SQL Server to Snowflake. In DataKitchen’s testing on three public datasets with 785 planted changes, it caught 774. The misses were binary columns, type changes inside the same general type, and values swapped across a composite key. If your migration risks one of those, write a specific test for it.