<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Emeka Adirije</title>
    <description>The latest articles on DEV Community by Emeka Adirije (@chuksadirije).</description>
    <link>https://dev.to/chuksadirije</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F469812%2Fa140f9a6-d455-4e90-bc89-0911e61c0e12.png</url>
      <title>DEV Community: Emeka Adirije</title>
      <link>https://dev.to/chuksadirije</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chuksadirije"/>
    <language>en</language>
    <item>
      <title>Red Once: never trust a test you haven't watched fail</title>
      <dc:creator>Emeka Adirije</dc:creator>
      <pubDate>Tue, 13 Oct 2026 13:53:48 +0000</pubDate>
      <link>https://dev.to/chuksadirije/red-once-never-trust-a-test-you-havent-watched-fail-1g2h</link>
      <guid>https://dev.to/chuksadirije/red-once-never-trust-a-test-you-havent-watched-fail-1g2h</guid>
      <description>&lt;p&gt;My visual regression check reported zero pixels changed across 28 screenshots.&lt;/p&gt;

&lt;p&gt;Every one of those pages had a bright red background injected into it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;28 screenshots compared
0 px changed
PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three separate defects were queuing up behind that zero. The check had been green since the day it was written, which is exactly what made it worthless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing that changed
&lt;/h2&gt;

&lt;p&gt;Test-driven development asks you to write a failing test before the code. Most engineers have never worked that way, and the profession has largely made peace with that. Tests get written after, or alongside, the suite goes green, and life continues.&lt;/p&gt;

&lt;p&gt;That peace held because writing a test by hand carried some verification as a side effect. You had to imagine the failure in order to type the assertion. You pictured the thing breaking, decided what breaking looked like, and wrote that down. Nobody called it a methodology, but it was doing real work.&lt;/p&gt;

&lt;p&gt;An agent generating forty tests from an implementation that already passes does none of that. It reads what the code does and asserts that the code does it. Every assertion is true at the moment it is written, by construction, and none of them has ever distinguished a working system from a broken one.&lt;/p&gt;

&lt;p&gt;The output is a suite that raises coverage while verifying nothing. It is harder to catch than a missing test, because it looks reviewed. Agents produce well-named, well-shaped, arrange-act-assert tests. They look like the work of someone careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the red step was actually for
&lt;/h2&gt;

&lt;p&gt;The ordering in TDD exists to deliver one guarantee: that a human being has personally watched this assertion distinguish broken from working. Writing the test first is simply the most convenient moment to collect that guarantee, because the code does not exist yet and red is free.&lt;/p&gt;

&lt;p&gt;Which means the guarantee is separable from the ordering. You can write the code first, the way nearly everyone and every agent actually works, and still collect it. You just have to pay for it afterwards, deliberately, instead of getting it for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern: Red Once
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;No assertion is trusted until you have watched it fail, for the reason you think it fails.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Performed at the moment a test is authored, not retroactively across an existing suite.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Break the code under test
&lt;/h3&gt;

&lt;p&gt;One change, inside the code under test. Invert a condition. Return a constant. Delete the line that does the work. Rename the column the query selects.&lt;/p&gt;

&lt;p&gt;Editing the test instead proves nothing, because a test can always be made to fail by breaking the test.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Run that one test and read the failure
&lt;/h3&gt;

&lt;p&gt;It has to go red, and the message has to name the thing you broke.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pytest tests/test_balance.py::test_excludes_body_only &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;span class="go"&gt;
FAILED  assert 4200 == 3900
          where 4200 = invoice_total(session)
1 failed in 0.41s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Restore, re-run, and write down what you broke
&lt;/h3&gt;

&lt;p&gt;Green again, and the mutation recorded next to the test. One line is enough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_excludes_body_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# red-once: returned the unfiltered total from
&lt;/span&gt;    &lt;span class="c1"&gt;# invoice_total() -&amp;gt; 4200 != 3900. 2026-10-13.
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;invoice_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3900&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the step everybody skips, and it is the one that turns a private act of diligence into something the next person can repeat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it sits
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbkpo6qys16cxeqyfqox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdbkpo6qys16cxeqyfqox.png" alt="Two pipelines compared. Both run write code, generate tests, suite green, merge and ship. The second inserts a break, red, revert step between generating the tests and the green suite, which is the only difference between them." width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Red for the right reason
&lt;/h2&gt;

&lt;p&gt;A test that fails is not automatically a test that works. It can go red because an import blew up, a fixture collapsed, a migration had not run, a network call timed out, or a mock went stale. All of those produce red, and none of them tells you anything about your assertion.&lt;/p&gt;

