SqlProof vs pgTAP
pgTAP is a mature, all-SQL testing framework that’s been around since 2008 — if you write SQL tests today, there’s a fair chance they’re pgTAP. SqlProof and pgTAP solve overlapping problems with very different tools. This page is the honest comparison.
The fundamental difference
Section titled “The fundamental difference”pgTAP is example-based. You write each assertion against a known fixture and check an exact value:
SqlProof is property-based. You describe the invariant and let Hypothesis generate hundreds of valid datasets to try to falsify it:
The pgTAP test passes for (50, 25) and proves nothing about (0.01, 99999.99), negative-zero edge cases, NULL aggregation, or empty
groups. The SqlProof test was given the same intent and runs it against
100 random valid datasets, including ones that hit those edges.
Where SqlProof has the advantage
Section titled “Where SqlProof has the advantage”1. Coverage you can’t write by hand
Section titled “1. Coverage you can’t write by hand”Hypothesis generates valid Postgres values across the entire domain of
each column type — NUMERIC(10, 2) includes 0, -99999999.99,
99999999.99, and many tricky values in between. pgTAP tests cover
exactly the rows you typed.
2. Shrinking on failure
Section titled “2. Shrinking on failure”When a SqlProof property fails, Hypothesis shrinks the failing dataset to the minimal example that still reproduces the bug. The counterexample is saved with a reproducible seed:
pgTAP gives you the assertion that failed against your fixture — to narrow further, you edit the fixture and re-run.
3. Stateful testing for sequence bugs
Section titled “3. Stateful testing for sequence bugs”Some bugs only surface after a sequence of operations — pagination
that drifts after deletes, RLS that allows access only after a role
change, aggregates that accumulate float error. SqlProof’s
SqlProofStateMachine lets you describe rules (insert, delete,
role-change) and invariants (the helper agrees with the model) and
Hypothesis explores valid sequences of those rules. pgTAP can test the
same scenarios but only by hand-rolling sequence fixtures one bug at a
time.
4. Reusability of property logic
Section titled “4. Reusability of property logic”A pgTAP test is a literal sequence of assertions. A SqlProof property
is a Python function — you can parameterize it across schemas, share
helpers, build factories, run it under pytest with the rest of your
suite, and compose properties from smaller properties.
5. Postgres edge-case discovery as a side effect
Section titled “5. Postgres edge-case discovery as a side effect”Because Hypothesis generates broadly, SqlProof tests routinely surface
Postgres-specific quirks the developer hadn’t considered: that
numeric and float8 round differently, that empty arrays and NULL
arrays are distinct, that LIKE '%_%' doesn’t mean what you think.
pgTAP only finds what you remembered to assert.
Where pgTAP has the advantage
Section titled “Where pgTAP has the advantage”1. All-SQL workflow
Section titled “1. All-SQL workflow”pgTAP runs inside Postgres. If your team’s expertise is SQL and you
don’t want a Python toolchain, pgTAP is the right choice. SqlProof
requires Python 3.11+, psycopg, Hypothesis, and (usually) pytest.
2. Schema introspection helpers
Section titled “2. Schema introspection helpers”pgTAP has a deep library of schema-shape assertions:
SqlProof can express each of these as a query against
information_schema, but pgTAP has decade-aged ergonomics.
3. Native plpgsql introspection
Section titled “3. Native plpgsql introspection”pgTAP can directly assert that a function RAISE EXCEPTIONs with a
specific message, that a particular NOTICE was emitted, or that the
function’s stored attributes (volatility, SECURITY DEFINER flag,
owner) match expected values. SqlProof can verify the behavior of
the function from outside, but not its plpgsql-internal control flow
with the same fidelity.
However, for the far more common case of testing what a function returns for given inputs — which is what most function tests actually do — SqlProof’s property-based approach is dramatically better. See the focused walkthrough: Testing SQL Functions — pgTAP vs SqlProof. That page works through a realistic pricing function and shows the combinatorial gap clearly.
4. Tight feedback loop in psql
Section titled “4. Tight feedback loop in psql”Every Postgres developer can run psql -f tests/foo.test.sql and read
the TAP output. SqlProof needs pytest (or its API) and a Python
environment. For a small SQL-only project, that ergonomic gap matters.
Recommended stance
Section titled “Recommended stance”These tools are complementary, not substitutes.
Use pgTAP for:
- Schema-shape assertions (“does this column exist with this type”)
- Tight-loop SQL function testing where you want to inspect plpgsql control flow and EXCEPTION behavior
- Migration smoke tests in SQL-only projects
Use SqlProof for:
- Behavioral invariants that must hold across the whole input domain (aggregates, joins, window functions, materialized-view freshness)
- RLS policy verification where you want to explore role/data combinations
- Stateful regression tests that catch sequence-of-mutations bugs
- Migration safety: “the new query produces the same answer as the old, on every dataset”
Many real projects ship both: pgTAP files for shape-and-function unit
tests in supabase/tests/, SqlProof properties for behavioral and
invariant testing in tests/. The repo’s
Ripenn example
takes exactly this approach — pgTAP files are kept outside SqlProof’s
scope (test_pgtap_tests_are_kept_outside_sqlproof_parser_scope),
and the two suites coexist.
The honest summary
Section titled “The honest summary”If you’ve already invested in pgTAP and your tests catch bugs, keep them. SqlProof shines on the bugs pgTAP can’t easily reach: the ones that hide in input domains you can’t enumerate, in sequences of mutations you didn’t think to write, and in aggregate calculations that go subtly wrong on data you didn’t have when you wrote the test.
If you’re starting fresh on a Postgres-heavy project that has Python
in the loop anyway — backend service, data pipeline, anything calling
psycopg — SqlProof tends to find more bugs per line of test code.