<?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: BreadWasEaten</title>
    <description>The latest articles on DEV Community by BreadWasEaten (@breadmsa).</description>
    <link>https://dev.to/breadmsa</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%2F3993651%2F65a5f0c2-2f51-44b1-b3f1-1089bd75cd8a.png</url>
      <title>DEV Community: BreadWasEaten</title>
      <link>https://dev.to/breadmsa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/breadmsa"/>
    <language>en</language>
    <item>
      <title>My Test-Selection Tool Skipped 73% of the Suite. That Number Was the Bug.</title>
      <dc:creator>BreadWasEaten</dc:creator>
      <pubDate>Sat, 20 Jun 2026 06:57:06 +0000</pubDate>
      <link>https://dev.to/breadmsa/my-test-selection-tool-skipped-73-of-the-suite-that-number-was-the-bug-49m2</link>
      <guid>https://dev.to/breadmsa/my-test-selection-tool-skipped-73-of-the-suite-that-number-was-the-bug-49m2</guid>
      <description>&lt;p&gt;Test Impact Analysis is a simple promise: don't re-run the tests your change couldn't possibly have broken. Record which tests touch which code once, then use your git diff to run only the affected subset. Google and Meta do it internally; pytest-testmon does a local-dev version. I built an open-source one for pytest called tia — and the most important thing I learned had nothing to do with the algorithm.&lt;/p&gt;

&lt;p&gt;It was this: the first time my benchmark looked great, it was lying to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  A number too good to be true
&lt;/h2&gt;

&lt;p&gt;To prove tia actually saved time, I avoided the trap of a toy demo where every test hits a unique function — that's rigged to look good. I pointed it at Flask, a real third-party suite, and replayed its last 15 real commits. For each one: record the impact map on the parent commit, then measure how much of the 483-test suite tia would skip for that change.&lt;/p&gt;

&lt;p&gt;The result came back: median skip rate 73%. I almost wrote it down and moved on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I didn't celebrate
&lt;/h2&gt;

&lt;p&gt;73% felt too good. Flask is small and tightly coupled — a lot of its tests funnel through the same request-handling core. If I changed that core, a correct selector should pull in most of the suite, not skip three-quarters of it. A number that good, on a codebase that coupled, wasn't a win. It was a symptom.&lt;/p&gt;

&lt;p&gt;So I dug in instead of celebrating. The bug was upstream of my own code, in coverage.py.&lt;/p&gt;

&lt;h2&gt;
  
  
  The false negative wearing a costume
&lt;/h2&gt;

&lt;p&gt;tia records which test touches which line using coverage.py's "dynamic contexts": it switches the active context to each test's id, so the coverage data knows which test executed which line. On Python 3.12+, coverage.py defaults to a fast new measurement core called sysmon (built on sys.monitoring). sysmon, at the time, recorded only the FIRST context to hit each line and dropped the rest.&lt;/p&gt;

&lt;p&gt;Read that again in the context of test selection. If test_a and test_b both call a shared helper, sysmon recorded that the helper's lines belonged to test_a — and silently forgot test_b. So when I later changed that helper, tia "knew" only test_a touched it, and skipped test_b.&lt;/p&gt;

&lt;p&gt;That's not a high skip rate. That's a false negative — the one thing a test-selection tool must never do. The impressive 73% was tia confidently skipping tests that would have caught the change. The costume was a "feature"; underneath was the cardinal sin.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, and the honest number
&lt;/h2&gt;

&lt;p&gt;The fix was to force coverage's C tracer during recording:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;COVERAGE_CORE=ctrace&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It records every context per line. After that, all 483 tests mapped correctly — and the skip rate fell to its honest value: about 21% median on Flask.&lt;/p&gt;

&lt;p&gt;21% is a far less exciting number. It is also a true one. And it taught me to publish the floor, not the ceiling.`&lt;/p&gt;

&lt;h2&gt;
  
  
  But a floor alone is its own kind of lie
&lt;/h2&gt;

&lt;p&gt;Flask is near the worst case for test selection: small, tightly coupled. Publishing only its number would undersell the tool as badly as the 73% oversold it. So I ran the same per-commit replay on boltons — a modular utility library where each module is largely independent.&lt;/p&gt;

&lt;p&gt;There, the honest median skip on real logic changes is about 96%. A change to one function runs the two tests that call it, not the other 400.&lt;/p&gt;

&lt;p&gt;Same tool. The only variable is how decoupled your tests are. So the honest pitch isn't a single number, it's a range:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Flask    (tightly-coupled, worst case):  ~21% median skip&lt;br&gt;
boltons  (modular library):              ~96% median skip&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Most real codebases live somewhere in between. Which means the skip rate is, quietly, a measurement of your own coupling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guarding the sin on purpose
&lt;/h2&gt;

&lt;p&gt;After being burned by a false negative I couldn't see, I stopped trusting "it looks right." I wrote an adversarial test: build a small repo, record the map, then mutate every single covered function and assert that tia re-selects every test that exercises it.&lt;/p&gt;

&lt;p&gt;Then — the part that actually matters — I deliberately broke the selector to confirm the test fails when a false negative is introduced. A guarantee you've never watched fail is just a hope with good posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;None of this makes tia magic. Dynamic dispatch — getattr, eval, reflection — is undecidable in general; tia detects it and degrades to running every test that touches that file, and you should still run the full suite on a cadence. It overlaps heavily with testmon for the common case. Its genuinely distinct edges are narrow: it tracks non-Python dependencies (change a fixture file and it reruns the tests that actually read it), and it's built to share one map across CI machines, which testmon struggles with.&lt;/p&gt;

&lt;p&gt;But the thing I'm proud of isn't a feature. It's that when the tool looked brilliant, I assumed it was broken — and it was. If you build anything that decides what NOT to run, that instinct is the entire job.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;pip install pytest-tia&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Repo, benchmarks, and the full write-ups: &lt;a href="https://github.com/breadMSA/pytest-tia" rel="noopener noreferrer"&gt;https://github.com/breadMSA/pytest-tia&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you read one thing, read the benchmark methodology and try to break it. I'd much rather find the next "73%" from you than from a user who trusted a skipped test.&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>devops</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