&lt;p&gt;So step 2 has two conditions, and the second one is the real one: it failed, &lt;strong&gt;and&lt;/strong&gt; the failure names the thing you broke. If you inverted a filter and the test reports a connection error, you have learned nothing and you are not finished.&lt;/p&gt;

&lt;p&gt;This is the same error that produced the red page and the zero. The check passed for a reason that had nothing to do with the pages matching: the diff library painted unchanged pixels as faded grey, the test read colour channels for any value at all, so nearly every pixel registered as unstable and the mask built to ignore instability grew to cover the entire page. A correct answer arrived by a route that could never have produced a wrong one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What was hiding behind the zero
&lt;/h2&gt;

&lt;p&gt;The harness was wrong three ways at once, and all three were invisible to a green run.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The mask ate the page.&lt;/strong&gt; The diff library paints unchanged pixels as faded greyscale rather than leaving them transparent. The comparison tested colour channels for any value, so almost every pixel counted as changed, and the instability mask covered everything. Reading the alpha channel instead fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A footer that was never photographed.&lt;/strong&gt; Screenshots captured the viewport. One of the footers sat four thousand pixels down the page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two sides on different frames.&lt;/strong&gt; The CSS animations were keyed to page load, so the comparison was never made at the same moment in the animation, and the drift was absorbed as noise.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There was a fourth problem in the threshold itself. It was expressed as a ratio of total pixels, which scales with page height, so the longer the page the more a real regression could hide inside an acceptable percentage. Absolute pixel counts, set just above a measured noise floor, do not have that property.&lt;/p&gt;

&lt;p&gt;All four surfaced within an hour of injecting a regression the check was supposed to catch and refusing to accept green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the agent do it
&lt;/h2&gt;

&lt;p&gt;This is the part that makes the discipline survive contact with how software is now written. An agent that can write a test can also break the code, run the test, and show you the failure. It is a strictly mechanical sequence, which is the kind of work agents are good at.&lt;/p&gt;

&lt;p&gt;Add it to your agent instructions, and require the evidence rather than the assurance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;After writing a test, prove it discriminates:
  1. make one change to the code under test that
     should break it
  2. run that test alone, capture the output
  3. confirm the failure names what you changed
  4. revert, re-run, confirm green
Paste both outputs. Do not describe them.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The captured failure is the artifact. It goes in the pull request next to the diff, and it changes what a reviewer is looking at. A recorded failure is a receipt, and a reviewer can check it in seconds.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The old review question&lt;/th&gt;
&lt;th&gt;The question that survives agents&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Is there a test for this?&lt;/td&gt;
&lt;td&gt;Has this test ever failed?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answerable by looking. Satisfied by anything shaped like a test.&lt;/td&gt;
&lt;td&gt;Answerable only with evidence, and the evidence is cheap to produce and impossible to fake by accident.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What it costs, and where it stops
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;About a minute per test.&lt;/strong&gt; Break, run one test, read, revert, note. One case, start to finish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not retroactive.&lt;/strong&gt; Nobody is going to go back through four thousand existing tests. Do it at authoring time, and do it for any test standing guard over something whose failure would be expensive or silent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One mutant is enough.&lt;/strong&gt; The goal is to establish that the assertion discriminates at all, not to prove it catches every possible defect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not replace the tools.&lt;/strong&gt; Mutation testing frameworks do this exhaustively and automatically. If you can run one, run it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  On the name and the idea
&lt;/h2&gt;

&lt;p&gt;None of the mechanism here is new. Deliberately breaking code to confirm a test notices is the manual form of mutation testing, which has been studied since the 1970s and has mature tooling: &lt;a href="https://stryker-mutator.io/" rel="noopener noreferrer"&gt;Stryker&lt;/a&gt;, &lt;a href="https://pitest.org/" rel="noopener noreferrer"&gt;PIT&lt;/a&gt;, &lt;a href="https://mutmut.readthedocs.io/" rel="noopener noreferrer"&gt;mutmut&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What changed is the cost of skipping it. For decades this was a nicety, because hand-writing a test did some of the work incidentally. Generated tests do none of that work, so the step everyone used to skip is now the one holding the suite up. It needed a short name and a place in the workflow.&lt;/p&gt;

&lt;p&gt;Write the code first if you want, but never trust an assertion you have not watched fail for the right reason. That is all of &lt;strong&gt;Red Once&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>automation</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
