<?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: Nikolay Chernev</title>
    <description>The latest articles on DEV Community by Nikolay Chernev (@nikolay_chernev_a67479931).</description>
    <link>https://dev.to/nikolay_chernev_a67479931</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%2F4144039%2Fe50476d7-a290-4248-b645-7143b5a04b6b.jpg</url>
      <title>DEV Community: Nikolay Chernev</title>
      <link>https://dev.to/nikolay_chernev_a67479931</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikolay_chernev_a67479931"/>
    <language>en</language>
    <item>
      <title>Flaky AI tests are usually bad tests: how to stabilize a non-deterministic suite</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:00:05 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/flaky-ai-tests-are-usually-bad-tests-how-to-stabilize-a-non-deterministic-suite-3dmg</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/flaky-ai-tests-are-usually-bad-tests-how-to-stabilize-a-non-deterministic-suite-3dmg</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; most AI test flakiness comes from two sources — exact-match assertions on output that was never guaranteed to match, and missing pins on model version and temperature. Fix those two things and 80% of your flapping tests stabilize. The remaining 20% are genuine non-determinism, and the answer there is a different kind of assertion, not a different kind of model.&lt;/p&gt;

&lt;p&gt;The "AI is untestable" complaint comes up constantly in QA teams that are six months into an LLM project. The suite runs, half the tests pass, half fail, nobody trusts it, CI goes red, someone marks the failures "known flaky" and disables them. Now you have a test suite that runs green and tells you nothing.&lt;/p&gt;

&lt;p&gt;That's not an AI problem. That's a test-design problem that AI exposed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do AI tests flap when nothing has changed?
&lt;/h2&gt;

&lt;p&gt;Two reasons, and they're distinct enough that fixing the wrong one wastes a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason 1: the assertion is wrong.&lt;/strong&gt; You called the API once, got a string back, and wrote &lt;code&gt;assert response == "Here are the three items: apples, bananas, oranges."&lt;/code&gt; The model gives the same &lt;em&gt;information&lt;/em&gt; every time but rephrases it — "The three items are apples, bananas, and oranges." or "I found three items: apples, bananas, oranges." Your assertion fails. The model isn't broken. The test is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason 2: the test isn't pinned.&lt;/strong&gt; You're running at the default temperature (often 0.7–1.0), and you haven't nailed down the model version. Even at temperature=0, some providers add noise. At temperature=1.0, the same prompt produces genuinely different outputs run-to-run. If your test doesn't control these, you're testing a moving target.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you tell which problem you have?
&lt;/h2&gt;

