Build1 distinct publisher3 min readPublished
A dev.to crash course splits pipeline testing into code correctness and data correctness. The split earns its keep because a renamed column produces nulls rather than a stack trace, so nothing pages anyone.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Start with the arithmetic on the worst item in that failure list. Swapping `sum` for `mean` in a grouped aggregation replaces each group's total with the total divided by its row count [3]. Revenue arriving about 90 percent smaller therefore implies groups averaging roughly ten rows: mean = sum / n, so 0.1 = 1/n gives n = 10 [1]. A number that plausible is harder to catch than a crash, because a crash arrives with a timestamp and an owner.
The failure modes in the piece are cheap to test precisely because they are small. A rename upstream turns a join into all-null matches instead of raising [2]. A cleaning function meant to drop duplicates takes valid rows with it [5]. Both are properties of a function given a fixed input, which is what the author means by writing deterministic checks against deterministic inputs [1]. The locale-dependent date parser sits on a different axis [4]: it is not the data that changed and not the logic, it is the environment, which is why CI is where it shows up rather than a laptop. The article's day-invariance heuristic routes checks between code tests and data tests [7], and this third bucket is the one that gets misfiled, because a test that passes locally and fails in CI looks like flakiness rather than a finding.
What pytest supplies here is modest and well judged. A test is a function whose name starts with `test_`, in a file starting with `test_` or ending in `_test.py`, with no class boilerplate required [10]. Assertions use the plain `assert` keyword, and pytest rewrites them so the failure output shows what was actually compared, rather than the `self.assertEqual` ceremony of `unittest` [11]. The framework's entire contract with you is a naming convention, which is about as much ceremony as this problem deserves.
The scope boundary the author draws is the load-bearing part: pytest answers whether the code is correct, while dbt test, Great Expectations and Soda answer whether today's data is correct, and a mature platform runs both rather than one [8]. That split transfers on one condition. Your transformation logic has to be callable as a function that takes a frame and returns a frame. If the logic lives inside a long SQL string or a notebook cell that also reads and writes storage, there is nothing for a code test to grip, and the split collapses into the data-test column by default.
Adoption cost is one command. `uv add --dev pytest` writes `pytest>=8.3.4` into a `[dependency-groups]` table in `pyproject.toml`, the standardised layout from PEP 735, kept apart from runtime dependencies so consumers of the package never install it [9]. Tests then run with `uv run pytest`, which executes inside the project's managed virtual environment without a manual activation step [12]. The article promises pandas, Polars and PySpark patterns on top of that base [14], and the honest caveat is that the excerpt here stops before the Spark section, so the cost of standing up a session fixture is not something I can price from it. For pandas and Polars, the work is in-process and the setup is what you see above.
Ranked by verification strength, evidence, and original report placement.
The article is a pytest crash course covering the patterns needed for pandas, Polars and PySpark pipelines, and is entirely about the code-correctness question rather than the data-correctness one.
The dev.to crash course argues most data engineers write pipelines the way most people write shell scripts (run it, eyeball the output, ship it), and that the fix is the habit of writing small deterministic checks against small deterministic inputs, which is what pytest is built for.
Listed failure mode: a column gets renamed upstream and the join silently produces all-null matches instead of erroring.
Listed failure mode: a refactor changes an aggregation from sum to mean and nobody notices until finance asks why revenue looks 90% smaller.
Listed failure mode: a date-parsing function works on the local machine's locale and breaks in the CI environment.
Listed failure mode: a cleaning function that is supposed to drop duplicates accidentally drops valid rows too.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 1, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Your Databricks Pipeline Is A Demo Until Promotion Only Runs One Way1 distinct publisher
build
255 tool schemas, 91K tokens: pricing the two MCP costs nobody budgets1 distinct publisher
build
An AI test suite hit 94% coverage and missed the one branch that mattered1 distinct publisher
build
The IR sets the ceiling: why Interlace swapped a DataFrame for a sqlglot AST1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Verifiable mechanics, unsupported framing
Split the piece in two and the evidence quality splits with it. The test_ prefix, the assert rewriting, the [dependency-groups] block with pytest>=8.3.4, uv run — all shown as code and all falsifiable against pytest and uv's own documentation within minutes. The claims that carry the argument are not evidenced at all: that data engineering lagged software engineering because its code touches clusters and files, and that the four listed failures are the ones tests catch first, rest on the author's experience and are presented without a single incident, repo, or measurement.
Nothing counted
Nobody in this story adopted anything measurable. pytest is called the dominant Python testing framework and no number is attached; no team, pipeline, or repository is described as having put this practice in place, and no before-and-after is reported. A version floor of 8.3.4 in an example dependency group is not usage.
Modest promises, one quiet leap
This is a restrained piece by the standards of tooling advocacy — it names what pytest cannot do, refuses to pit it against dbt test or Great Expectations, and volunteers its own scope limits. The overreach is narrow: the failure list is presented as bugs tests catch before production, when what is actually shown is that such bugs are silent. Whether a suite of small deterministic checks would have caught the renamed column or the sum-to-mean swap is asserted, never demonstrated. The gap is a claim about outcomes standing where a worked example should be.
Tutorial economics, no visible vendor
The pressures here are the ordinary ones of practitioner publishing: an individual author building an audience on a developer platform, with the reputational payoff that comes from a tutorial being useful rather than from a product being bought. The tools recommended for the code half — pytest, uv — cost nothing. Three products with commercial interests are named for the data half, dbt test, Great Expectations and Soda, and the piece states no relationship with any of them, favourable or otherwise; readers get no disclosure either way, which is why this does not score lower.
Trust the commands, weigh the thesis yourself
Middling, and for structural reasons rather than sloppiness. One publisher, one author, no corroboration — but the factual load is low-stakes and self-demonstrating, so a reader can verify the install and run steps faster than they can read the argument for them. What keeps this from scoring higher: the thesis about why data engineering lagged is one person's read, no outcome is measured anywhere, and the dataframe-specific material that would justify the DE framing is cut off before pandas, Polars or PySpark examples arrive.