&lt;p&gt;Run the failing test 10 times in a row against the same model, same prompt, with &lt;code&gt;temperature=0&lt;/code&gt; and an explicit model version pinned. Count the failures.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;10/10 fail:&lt;/strong&gt; the assertion is fundamentally wrong — it's asserting something that was never stable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0/10 fail:&lt;/strong&gt; your test was running at high temperature, or the model version was drifting. Pinning fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3/10 fail at temperature=0:&lt;/strong&gt; you've found genuine residual non-determinism (most providers don't guarantee exact reproducibility even at temp=0). You need a statistical assertion or a threshold check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The diagnosis tells you which fix to apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you fix wrong assertions?
&lt;/h2&gt;

&lt;p&gt;Stop asserting the string. Assert the properties that must be true of any valid output.&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="c1"&gt;# Before: brittle, flaps on every rephrasing
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_item_list&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;List the three items.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Here are the three items: apples, bananas, oranges.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# After: stable, tests what actually matters
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_item_list_properties&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;List the three items.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# all three items must be present, order doesn't matter
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;orange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# must not invent a fourth item
&lt;/span&gt;    &lt;span class="n"&gt;unexpected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;grape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mango&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;melon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;unexpected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# must stay concise — this isn't an essay prompt
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These assertions are deterministic. They never flap, they run at CI speed, and when one fails you know exactly what class of failure occurred. The first version was testing the model's wording. These versions test whether the model was &lt;em&gt;correct&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The same logic applies to structured output. If your app returns JSON, don't assert the whole serialized string — assert the schema and the values.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;jsonschema&lt;/span&gt;

&lt;span class="n"&gt;ITEM_SCHEMA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;array&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minItems&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minimum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_structured_output_shape&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;List the items.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;jsonschema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ITEM_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schema validation never flaps. A miscount between &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;len(items)&lt;/code&gt; is a real logic bug — and it'll catch it reliably every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you pin model version and temperature?
&lt;/h2&gt;

&lt;p&gt;This is the simplest fix and the most commonly skipped one.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;query_for_tests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Use this wrapper in all tests — never call the client directly.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5-20251001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# explicit version, not "claude-haiku-latest"
&lt;/span&gt;        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                       &lt;span class="c1"&gt;# deterministic mode for unit tests
&lt;/span&gt;        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to note. First, pin the &lt;em&gt;full&lt;/em&gt; model ID — &lt;code&gt;claude-haiku-4-5-20251001&lt;/code&gt;, not a &lt;code&gt;latest&lt;/code&gt; alias. When the provider bumps the alias, your test behavior changes silently. Second, run your &lt;em&gt;property checks&lt;/em&gt; at temperature=0, but keep a separate eval suite that runs at your production temperature. A bug that only surfaces at temperature=1.0 is still a bug, and you want to find it before users do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about tests that still flap after pinning?
&lt;/h2&gt;

&lt;p&gt;At temperature=0, most models produce identical output for identical inputs most of the time. "Most of the time" isn't all the time — some providers are explicit that exact reproducibility isn't guaranteed. If you're still seeing flakiness after pinning, you have two options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A — statistical assertion.&lt;/strong&gt; Run the query N times and assert a pass rate, not a single result.&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_sentiment_is_positive_most_of_the_time&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ai_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify_sentiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Great product!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;positive_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;positive_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Only &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;positive_rate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; positive — expected ≥90%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty runs is usually enough to distinguish a real regression (drops to 60%) from noise (stays above 90%). Don't run this in fast CI — it's for a nightly or pre-release gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B — property check on the distribution.&lt;/strong&gt; If your app is supposed to return a confident label 90% of the time, write that as the spec directly. A test that runs once and sometimes passes isn't a spec — it's a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which flakiness isn't worth fighting?
&lt;/h2&gt;

&lt;p&gt;Genuine creative variation in open-ended generation. If your feature is "give me five marketing tagline ideas," the outputs &lt;em&gt;should&lt;/em&gt; vary. Testing that they don't vary is testing the wrong thing. What you can assert: every tagline is under 15 words, none contain the competitor's name, all use imperative tense. Stable properties on variable output. &lt;a href="https://ai-testing-in-practice.ai-testing-in-practice-site.workers.dev/articles/testing-llm-rag-nondeterministic-outputs/" rel="noopener noreferrer"&gt;Writing those for LLM and RAG output&lt;/a&gt; takes some practice but it's the same skill.&lt;/p&gt;

&lt;p&gt;Also not worth fighting: latency variation. A 200ms swing on a generation call doesn't mean anything. Set a hard ceiling (5s, 10s, whatever production SLA is) and assert that — not an exact time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't fix
&lt;/h2&gt;

&lt;p&gt;Bad property coverage. If you write properties that are too loose — "response must be non-empty" — you'll have a green suite that misses every quality regression. Properties have to actually capture the contract of the feature.&lt;/p&gt;

&lt;p&gt;The deeper problem: figuring out &lt;em&gt;what properties to assert&lt;/em&gt; for &lt;em&gt;your specific system&lt;/em&gt; is the design work. There's no universal answer. The properties for a customer-support chatbot are different from those for a code-generation tool. You have to understand what "correct" looks like for your feature before you can write a test that catches when it isn't.&lt;/p&gt;

&lt;p&gt;That design work is what &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX?tag=aitestingbook-20" rel="noopener noreferrer"&gt;&lt;em&gt;AI Software Testing in Practice&lt;/em&gt;&lt;/a&gt; is built around — working out the right assertion strategy per feature type, with examples across real AI product categories. The &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;free test lab&lt;/a&gt; has working property-check templates you can adapt today.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Are flaky AI tests a sign that the model is broken?&lt;/strong&gt;&lt;br&gt;
Rarely. Most AI test flakiness is caused by exact-match assertions on non-deterministic output, or by tests that don't pin temperature and model version. Fix the assertion and the test stops flapping without touching the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I quarantine flaky AI tests instead of fixing them?&lt;/strong&gt;&lt;br&gt;
Quarantine for 48 hours while you diagnose — don't leave them skipped permanently. A flaky test is either a bad assertion (fix it now) or real non-determinism you need to understand (build a statistical version). Skipping it forever means a category of bugs stops being caught.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I stabilize an AI test that sometimes passes and sometimes fails on the same input?&lt;/strong&gt;&lt;br&gt;
First check if you're pinning model version and temperature=0. If yes and it still flaps, the test is asserting something that varies legitimately — rewrite it as a property check or a scored threshold instead of an exact match.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>programming</category>
    </item>
    <item>
      <title>From experienced tester to AI testing: the roadmap, in order</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Wed, 28 Oct 2026 19:48:14 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/from-experienced-tester-to-ai-testing-the-roadmap-in-order-19e5</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/from-experienced-tester-to-ai-testing-the-roadmap-in-order-19e5</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; learn it in this order — the non-determinism mindset, prompts as test inputs, evals (the new assertion), LLM/RAG failure modes, then verifying AI-written tests with mutation testing. Skip the math-heavy ML theory; you're testing behavior, not training models.&lt;/p&gt;

&lt;p&gt;I keep seeing the same post on r/softwaretesting: &lt;em&gt;"10 years of manual and automation under my belt, and the AI stuff makes me feel like a junior again. Where do I even start?"&lt;/em&gt; The honest answer is that you're closer than you think. Maybe 70% of what makes you a good tester transfers directly. The other 30% is a handful of new ideas, and they stack in a specific order. Learn them out of order and it feels like chaos. Learn them in order and each one clicks.&lt;/p&gt;

&lt;p&gt;Here's the order I'd follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Get the one mindset shift first: non-determinism
&lt;/h2&gt;

&lt;p&gt;Everything new starts here. In normal testing, one input has one correct output, so you assert equality. In AI testing, one input has a &lt;em&gt;set&lt;/em&gt; of valid outputs — ask the same question twice and the wording changes, both answers fine. That single fact breaks every exact-match assertion you've ever written and is why your first naive AI tests will flake.&lt;/p&gt;

&lt;p&gt;Don't move on until this feels natural, because the rest of the roadmap is just tools for dealing with it. If a test flaps on identical input, your instinct should flip from "the system is flaky" to "I'm asserting the wrong thing."&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Learn prompting — as a tester, not a hobbyist
&lt;/h2&gt;

&lt;p&gt;Everyone treats prompting as a party trick. For you it's &lt;strong&gt;controlling the test input.&lt;/strong&gt; A vague prompt is an uncontrolled variable; you can't tell a real regression from your own sloppy setup. Spend a week writing prompts that pin down format, scope, and constraints, and reading back exactly what the model did with them. You're not learning to be clever. You're learning to make the input reproducible enough to test against.&lt;/p&gt;

&lt;p&gt;This is the step most testers skip, and it's why their AI tests feel unreliable later.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Evals — the assertion for the AI era
&lt;/h2&gt;

&lt;p&gt;This is the real new skill, and it's where I'd spend the most time. An eval replaces "did the output equal X?" with "is this output acceptable?" Three flavors, in increasing effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Property checks&lt;/strong&gt; — rules true for &lt;em&gt;every&lt;/em&gt; valid answer. No PII leaked, no invented dollar figures, output parses as JSON. Deterministic, cheap, catch the scary failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reference/similarity&lt;/strong&gt; — score the answer against a known-good one with embeddings or keyword coverage, assert a threshold, not equality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-as-judge&lt;/strong&gt; — have a model grade against a rubric. Powerful for open-ended text, but it's non-deterministic too, so pin its model and pair it with hard property checks.
&lt;/li&gt;
&lt;/ul&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_support_reply&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my card was declined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intent&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;              &lt;span class="c1"&gt;# property: deterministic
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;                   &lt;span class="c1"&gt;# property: no invented amounts
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rubric&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acknowledges, asks one question, no blame&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you learn one thing from this whole list, learn to write evals. It's the skill job posts are circling around without naming.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The LLM/RAG failure modes you'll actually hit
&lt;/h2&gt;

&lt;p&gt;Now get specific about &lt;em&gt;how&lt;/em&gt; these systems break, because it's not how normal software breaks — and &lt;a href="https://ai-testing-in-practice.ai-testing-in-practice-site.workers.dev/articles/real-bug-or-model-failure/" rel="noopener noreferrer"&gt;how to tell a real bug from the model just drifting&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hallucination&lt;/strong&gt; — confident, fluent, completely made up. Your test is a grounding check: did the answer stick to the provided context?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval failure (RAG)&lt;/strong&gt; — the model's fine, but it fetched the wrong documents. Test retrieval &lt;em&gt;separately&lt;/em&gt; from generation, or you'll debug the wrong half for a day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection&lt;/strong&gt; — user input that hijacks the instructions. This is the new security test, and it's yours now.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to know how a transformer works to test any of this. You need to know what wrong looks like, which is exactly what testers are good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use AI to write tests — then prove the tests work
&lt;/h2&gt;

&lt;p&gt;You'll let Copilot or Claude generate test suites. Fine. But generated tests have a specific trap: they tend to assert that the code does what it currently does — bugs included. A green suite that was written against buggy code is just the bug, notarized.&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;mutation testing&lt;/strong&gt;: deliberately break the code and check your tests scream. If you flip a &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;gt;=&lt;/code&gt; and every test still passes, those tests prove nothing. Run this on AI-generated suites especially — it's &lt;a href="https://ai-testing-in-practice.ai-testing-in-practice-site.workers.dev/articles/trust-ai-generated-tests/" rel="noopener noreferrer"&gt;how you actually earn confidence in AI-written tests&lt;/a&gt;. This is the whole point of the book, and the &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;free lab&lt;/a&gt; is a 10-minute demo of it — a suite that's green against four real bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to skip
&lt;/h2&gt;

&lt;p&gt;You do &lt;strong&gt;not&lt;/strong&gt; need: the math behind training, building models, PyTorch, or a data-science course. You're testing behavior at the boundary, the same place you always have. Picking up ML theory first is the most common way people stall for three months and learn nothing testable. Skip it until something forces you to care.&lt;/p&gt;

&lt;p&gt;Realistically this is a few focused weeks, not a bootcamp — and you keep every bit of your existing automation, CI, and bug-sense. Don't let anyone sell you "AI testing" as a reset. It's an extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need to learn Python?&lt;/strong&gt;&lt;br&gt;
If you already automate, you're set — most eval and LLM tooling is Python-first, but it's the same testing you do now with different assertions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this going to replace traditional QA?&lt;/strong&gt;&lt;br&gt;
No. It sits on top of it. CI, coverage, API and E2E testing all still apply; AI adds non-determinism and a new set of failure modes to cover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does the book fit in step 3 and 5?&lt;/strong&gt;&lt;br&gt;
Those two steps — evals and verifying AI-written tests — are the spine of &lt;em&gt;AI Testing in Practice&lt;/em&gt;. It walks the full version with runnable examples, so you're not piecing it together from scattered blog posts.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>programming</category>
    </item>
    <item>
      <title>Codex or Claude wrote my tests — how do I actually trust them?</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Wed, 28 Oct 2026 19:37:08 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/codex-or-claude-wrote-my-tests-how-do-i-actually-trust-them-2ooi</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/codex-or-claude-wrote-my-tests-how-do-i-actually-trust-them-2ooi</guid>
      <description>&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; don't trust them because they're green. AI writes tests that assert the code's &lt;em&gt;current&lt;/em&gt; behavior — including the bugs. Earn the confidence by mutation testing: change the code on purpose and check the tests fail. The ones that stay green are lying to you.&lt;/p&gt;

&lt;p&gt;Last month I let Claude generate a test file for a pricing function. Forty tests, all passing, 94% coverage, took ninety seconds. It felt like a gift. Then I changed a &lt;code&gt;&amp;lt;=&lt;/code&gt; to &lt;code&gt;&amp;lt;&lt;/code&gt; in the discount tier — a real off-by-one I'd have shipped — and all forty tests still passed. The suite wasn't testing the pricing logic. It was describing it.&lt;/p&gt;

&lt;p&gt;That's the trap, and it's specific to generated tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI tests pass against broken code
&lt;/h2&gt;

&lt;p&gt;When you write a test, you usually start from "here's what this &lt;em&gt;should&lt;/em&gt; do." When a model writes a test, it reads the implementation and writes assertions that match what the code &lt;em&gt;does&lt;/em&gt;. Those are not the same job. If the function has a bug, the model faithfully writes a test that locks the bug in place and marks it green.&lt;/p&gt;

&lt;p&gt;Three failure patterns show up again and again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The mirror test.&lt;/strong&gt; It asserts &lt;code&gt;result == function(input)&lt;/code&gt; essentially by re-deriving the output from the same code path. Tautology. It can never fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The assertion that isn't.&lt;/strong&gt; &lt;code&gt;assert response is not None&lt;/code&gt;, &lt;code&gt;assert len(result) &amp;gt; 0&lt;/code&gt;. Technically an assertion. Catches almost nothing. Models love these because they always pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage theater.&lt;/strong&gt; The line runs, so coverage counts it, but nothing checks the value it produced. 94% coverage, 0% confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coverage tools can't see any of this. A line is "covered" the moment it executes — whether or not a single assertion cares about the result. This is why I stopped treating coverage as a quality signal years ago, and why it's actively dangerous with AI-generated suites: it gives you a big green number that measures the wrong thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test that proves your tests: mutation testing
&lt;/h2&gt;

&lt;p&gt;Here's the move. Instead of asking "do my tests pass?", ask "if the code were wrong, would my tests notice?" You answer it by deliberately introducing bugs — &lt;em&gt;mutants&lt;/em&gt; — and running the suite against each one.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flip &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;gt;=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;+&lt;/code&gt; to &lt;code&gt;-&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Replace a return value with &lt;code&gt;None&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete a line&lt;/li&gt;
&lt;li&gt;Swap &lt;code&gt;and&lt;/code&gt; for &lt;code&gt;or&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every mutant is a tiny, realistic bug. A good test suite &lt;em&gt;kills&lt;/em&gt; the mutant — at least one test fails. A mutant that survives is a bug your tests would have let through. The score is simple: killed ÷ total. That number, not coverage, is how much your suite is actually worth.&lt;/p&gt;

&lt;p&gt;You don't do this by hand. The tooling is mature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; — &lt;a href="https://github.com/boxed/mutmut" rel="noopener noreferrer"&gt;&lt;code&gt;mutmut&lt;/code&gt;&lt;/a&gt; or &lt;code&gt;cosmic-ray&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript/TypeScript&lt;/strong&gt; — &lt;a href="https://stryker-mutator.io/" rel="noopener noreferrer"&gt;Stryker&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java&lt;/strong&gt; — PITest&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C#&lt;/strong&gt; — Stryker.NET&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A first run on that AI-generated pricing suite looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;mutmut run
&lt;span class="c"&gt;# ... a few minutes later&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;mutmut results
Killed: 22  Survived: 18  Timeout: 0

&lt;span class="c"&gt;# the survivors are the interesting part:&lt;/span&gt;
- discount.py:14  &lt;span class="sb"&gt;`&lt;/span&gt;&amp;lt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; → &lt;span class="sb"&gt;`&lt;/span&gt;&amp;lt;&lt;span class="sb"&gt;`&lt;/span&gt;     &lt;span class="o"&gt;(&lt;/span&gt;survived&lt;span class="o"&gt;)&lt;/span&gt;   ← my off-by-one
- discount.py:27  &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;return &lt;/span&gt;total&lt;span class="sb"&gt;`&lt;/span&gt; → &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="k"&gt;return &lt;/span&gt;None&lt;span class="sb"&gt;`&lt;/span&gt;  &lt;span class="o"&gt;(&lt;/span&gt;survived&lt;span class="o"&gt;)&lt;/span&gt;
- tax.py:9        &lt;span class="sb"&gt;`&lt;/span&gt;rate &lt;span class="k"&gt;*&lt;/span&gt; amount&lt;span class="sb"&gt;`&lt;/span&gt; → &lt;span class="sb"&gt;`&lt;/span&gt;rate + amount&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;survived&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eighteen survivors out of forty mutants. The suite that looked airtight was catching a little over half of injected bugs. Now I had a to-do list: eighteen specific holes, each pointing at the exact line and the exact kind of mistake my tests ignored. I wrote real assertions for those, re-ran, and got survivors down to two (both equivalent mutants — changes that don't actually alter behavior, which you learn to recognize).&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow I'd actually recommend
&lt;/h2&gt;

&lt;p&gt;Keep letting the AI write tests. It's genuinely fast and the boilerplate is fine. Just add one gate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate the tests. Let the model do the grunt work.&lt;/li&gt;
&lt;li&gt;Run mutation testing on the module, not just coverage.&lt;/li&gt;
&lt;li&gt;Treat every surviving mutant as a missing assertion. Fix the test, not the score.&lt;/li&gt;
&lt;li&gt;Set a mutation-score threshold in CI for the code that matters (payments, auth, anything that loses money or leaks data). Start at 70%, raise it as you go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point isn't to mutation-test your entire repo on every commit — that's slow and you'll turn it off within a week. Run it on the risky modules, and run it &lt;em&gt;especially&lt;/em&gt; on anything an AI wrote, because that's where the mirror tests hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't fix
&lt;/h2&gt;

&lt;p&gt;Mutation testing proves your tests can detect code changes. It does &lt;strong&gt;not&lt;/strong&gt; tell you the original spec was right — if the requirement itself is wrong, you'll have a high mutation score on the wrong behavior. It's also not free: runtimes are minutes, not seconds, so it belongs on critical paths and pre-merge, not on every keystroke. And it says nothing about non-deterministic AI &lt;em&gt;outputs&lt;/em&gt; — testing an LLM's answers is a different problem (that's &lt;a href="https://ai-testing-in-practice.ai-testing-in-practice-site.workers.dev/articles/real-bug-or-model-failure/" rel="noopener noreferrer"&gt;evals, and telling a real bug from model drift&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;What it does do is convert "the tests are green" into "the tests would actually catch a bug," which is the only version of green worth having.&lt;/p&gt;

&lt;p&gt;This verification layer — proving AI-written tests before you rely on them — is the spine of &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;&lt;em&gt;AI Testing in Practice&lt;/em&gt;&lt;/a&gt;. If you want to feel it before reading, the &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;free lab&lt;/a&gt; is a 10-minute run: a green suite sitting on top of four real bugs, and the mutation pass that exposes them.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't high coverage enough if the tests are well written?&lt;/strong&gt;&lt;br&gt;
Coverage tells you a line &lt;em&gt;ran&lt;/em&gt;, not that anything &lt;em&gt;checked the result&lt;/em&gt;. You can hit 100% coverage with zero meaningful assertions — and AI-generated suites do this constantly. Mutation score measures whether the assertions actually bite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is mutation testing different from just writing more tests?&lt;/strong&gt;&lt;br&gt;
More tests can all be mirror tests. Mutation testing finds the &lt;em&gt;specific&lt;/em&gt; behaviors nothing is checking, so you add the three assertions that matter instead of thirty that don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My mutation run is too slow to put in CI. Now what?&lt;/strong&gt;&lt;br&gt;
Don't run it repo-wide. Scope it to the modules that carry real risk and run it pre-merge on changed files. A 90-second mutation run on the payment module is worth more than a 40-minute run across code nobody will break.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your AI Wrote 3 Tests. 3 Passed. The Code Was Still Broken.</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Mon, 19 Oct 2026 14:13:54 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/your-ai-wrote-3-tests-3-passed-the-code-was-still-broken-4376</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/your-ai-wrote-3-tests-3-passed-the-code-was-still-broken-4376</guid>
      <description>&lt;h1&gt;
  
  
  Your AI Wrote 3 Tests. 3 Passed. The Code Was Still Broken.
&lt;/h1&gt;

&lt;p&gt;I ran this exact scenario last week: an AI assistant writes a small test suite against a function with a seeded bug. All 3 tests pass. Green checkmarks, done, ship it. Then I ran an independent verification suite against the same code — written to check the &lt;em&gt;spec&lt;/em&gt;, not to agree with whatever the implementation already does. It failed, and it caught all 4 bugs I'd planted, including the one the AI's own tests walked right past.&lt;/p&gt;

&lt;p&gt;That gap — "the tests pass" vs. "the code is correct" — is the single biggest trust problem in AI-assisted testing right now, and it's not a vibe. Stack Overflow's 2025 Developer Survey put real numbers on it: 84% of developers now use or plan to use AI tools, up from 76% in 2024, but trust in the &lt;em&gt;accuracy&lt;/em&gt; of what those tools produce kept falling — Stack Overflow's own analysis puts it at 29%, down from 40% the year before, and 46% of developers say they actively distrust AI output (&lt;a href="https://infoworld.com/article/4031673/ai-use-among-software-developers-grows-but-trust-remains-an-issue-stack-overflow-survey.html" rel="noopener noreferrer"&gt;InfoWorld&lt;/a&gt;, &lt;a href="https://leaddev.com/technical-direction/trust-in-ai-coding-tools-is-plummeting" rel="noopener noreferrer"&gt;LeadDev&lt;/a&gt;). Adoption is up. Trust is down. That's not a contradiction, it's a warning sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "all tests passed" doesn't mean what you think
&lt;/h2&gt;

&lt;p&gt;Here's the mechanism, and it's structural, not a one-off glitch. When the same model writes the implementation and the tests, both inherit the same blind spots. If the code has a bug — say, a counter that increments regardless of whether a request actually succeeded — a model asked to "write tests for this function" will often write an assertion that checks the behavior the code &lt;em&gt;actually has&lt;/em&gt;, not the behavior the spec &lt;em&gt;requires&lt;/em&gt;. The test and the bug agree with each other. Nothing goes red, because nothing was ever checking against an independent standard.&lt;/p&gt;

&lt;p&gt;This is distinct from flakiness, though the two get confused. Flaky tests fail intermittently on correct code. What I'm describing is the opposite and arguably worse: tests that pass &lt;em&gt;reliably&lt;/em&gt; on incorrect code. Google's own engineering data shows roughly 16% of tests exhibit some flakiness, and flaky failures account for around 84% of red builds — a huge, well-studied problem. But a test suite that passes consistently on broken code doesn't even give you a red build to investigate. There's nothing alerting you. That's the harder failure mode to catch, because every signal you'd normally rely on says "fine."&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete way to tell the difference
&lt;/h2&gt;

&lt;p&gt;The fix isn't "don't use AI to write tests." It's: don't let the same pass of AI-authored assertions be the only check. Verify the verifier.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separate the test-writer from the test-grader.&lt;/strong&gt; Don't ask the same prompt/context that explains the implementation to also validate it. Write (or generate) an independent suite from the spec/requirements alone, without showing it the implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the AI's suite against a known-broken version of the code.&lt;/strong&gt; If it still passes 3/3, the suite isn't testing behavior — it's testing "does the code run."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check what fraction of seeded bugs survive.&lt;/strong&gt; This is exactly what mutation testing measures (coverage tells you code &lt;em&gt;executed&lt;/em&gt;; mutation score tells you whether anything was actually &lt;em&gt;checked&lt;/em&gt;) — but you don't need a mutation-testing framework to get the intuition. Plant 3-4 real bugs by hand and see which ones your AI-written suite catches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat a pass from AI-authored tests as a claim, not a verdict.&lt;/strong&gt; "Don't trust it — run it" against an independent check before you believe it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I built a small, free, runnable lab that makes this concrete instead of theoretical: a deliberately broken implementation, an AI-generated test suite that passes 3/3 against it, and an independent verification suite that fails and catches all 4 seeded bugs. You can run both in under a minute and watch the gap yourself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free lab (GitHub):&lt;/strong&gt; &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node run-lab.js weak              # AI's own tests: PASS 3/3 on broken code
node run-lab.js verify-candidate  # independent suite: FAILS, catches all 4 bugs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this distinction — "the model failed" vs. "there's a real bug, and the model's own tests just didn't catch it" — is a question you're currently answering by gut feel, that's the exact problem the lab is built to make visible. It's also chapter one of a longer field guide I wrote on verification-first AI testing for QA/SDET engineers, with reproducible examples instead of slide-deck claims:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kindle ($9.99): &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;https://www.amazon.com/dp/B0HL4SC5PX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PDF+EPUB+labs bundle ($19): &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;https://nikolaychernev.gumroad.com/l/ai-testing-in-practice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No guarantees, no "best practices" fluff — just a repeatable way to check whether you can trust what the AI just handed you.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>programming</category>
    </item>
    <item>
      <title>100% Coverage, 4% Mutation Score: The Gap That Explains Why Your AI Tests Pass Everything</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Fri, 16 Oct 2026 17:57:42 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/100-coverage-4-mutation-score-the-gap-that-explains-why-your-ai-tests-pass-everything-3m92</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/100-coverage-4-mutation-score-the-gap-that-explains-why-your-ai-tests-pass-everything-3m92</guid>
      <description>&lt;h1&gt;
  
  
  100% Coverage, 4% Mutation Score: The Gap That Explains Why Your AI Tests Pass Everything
&lt;/h1&gt;

&lt;p&gt;One team ran mutation testing on an AI-generated suite that reported 100% line coverage. Every statement executed. The mutation score — the share of deliberately injected bugs the tests actually caught — came back at 4%. &lt;a href="https://www.shiplight.ai/api/blog/do-ai-generated-tests-actually-catch-bugs/raw" rel="noopener noreferrer"&gt;Shiplight's write-up&lt;/a&gt; walks through exactly how that happens, and it's worth sitting with the number: 96% of injected defects slipped straight through a "fully covered" suite.&lt;/p&gt;

&lt;p&gt;That gap is the whole problem with trusting an AI's own tests, and it's sharper than the usual "AI hallucinates" complaint. Coverage tells you code &lt;em&gt;ran&lt;/em&gt;. It says nothing about whether anything was actually &lt;em&gt;checked&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why coverage lies
&lt;/h2&gt;

&lt;p&gt;Line coverage counts a statement as tested the moment it executes inside a test function — even if the assertion that follows is &lt;code&gt;expect(result).not.toBeNull()&lt;/code&gt; on a function that was never going to return null. &lt;a href="https://www.thoughtworks.com/radar/techniques/mutation-testing" rel="noopener noreferrer"&gt;Thoughtworks' Technology Radar&lt;/a&gt; flags this directly: high coverage percentages routinely mask logically hollow tests, especially generated ones.&lt;/p&gt;

&lt;p&gt;There's a structural reason AI-written tests drift this way. When the same model writes the implementation and the test, both inherit the same blind spots. One practitioner account (&lt;a href="https://sia.hackernoon.com/your-ai-generated-code-tests-might-be-lying-to-you" rel="noopener noreferrer"&gt;HackerNoon&lt;/a&gt;) describes a counter that increments whether or not a request actually succeeds — and a test suite that treats "counter went up" as proof the request worked, because that's the assumption baked into the code it was copying. The bug and the test agree with each other. Nobody disagrees, so nothing goes red.&lt;/p&gt;

&lt;p&gt;Mutation testing is the counter-check: inject a small fault (flip a comparison, drop a line, change a constant) and rerun the suite. If nothing fails, that mutant "survived" — and a surviving mutant means some class of real bug could ship with every test green. In one informal report, 20 AI-written tests against a Laravel controller reported full endpoint coverage but killed only 68 of 95 injected mutants — 27 bugs that full coverage didn't catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reader already found the sharper version of this
&lt;/h2&gt;

&lt;p&gt;Last week I wrote about telling a real bug from a model failure. A reader, &lt;a href="https://dev.to/runfoundry"&gt;Foundry&lt;/a&gt;, left a comment describing something worse than a weak assertion: a check that is &lt;em&gt;structurally&lt;/em&gt; incapable of failing.&lt;/p&gt;

&lt;p&gt;Their example: an automated mobile check hashed a screenshot before and after a swipe to confirm the feed advanced. On a playing video, the hash changes every single frame — so the check passes whether or not the swipe did anything at all. A second check read process age with &lt;code&gt;ps -o etimes=&lt;/code&gt;, which doesn't exist on macOS; the command failed silently, the age never crossed the threshold, and the rule never fired, ever. Neither check had ever gone red, so neither got flagged for review. Their question is the one worth stealing: &lt;em&gt;could this check pass if the thing I care about did not happen?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's mutation testing in one sentence, applied by hand instead of with a tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do about it
&lt;/h2&gt;

&lt;p&gt;You don't need a mutation-testing framework to start. Before trusting any AI-written test — yours or a teammate's — ask it Foundry's question directly: if I introduce the bug this test is supposed to catch, does it fail? If you can't answer that without running it, that's the review item, not "does it pass."&lt;/p&gt;

&lt;p&gt;The free lab I maintain is a runnable version of exactly this comparison. &lt;code&gt;node run-lab.js weak&lt;/code&gt; generates AI tests against deliberately broken code — they pass 3/3. &lt;code&gt;node run-lab.js verify-candidate&lt;/code&gt; runs an independent verification suite against the same code — it fails, and it catches all 4 seeded bugs. The difference between those two runs is the entire argument of this post, reproducible in about two minutes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free lab:&lt;/strong&gt; &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this is a problem you deal with weekly and you want the fuller method — how to tell a real failure from a model failure, how to build an independent verification layer, worked examples — that's the subject of the book:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kindle ($9.99): &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;https://www.amazon.com/dp/B0HL4SC5PX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PDF+EPUB+labs bundle ($19): &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;https://nikolaychernev.gumroad.com/l/ai-testing-in-practice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't trust it — run it.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>mutationtesting</category>
    </item>
    <item>
      <title>3 'Flaky' Tests, 1 Real Segfault: How to Tell a Bug From a Model Failure</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Mon, 12 Oct 2026 18:54:12 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/3-flaky-tests-1-real-segfault-how-to-tell-a-bug-from-a-model-failure-345o</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/3-flaky-tests-1-real-segfault-how-to-tell-a-bug-from-a-model-failure-345o</guid>
      <description>&lt;h1&gt;
  
  
  3 "Flaky" Tests, 1 Real Segfault: How to Tell a Bug From a Model Failure
&lt;/h1&gt;

&lt;p&gt;A team investigating three flaky CI tests this year didn't find flakiness. They found a race condition that silently dropped a data point, a background task re-inserting deleted rows, and a segfault that reproduced in &lt;strong&gt;7% of runs — but only on Python 3.13&lt;/strong&gt;. &lt;a href="https://openwisp.org/blog/aggressively-hunting-down-flaky-ci-tests-with-ai/" rel="noopener noreferrer"&gt;Full writeup here.&lt;/a&gt; All three had been living under the label "flaky" for a while, because that label is cheaper than investigating.&lt;/p&gt;

&lt;p&gt;That's the exact failure mode this post is about — not "AI is bad at testing," but a sharper question: &lt;strong&gt;when a test disagrees with your assumptions, how do you know if the test found a bug, or if the test (or the model that wrote it) is just wrong?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the trust gap the data keeps confirming. Stack Overflow's &lt;a href="https://itbrief.co.uk/story/stack-overflow-survey-finds-developers-wary-of-ai-tools" rel="noopener noreferrer"&gt;2026 Developer Survey&lt;/a&gt; (30,000+ respondents, published Oct 6) found only 6.6% of developers would trust AI with an important decision outright — but nearly half said they'd trust AI output &lt;em&gt;when they can verify it themselves&lt;/em&gt;. Verification, not vibes, is what closes the gap. The 2025 edition of the same survey found 84% of developers now use AI tools, while only 29% trust the output and 46% actively distrust it. Adoption outran trust, and nobody built the missing piece.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "the AI said it passed" isn't evidence
&lt;/h2&gt;

&lt;p&gt;There's a structural reason AI-written tests are bad witnesses for their own correctness: a recent bug-validation study (&lt;a href="https://arxiv.org/abs/2604.11950" rel="noopener noreferrer"&gt;AnyPoC, arXiv 2604.11950&lt;/a&gt;) found that naive LLM agents are unreliable validators — they're biased toward reporting success, sometimes producing proof-of-concept reproductions that look plausible but don't actually run, or confidently describing execution traces that never happened. Separately, researchers &lt;a href="https://tianpan.co/blog/2026-05-04-ai-generated-tests-coverage-illusion" rel="noopener noreferrer"&gt;cataloguing why AI-generated tests mislead&lt;/a&gt; point out the core mechanism: an LLM generating a test often writes the &lt;em&gt;expected&lt;/em&gt; value by looking at what the code currently does, not what it's supposed to do. If the implementation is wrong, the test enshrines the wrong answer and then passes forever. High coverage, zero fault detection.&lt;/p&gt;

&lt;p&gt;That's also why Thoughtworks' Technology Radar now &lt;a href="https://www.thoughtworks.com/radar/techniques/mutation-testing" rel="noopener noreferrer"&gt;recommends mutation testing over coverage&lt;/a&gt; as the more honest signal for AI-assisted suites — coverage tells you code ran, not that anything was actually checked. A suite that can't catch a deliberately flipped &lt;code&gt;&amp;gt;=&lt;/code&gt; isn't testing, it's decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete way to tell the difference
&lt;/h2&gt;

&lt;p&gt;You don't need a research paper to apply this — you need a second, independent check that doesn't trust the first one's homework. That's the whole premise of the free lab I built:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node run-lab.js weak&lt;/code&gt; — an AI-written test suite runs against deliberately broken code and reports &lt;strong&gt;3/3 passing.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node run-lab.js verify-candidate&lt;/code&gt; — an independently written verification suite runs against the &lt;em&gt;same&lt;/em&gt; broken code and &lt;strong&gt;fails, catching all 4 seeded bugs.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same code. Same "the tests pass" starting point. Opposite verdicts, because one suite actually checks outcomes against the spec and the other checks outcomes against itself. That gap — not whether you used AI — is what determines whether you shipped a real bug or a model failure.&lt;/p&gt;

&lt;p&gt;Three things worth doing this week if you work with AI-generated tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust a green run from the same model that wrote the test.&lt;/strong&gt; Reproduce independently, ideally with a suite or a human who never saw the implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check what the test actually asserts&lt;/strong&gt;, not just that it ran. A test with no meaningful assertion is coverage theater.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run mutation testing on anything AI-generated and load-bearing.&lt;/strong&gt; If flipping a comparison operator doesn't break the suite, the suite isn't catching real bugs — it's catching nothing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;The free lab above is open source and takes about 10 minutes to run: &lt;strong&gt;&lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;ai-test-verification-lab on GitHub&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the "real bug vs. model failure" problem is one you deal with regularly, I wrote a longer field guide on exactly this — reproducible labs included, not just advice: &lt;strong&gt;&lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;AI-Assisted Software Testing in Practice&lt;/a&gt;&lt;/strong&gt; ($9.99 Kindle) or the &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;PDF+EPUB+labs bundle on Gumroad&lt;/a&gt; ($19). It's a first-edition field guide with runnable proof, not a platform or a certification — the tagline I keep coming back to is: don't trust it, run it.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>automation</category>
    </item>
    <item>
      <title>Your AI Wrote Tests With 100% Coverage. They Catch 4% of Real Bugs.</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Sun, 11 Oct 2026 12:01:37 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/your-ai-wrote-tests-with-100-coverage-they-catch-4-of-real-bugs-182i</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/your-ai-wrote-tests-with-100-coverage-they-catch-4-of-real-bugs-182i</guid>
      <description>&lt;p&gt;That's not a hypothetical split — it's a documented test suite: 100% line coverage, 4% mutation score. Every line executed. 96% of seeded bugs slipped through anyway. &lt;a href="https://tianpan.co/blog/2026-05-04-ai-generated-tests-coverage-illusion" rel="noopener noreferrer"&gt;Source&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Coverage tells you which lines ran. It says nothing about whether the test would notice if the logic were wrong. That gap is exactly where AI-generated tests tend to live: fluent, green, and checking almost nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fresh data point: what happens when a second AI checks the first one's work
&lt;/h2&gt;

&lt;p&gt;Two weeks ago, First Mate Technologies published results from building QueueMate, a restaurant queue app, with a deliberate builder/checker split: one model writes the implementation, a &lt;em&gt;separate&lt;/em&gt; model designs test cases and reviews the code before it merges. The checker produced 554 test cases. The resulting sweep found 38 defects — including a severity-one bug in session handling that the builder's own tests had missed. (&lt;a href="https://www.developer-tech.com/news/ai-coding-agents-test-own-code/" rel="noopener noreferrer"&gt;developer-tech.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That's the whole thesis of verification-first testing in one case study: the model that wrote the code is a bad judge of whether the code is right. You need a second, independent check — and ideally not just "another AI," but something that actually probes behavior.&lt;/p&gt;

&lt;p&gt;Also this past week: Momentic launched "Mo," an agent that drives an app the way a human would and reports reproduction steps and video evidence rather than asserting against its own output (&lt;a href="https://siliconangle.com/2026/09/28/momentic-debuts-mo-ai-agent-to-automate-software-testing-without-scripts" rel="noopener noreferrer"&gt;SiliconANGLE, Sep 28&lt;/a&gt;) — a sign the industry is converging on the same answer: separate the generator from the grader.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number that exposes the gap: mutation score
&lt;/h2&gt;

&lt;p&gt;Here's the practical tool, and it's not new or exotic — mutation testing has been around for decades, it's just newly relevant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A mutation testing tool (Stryker, PIT, mutmut, depending on your stack) automatically changes a small piece of your code — flips a &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;gt;=&lt;/code&gt;, deletes a line, swaps a boolean.&lt;/li&gt;
&lt;li&gt;It reruns your test suite against that mutated code.&lt;/li&gt;
&lt;li&gt;If a test fails, the mutant is "killed" — your suite caught it. If every test still passes, the mutant "survives" — your suite didn't actually verify that logic, no matter what the coverage report says.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gartner's February 2026 insights abstract on this put it plainly: relying on AI to expand test coverage leads to missed defects, because AI-generated tests often lack depth and precision — and recommended mutation testing specifically as the corrective layer (&lt;a href="https://www.gartner.com/en/documents/7454926" rel="noopener noreferrer"&gt;Gartner, Sushant Singhal &amp;amp; Erin Khoo&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A mutation score isn't a vibe. It's a number that tells you, mutant by mutant, whether your tests would actually notice if the logic broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing it without installing a mutation framework
&lt;/h2&gt;

&lt;p&gt;You don't need a full mutation testing setup to feel this gap in under ten minutes. I built a free, runnable lab that does the same thing by hand: four bugs seeded into working-looking code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;node run-lab.js weak&lt;/code&gt; → the AI-generated test suite passes &lt;strong&gt;3/3&lt;/strong&gt;. On broken code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;node run-lab.js verify-candidate&lt;/code&gt; → an independent verification suite fails, and catches all 4 seeded bugs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same mechanism as a mutation score, minus the tooling setup: deliberately broken logic, and a direct answer to "would my tests have caught this?" Repo: &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What to actually do about it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't grade an AI's tests by whether they're green. Grade them by whether they'd fail on a plausible wrong version of the code.&lt;/li&gt;
&lt;li&gt;If you can run a mutation tool for your stack, run it on AI-generated test files specifically — the survival rate is the honest coverage number.&lt;/li&gt;
&lt;li&gt;If you can't, write (or have a second, independent pass write) 2-3 deliberately broken variants of the function under test and check whether the existing suite catches any of them. That's mutation testing with a manual crank.&lt;/li&gt;
&lt;li&gt;Treat "the AI's own tests pass" as the start of verification, not the end of it — the question that matters is "real bug, or model failure?", and only an independent check answers it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is chapter-one territory in &lt;em&gt;AI-Assisted Software Testing in Practice&lt;/em&gt;, a field guide for testers and SDETs who already know the fundamentals and now need a working method for verifying AI-written tests rather than trusting them. No certification, no platform claims — a runnable kit and the reasoning behind it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free lab (same mechanism as above, run it yourself): &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kindle, $9.99: &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;https://www.amazon.com/dp/B0HL4SC5PX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PDF+EPUB+labs bundle, $19: &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;https://nikolaychernev.gumroad.com/l/ai-testing-in-practice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Make AI-generated tests you can actually trust. Don't trust it — run it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>programming</category>
    </item>
    <item>
      <title>Three Reports Dropped This Week. All Say the Same Thing: Your AI's Tests Are Lying to You.</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Thu, 01 Oct 2026 18:32:18 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/three-reports-dropped-this-week-all-say-the-same-thing-your-ais-tests-are-lying-to-you-2e7j</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/three-reports-dropped-this-week-all-say-the-same-thing-your-ais-tests-are-lying-to-you-2e7j</guid>
      <description>&lt;p&gt;89% of organizations had an AI-related production incident. Not "might." Had. That's not a hot take — it's from a report published three days ago.&lt;/p&gt;

&lt;p&gt;This week, three separate 2026 industry surveys landed within 48 hours of each other, and they all point at the same hole: AI got good at writing code and good at writing tests for that code — including tests that pass when the code is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, stacked up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Qodo's 2026 State of AI Code Quality Report&lt;/strong&gt; (500 developers, 300 engineering leaders, surveyed Aug 2026): reviewing and validating AI-generated code is now the #1 delivery bottleneck. 89% of orgs had an AI-related production incident. Only 3.7% of engineering leaders think their current process is actually sufficient going forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checksum's State of AI Code 2026&lt;/strong&gt; (105 engineering leaders): 78.1% trust AI code more than they did a year ago. Meanwhile, 61% shipped a production incident from AI-generated code in the last 90 days, and 74.3% had to roll back AI code because it failed in a way &lt;em&gt;their own unit tests didn't catch.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeviQA's 2026 QA Report&lt;/strong&gt; (4,000 QA professionals): 47% have hit AI-assisted code that passed its primary scenario and broke something else anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rising confidence. Rising incidents. That gap is the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this isn't a review problem — it's a trust-the-grader problem
&lt;/h2&gt;

&lt;p&gt;Here's the part that should worry testers specifically: it's not that AI-generated tests are sparse. It's that they can be confidently, fluently wrong — and they're the same agent's own homework, graded by itself.&lt;/p&gt;

&lt;p&gt;OWASP's Secure Coding with AI Cheat Sheet now calls this out directly: coding agents can make CI green by deleting failing tests, weakening assertions (an &lt;code&gt;assertEquals&lt;/code&gt; quietly becomes &lt;code&gt;assertNotNull&lt;/code&gt;), mocking the very unit under test, or just asserting whatever the buggy code already does. Their line, almost verbatim: &lt;em&gt;a passing test suite generated by the same agent that produced the code provides no independent assurance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's not a hypothetical. I built a free, runnable lab specifically to make it visible in under 10 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it fail, on purpose
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;AI Test Verification Lab&lt;/a&gt; ships deliberately broken code plus the AI-generated test suite that was written for it. Run it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node run-lab.js weak
# AI's own tests: PASS 3/3 — on broken code.

node run-lab.js verify-candidate
# Independent verification suite: FAIL 8/12 — catches all 4 seeded bugs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same code. Same bugs. One test suite says "ship it." The other says "stop." The difference isn't model quality — it's whether anything &lt;em&gt;independent&lt;/em&gt; checked the AI's own claim.&lt;/p&gt;

&lt;p&gt;This matches what the Stack Overflow 2025 Developer Survey already told us at scale: 84% of developers use AI tools, but only 3% highly trust the output, 46% actively distrust it, and 45% say they lose real time debugging what it hands back. JetBrains' 2025 ecosystem survey puts regular AI usage at 85%. Adoption is not the problem. Verification is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verification-first method (short version)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Never let the AI grade its own test.&lt;/strong&gt; If the same prompt/session that wrote the code also wrote the test, treat it as a draft, not evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seed known bugs and check the suite catches them.&lt;/strong&gt; If it doesn't catch a bug you put there on purpose, it won't catch the one you didn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for weakened assertions and deleted tests in diffs&lt;/strong&gt;, specifically — not just "tests still pass."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep an independent suite&lt;/strong&gt; that doesn't share assumptions with the code generator.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the whole method. It's not exotic — it's just discipline that AI-assisted speed makes easy to skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go deeper
&lt;/h2&gt;

&lt;p&gt;If this resonated, the free lab above is the fastest way to see it firsthand — it's the same demo in this post, runnable in 10 minutes. For the full method, worked examples, and a runnable kit built for QA/SDET workflows, the field guide is &lt;strong&gt;&lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;AI-Assisted Software Testing in Practice&lt;/a&gt;&lt;/strong&gt; ($19, PDF+EPUB+labs).&lt;/p&gt;

&lt;p&gt;Make AI-generated tests you can actually trust. Don't trust it — run it.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>softwaretesting</category>
    </item>
    <item>
      <title>The 15-second test that tells you an AI-generated test is worthless</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Wed, 30 Sep 2026 09:14:18 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/the-15-second-test-that-tells-you-an-ai-generated-test-is-worthless-i0</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/the-15-second-test-that-tells-you-an-ai-generated-test-is-worthless-i0</guid>
      <description>&lt;p&gt;Your AI just generated a test. Before you trust it, run this 15-second check — no tools, just one question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What broken version of this code would still make this test pass?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can picture even one realistic wrong implementation that stays green, the test isn't checking behavior. It's decoration — delete it or fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this one question works
&lt;/h2&gt;

&lt;p&gt;A test earns trust only if it &lt;em&gt;fails when the behavior is wrong&lt;/em&gt;. AI-generated tests reliably do the two easier things: they run, and they agree with the code the model just wrote — bugs included. The 15-second question forces the third, the one that matters: discrimination.&lt;/p&gt;

&lt;p&gt;Take a generated test like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;applies discount&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What broken &lt;code&gt;applyDiscount&lt;/code&gt; would still pass? Almost any of them — wrong rate, wrong rounding, no discount at all. It returns a number every time. Covered, green, worthless.&lt;/p&gt;

&lt;p&gt;Now the discriminating version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;10% over $2000, exactly at the boundary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// boundary: not discounted&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;applyDiscount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2001&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1800.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// 10% off&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;&amp;gt;&lt;/code&gt; where the spec says &lt;code&gt;&amp;gt;=&lt;/code&gt; now turns this red. That's a test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the habit concrete (free, 10 min)
&lt;/h2&gt;

&lt;p&gt;I built a tiny lab (no install, Node 20+) that runs the whole failure mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node run-lab.js all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It hands the AI's own suite a pricing function with four seeded defects. The AI's tests pass 3/3 against the broken code; an independent verification suite fails and catches all four:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WEAK TESTS (the AI's suite) .... PASS 3/3
STRONG VERIFICATION ........... FAIL 8/12   &amp;lt;- catches all 4 defects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the flawed file and try to write the smallest test that catches each defect &lt;em&gt;before&lt;/em&gt; you read the verification suite. That exercise is the whole method.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop around the question
&lt;/h2&gt;

&lt;p&gt;The 15-second test is the pocket version of a bigger habit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design the discriminating test first&lt;/strong&gt;, then generate.&lt;/li&gt;
&lt;li&gt;Keep the generator separate from the approver — a different model or session reviews it.&lt;/li&gt;
&lt;li&gt;Prove the suite catches faults with seeded defects and mutation.&lt;/li&gt;
&lt;li&gt;A human owns the release call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it, then go deeper
&lt;/h2&gt;

&lt;p&gt;Free, self-contained lab: &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I turned the full method into a book + runnable kit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kindle on Amazon ($9.99): &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;https://www.amazon.com/dp/B0HL4SC5PX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full bundle (PDF + EPUB + labs) on Gumroad ($19): &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;https://nikolaychernev.gumroad.com/l/ai-testing-in-practice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest caveat: the lab systems are simulators with &lt;em&gt;declared&lt;/em&gt; defects — they prove the method catches faults, not that any product is certified. Don't trust it — run it.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Coverage theatre: your AI hit 90% coverage and still shipped the bug</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Wed, 30 Sep 2026 09:07:33 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/coverage-theatre-your-ai-hit-90-coverage-and-still-shipped-the-bug-5bml</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/coverage-theatre-your-ai-hit-90-coverage-and-still-shipped-the-bug-5bml</guid>
      <description>&lt;p&gt;Your AI assistant just generated tests until the coverage bar turned green. 92%. Ship it?&lt;/p&gt;

&lt;p&gt;Here's the trap: coverage measures which lines &lt;em&gt;ran&lt;/em&gt;, not whether a test would notice when they're &lt;em&gt;wrong&lt;/em&gt;. AI made that gap free — you can generate 90% coverage in a minute, all of it happy-path, none of it discriminating. That's coverage theatre: it looks like safety, it measures activity, it proves almost nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why coverage lies
&lt;/h2&gt;

&lt;p&gt;A line is "covered" the moment a test executes it. But executing a line and asserting the &lt;em&gt;right thing&lt;/em&gt; about it are different claims. An AI-generated test that calls &lt;code&gt;applyDiscount(order)&lt;/code&gt; and asserts &lt;code&gt;typeof result === 'number'&lt;/code&gt; covers the function — and would stay green if the discount math were completely wrong.&lt;/p&gt;

&lt;p&gt;Coverage answers "did this code run in a test?" The question that earns trust is "would a test fail if this code were wrong?" Those are not the same, and AI output routinely nails the first while skipping the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  The antidote is old and boring: break the code on purpose
&lt;/h2&gt;

&lt;p&gt;If a suite can't tell correct behavior from a real defect, its coverage number is decoration. The cheapest check is mutation: change &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;gt;=&lt;/code&gt;, flip a boolean, swap two lines — then see if anything goes red. If the suite stays green on broken code, it isn't protecting you.&lt;/p&gt;

&lt;p&gt;I put the smallest runnable version of this in a free lab (no install, Node 20+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node run-lab.js all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It hands the AI's own suite a pricing function with four seeded defects, then runs an independent verification suite against the same broken code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WEAK TESTS (the AI's suite) .... PASS 3/3
STRONG VERIFICATION ........... FAIL 8/12   &amp;lt;- catches all 4 defects
MUTATION GATE ................. PASS 4/4
LAB GATE: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI's tests pass against code that is wrong. Verification is what fails — and that failure is the signal coverage never gave you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do instead of chasing the coverage bar
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Before you trust a generated test, ask: &lt;em&gt;what realistic broken version of this code would still make it pass?&lt;/em&gt; If you can picture one, the test isn't discriminating.&lt;/li&gt;
&lt;li&gt;Add a mutation or seeded-defect check to CI. One flipped operator per critical function exposes a suite full of decoration.&lt;/li&gt;
&lt;li&gt;Keep the thing that writes tests separate from the thing that approves them. The generator proposes; an independent reviewer (a different model or session) checks; a human owns the release call.&lt;/li&gt;
&lt;li&gt;Report mutation score, not just coverage %. Coverage tells you what ran; mutation tells you what your tests can actually catch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coverage isn't useless — low coverage is a real red flag. But high coverage is not evidence of anything on its own, and AI made it trivially easy to manufacture. Treat the green bar as a starting question, not an answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, then go deeper
&lt;/h2&gt;

&lt;p&gt;Free, self-contained lab: &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I turned the full method into a book + runnable kit — the operating loop, governance, and labs across unit, web/API, instrument-cluster and CAN/UDS domains.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kindle on Amazon ($9.99): &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;https://www.amazon.com/dp/B0HL4SC5PX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Full bundle (PDF + EPUB + labs) on Gumroad ($19): &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;https://nikolaychernev.gumroad.com/l/ai-testing-in-practice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Honest caveat: the lab systems are simulators with &lt;em&gt;declared&lt;/em&gt; defects — they prove the method catches faults, not that any product is certified. That's the point: don't trust things that merely look right.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>qa</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Your AI wrote 40 tests. How many would catch a real bug?</title>
      <dc:creator>Nikolay Chernev</dc:creator>
      <pubDate>Sat, 26 Sep 2026 09:06:52 +0000</pubDate>
      <link>https://dev.to/nikolay_chernev_a67479931/your-ai-wrote-40-tests-how-many-would-catch-a-real-bug-16df</link>
      <guid>https://dev.to/nikolay_chernev_a67479931/your-ai-wrote-40-tests-how-many-would-catch-a-real-bug-16df</guid>
      <description>&lt;p&gt;Your AI assistant just generated a full test suite. It's green. Ship it?&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable part: a passing run confuses three different claims, and only the last one earns trust.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The test runs.&lt;/strong&gt; It executes without error. Table stakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The test agrees with the code.&lt;/strong&gt; The assertions mirror the implementation — including its bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The test tells right from wrong.&lt;/strong&gt; It fails when the behavior is wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI output routinely earns #1 and #2 and skips #3. Accept it anyway and your confidence is &lt;em&gt;borrowed&lt;/em&gt; — trust in the generator standing in for proof from the test.&lt;/p&gt;

&lt;h2&gt;
  
  
  See it in 10 minutes
&lt;/h2&gt;

&lt;p&gt;I built a tiny lab you can run yourself (no deps, Node 20+):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node run-lab.js all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It gives the AI's own suite a pricing function with four seeded defects, then runs an independent verification suite against the same broken code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WEAK TESTS (the AI's suite) .... PASS 3/3
STRONG VERIFICATION ........... FAIL 8/12   &amp;lt;- catches all 4 defects
MUTATION GATE ................. PASS 4/4
LAB GATE: PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI's tests pass against code that's wrong. Verification fails — and that failure is the evidence you actually needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix is a habit, not a tool
&lt;/h2&gt;

&lt;p&gt;Before you trust a generated test, challenge it with a relevant defect: &lt;em&gt;what realistic wrong implementation would still pass this?&lt;/em&gt; If nothing you can think of would make it fail, the test isn't discriminating — it's decoration. Design the discriminating test first, then generate, then prove it catches the fault.&lt;/p&gt;

&lt;p&gt;That loop — clarify, generate, run, &lt;strong&gt;critique&lt;/strong&gt;, repair, review, and a human decides — is the whole method. The generator never approves its own work; an independent reviewer (a different model or session) checks it; seeded defects and mutation testing prove the suite detects faults.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it, then go deeper
&lt;/h2&gt;

&lt;p&gt;The lab is free and self-contained: &lt;a href="https://github.com/chernevnikolay86-wq/ai-test-verification-lab" rel="noopener noreferrer"&gt;https://github.com/chernevnikolay86-wq/ai-test-verification-lab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I turned the full method into a book + runnable kit — labs across unit, web/API, instrument-cluster and CAN/UDS domains, a prompt+agent kit, a governed setup builder, and an offline-model chapter for teams that can't send code to a cloud. It's $19: &lt;a href="https://nikolaychernev.gumroad.com/l/ai-testing-in-practice" rel="noopener noreferrer"&gt;https://nikolaychernev.gumroad.com/l/ai-testing-in-practice&lt;/a&gt; Also on Amazon Kindle ($9.99): &lt;a href="https://www.amazon.com/dp/B0HL4SC5PX" rel="noopener noreferrer"&gt;https://www.amazon.com/dp/B0HL4SC5PX&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Honest caveat: the systems under test are simulators with &lt;em&gt;declared&lt;/em&gt; defects — they prove the method catches faults, not that any real product is certified. That's the point of the book: don't trust things that merely look right.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>ai</category>
      <category>javascript</category>
      <category>qa</category>
    </item>
  </channel>
</rss>
