<?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>Forem: r4mimu</title>
    <description>The latest articles on Forem by r4mimu (@r4mimu).</description>
    <link>https://forem.com/r4mimu</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3858473%2F70db7fef-9864-42a3-8ccd-97b2d26d759a.jpg</url>
      <title>Forem: r4mimu</title>
      <link>https://forem.com/r4mimu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/r4mimu"/>
    <language>en</language>
    <item>
      <title>Your Go Tests Pass, But Do They Actually Test Anything? An Introduction to Mutation Testing</title>
      <dc:creator>r4mimu</dc:creator>
      <pubDate>Fri, 03 Apr 2026 00:09:32 +0000</pubDate>
      <link>https://forem.com/r4mimu/your-go-tests-pass-but-do-they-actually-test-anything-an-introduction-to-mutation-testing-1k9l</link>
      <guid>https://forem.com/r4mimu/your-go-tests-pass-but-do-they-actually-test-anything-an-introduction-to-mutation-testing-1k9l</guid>
      <description>&lt;p&gt;GitHub Copilot, Cursor, Claude Code — these tools can generate hundreds of lines of Go in seconds. But there's a problem that doesn't get enough attention: AI-generated code ships with AI-generated confidence, not correctness.&lt;/p&gt;

&lt;p&gt;Your test suite says "all green." Your coverage report says 85%. But how many of those tests actually catch real bugs? How many are just going through the motions — executing code paths without verifying behavior?&lt;/p&gt;

&lt;p&gt;This is where mutation testing comes in. And this is why I built &lt;a href="https://github.com/fchimpan/mutest" rel="noopener noreferrer"&gt;mutest&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Cost of AI-Assisted Development
&lt;/h2&gt;

&lt;p&gt;AI coding assistants are good at producing code that &lt;em&gt;looks&lt;/em&gt; correct. They generate functions with proper signatures, idiomatic error handling, and even test files. But there's a gap: AI tends to produce tests that cover code paths rather than verify boundaries.&lt;/p&gt;

&lt;p&gt;Consider this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;IsEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An AI might generate tests like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestIsEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IsEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"IsEligibleForDiscount(15) = %v, want true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IsEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"IsEligibleForDiscount(3) = %v, want false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Coverage: 100%. Every line is executed. CI is green. Ship it.&lt;/p&gt;

&lt;p&gt;But what happens if someone accidentally changes &lt;code&gt;quantity &amp;gt;= 10&lt;/code&gt; to &lt;code&gt;quantity &amp;gt; 10&lt;/code&gt;? Both tests still pass. The boundary case — &lt;code&gt;IsEligibleForDiscount(10)&lt;/code&gt; — was never tested. A customer ordering exactly 10 items would silently lose their discount, and your test suite would never notice.&lt;/p&gt;

&lt;p&gt;This is not a contrived example. Boundary-value and equality bugs are among the most common defects in production software, and they're the kind of bug that AI-generated tests routinely miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mutation Testing: The Test for Your Tests
&lt;/h2&gt;

&lt;p&gt;Mutation testing flips the question. Instead of "does my code pass the tests?", it asks "do my tests catch bugs?"&lt;/p&gt;

&lt;p&gt;The concept is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take your source code&lt;/li&gt;
&lt;li&gt;Introduce a small, deliberate change (a "mutation") — like swapping &lt;code&gt;&amp;gt;=&lt;/code&gt; for &lt;code&gt;&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run your test suite against the mutated code&lt;/li&gt;
&lt;li&gt;If the tests fail, the mutant is "killed" — your tests caught the bug&lt;/li&gt;
&lt;li&gt;If the tests pass, the mutant has "survived" — you have a test gap&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every surviving mutant points to a specific line where a bug could hide undetected. Unlike line coverage, which just tells you what code ran, this tells you whether your tests actually noticed the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters Now
&lt;/h3&gt;

&lt;p&gt;In the pre-AI era, developers wrote code slowly and typically had intuition about boundary conditions because they had reasoned through the logic themselves. AI-generated code skips that reasoning step. The code appears, the tests appear, and the developer reviews both — but the boundary considerations are often missing from both.&lt;/p&gt;

&lt;p&gt;Mutation testing fills that gap. It's the automated version of a senior engineer asking, "But what happens when the value is exactly 10?"&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Existing Mutation Testing Tools
&lt;/h2&gt;

&lt;p&gt;If mutation testing is so valuable, why isn't everyone doing it? Because existing tools make it impractical for real-world CI pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  They're Too Slow
&lt;/h3&gt;

&lt;p&gt;Traditional mutation testing tools generate thousands of mutants — mutating arithmetic operators, boolean returns, assignments, method calls, and more. For a medium-sized Go project, this easily means 5,000-10,000+ mutants. Each one requires recompilation and a full test run. Even with optimization, you're looking at 30-60 minutes of CI time.&lt;/p&gt;

&lt;p&gt;Nobody runs a 45-minute mutation test on every pull request.&lt;/p&gt;

&lt;h3&gt;
  
  
  They're Too Noisy
&lt;/h3&gt;

&lt;p&gt;More mutation operators mean more surviving mutants — but not all surviving mutants represent real test gaps. Mutating &lt;code&gt;x + y&lt;/code&gt; to &lt;code&gt;x - y&lt;/code&gt; in a logging format string is technically a surviving mutant, but it doesn't point to a meaningful test gap. When your mutation report has 200 survivors and only 15 are actionable, developers stop reading the report.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go-Specific Pain Points
&lt;/h3&gt;

&lt;p&gt;The Go ecosystem has a few mutation testing tools, but they share common limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Per-mutant recompilation: Most tools modify source files and rebuild for every single mutant. Go's compilation is fast, but &lt;code&gt;N mutations * (compile + test)&lt;/code&gt; adds up quickly across a whole project.&lt;/li&gt;
&lt;li&gt;Source file modification: Some tools directly modify &lt;code&gt;.go&lt;/code&gt; files, creating race conditions with IDE file watchers, breaking &lt;code&gt;gopls&lt;/code&gt;, and risking corrupted source if the process is interrupted mid-run.&lt;/li&gt;
&lt;li&gt;Kitchen-sink mutation strategies: Trying to mutate everything — arithmetic, assignments, returns, conditionals — generates way too many mutants, most of which aren't useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: developers try mutation testing once, wait 20 minutes for a noisy report, and never run it again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing mutest
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/fchimpan/mutest" rel="noopener noreferrer"&gt;mutest&lt;/a&gt; is a mutation testing tool for Go that takes a different approach. Instead of mutating everything, it focuses on the operators that actually matter — and runs fast enough for CI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mutest ./...
&lt;span class="go"&gt;mutest: discovered 4 mutation points
mutest: testing with 10 workers, 30s timeout per mutant

&lt;/span&gt;&lt;span class="gp"&gt;--- SURVIVED: calc.go:5:7  &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.21s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;--- SURVIVED: calc.go:21:7  &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.21s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;--- SURVIVED: calc.go:18:7  &amp;lt; to &amp;lt;= (0.21s)
&lt;/span&gt;&lt;span class="gp"&gt;--- KILLED: calc.go:13:11  &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.63s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;
===== Mutation Testing Summary =====
Total:     4
Killed:    1
Survived:  3
Score:     25.0%
Duration:  633ms
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;633 milliseconds. Not minutes. Milliseconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Makes mutest Different
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Focused Mutation Strategy
&lt;/h3&gt;

&lt;p&gt;mutest targets Relational Operator Replacement (ROR) — comparison and equality operators only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;  →  &amp;gt;=
&amp;gt;=  →  &amp;gt;
&amp;lt;  →  &amp;lt;=
&amp;lt;=  →  &amp;lt;
==  →  !=
!=  →  ==
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a limitation — it's a deliberate choice. Off-by-one and equality bugs are some of the most common defects in production Go code, and these six mutations target exactly those. The academic literature on mutation testing backs this up: ROR is one of the most cost-effective mutation operator subsets.&lt;/p&gt;

&lt;p&gt;By limiting scope, mutest keeps the mutant count small — typically 10-50 per package instead of thousands. Every survivor is something you should actually look at.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Runtime Mutation Selection (Compile Once, Run Many)
&lt;/h3&gt;

&lt;p&gt;Traditional tools follow this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each mutant:
    1. Modify source file
    2. Compile package
    3. Run tests
    4. Restore source file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mutest does it differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each package:
    1. Instrument all mutation points with generic helper functions
    2. Compile ONE test binary with all mutations embedded
For each mutant:
    3. Run the pre-built binary with MUTEST_ID=N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, a comparison like &lt;code&gt;a &amp;gt; b&lt;/code&gt; is replaced with a generic helper function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;_mutest_cmp_1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;cmp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordered&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_mutest_init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_mutest_active&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;1&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;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;  &lt;span class="c"&gt;// mutated&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;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;  &lt;span class="c"&gt;// original&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package is compiled once with all these helpers embedded. Each mutant is then activated by running the same binary with a different environment variable: &lt;code&gt;MUTEST_ID=1&lt;/code&gt;, &lt;code&gt;MUTEST_ID=2&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;The cost goes from &lt;code&gt;N mutations * (compile + run)&lt;/code&gt; to &lt;code&gt;P packages * compile + N mutations * run&lt;/code&gt;. For a package with 30 mutations, that's 1 compilation instead of 30.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Non-Destructive by Design
&lt;/h3&gt;

&lt;p&gt;mutest never modifies your source files. All instrumented code lives in temporary files, and Go's &lt;code&gt;-overlay&lt;/code&gt; flag tells the compiler to use them instead of the originals. If the process is killed, interrupted, or crashes — your source code is untouched. Your IDE stays happy. Your &lt;code&gt;git status&lt;/code&gt; stays clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Smart Noise Reduction
&lt;/h3&gt;

&lt;p&gt;mutest automatically skips mutations that would produce false positives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;len(x) &amp;gt; 0&lt;/code&gt; to &lt;code&gt;len(x) &amp;gt;= 0&lt;/code&gt;: &lt;code&gt;len()&lt;/code&gt; never returns a negative value, so this mutation can never change behavior. Skipped.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cap(x) &amp;gt; 0&lt;/code&gt; to &lt;code&gt;cap(x) &amp;gt;= 0&lt;/code&gt;: Same reasoning — &lt;code&gt;cap()&lt;/code&gt; is always non-negative. Skipped.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;if err != nil { return err }&lt;/code&gt;: Go's idiomatic error propagation. Mutating these generates noise without revealing meaningful test gaps. Skipped by default (configurable with &lt;code&gt;-skip-err-propagation=false&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparisons against non-zero values (like &lt;code&gt;len(s) &amp;gt; 1&lt;/code&gt;) are not skipped — the boundary between 1 and 2 matters and should be tested.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Zero External Dependencies
&lt;/h3&gt;

&lt;p&gt;mutest is built entirely on the Go standard library — &lt;code&gt;go/ast&lt;/code&gt;, &lt;code&gt;go/parser&lt;/code&gt;, &lt;code&gt;go/token&lt;/code&gt;, &lt;code&gt;os/exec&lt;/code&gt;, and friends. No third-party dependencies. &lt;code&gt;go install&lt;/code&gt; and you're done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started with mutest
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/fchimpan/mutest@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requires Go 1.24 or later. Pre-built binaries for Linux, macOS, and Windows are also available on the &lt;a href="https://github.com/fchimpan/mutest/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt; page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run against all packages (same syntax as go test)&lt;/span&gt;
mutest ./...

&lt;span class="c"&gt;# Target a specific package&lt;/span&gt;
mutest ./pkg/handler

&lt;span class="c"&gt;# Show test output for each mutant&lt;/span&gt;
mutest &lt;span class="nt"&gt;-v&lt;/span&gt; ./...

&lt;span class="c"&gt;# Preview mutations without running tests&lt;/span&gt;
mutest &lt;span class="nt"&gt;-dry-run&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output format mirrors &lt;code&gt;go test&lt;/code&gt;, so it feels familiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;--- KILLED: handler.go:25:11  &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.42s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;--- SURVIVED: handler.go:44:9  &amp;lt; to &amp;lt;= (0.19s)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI Integration
&lt;/h3&gt;

&lt;p&gt;mutest is designed to work as a CI quality gate. Two practical patterns:&lt;/p&gt;

&lt;p&gt;Pattern 1 — diff-only mutation testing. Only mutate lines changed in the current pull request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mutest &lt;span class="nt"&gt;-diff&lt;/span&gt; origin/main &lt;span class="nt"&gt;-threshold&lt;/span&gt; 100 ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This says: "For every comparison operator I changed or added, there must be a test that covers the boundary." If any changed comparison survives mutation, the pipeline fails. If the diff contains no mutation targets (e.g., only comments or non-Go files changed), mutest exits 0 — no false failures.&lt;/p&gt;

&lt;p&gt;Pattern 2 — full-project threshold. Set a minimum mutation score for the entire project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mutest &lt;span class="nt"&gt;-threshold&lt;/span&gt; 80 ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Actions Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Mutation Testing&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mutest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# Full history needed for -diff&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-go@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;go-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go install github.com/fchimpan/mutest@latest&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mutest -diff origin/main -threshold 100 ./...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Under a minute of CI time for most projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controlling Scope with &lt;code&gt;//mutest:skip&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Not every comparison needs mutation testing. Use the &lt;code&gt;//mutest:skip&lt;/code&gt; directive to exclude code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Skip an entire function&lt;/span&gt;
&lt;span class="c"&gt;//mutest:skip&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;legacyCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&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;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Skip a block (if/for/switch/select) — skips all nested statements too&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;//mutest:skip&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Canceled&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="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrCanceled&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fetch: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Skip a single line&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;//mutest:skip&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JSON Output for Tooling
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Single JSON summary&lt;/span&gt;
mutest &lt;span class="nt"&gt;-json&lt;/span&gt; ./...

&lt;span class="c"&gt;# Streaming NDJSON (one line per mutant as results arrive)&lt;/span&gt;
mutest &lt;span class="nt"&gt;-json&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON output makes it easy to integrate mutest with custom dashboards, Slack notifications, or code review tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fixing a Survived Mutant
&lt;/h2&gt;

&lt;p&gt;When mutest reports a survivor, it tells you exactly where to look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;--- SURVIVED: pricing.go:12:7  &amp;gt;&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; to &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.21s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means mutest changed &lt;code&gt;&amp;gt;=&lt;/code&gt; to &lt;code&gt;&amp;gt;&lt;/code&gt; and no test noticed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;IsEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;// mutest changed this to &amp;gt;, tests still passed&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix — add a test at the boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestIsEligibleForDiscount_ExactlyTen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// This test kills the &amp;gt;= to &amp;gt; mutation because&lt;/span&gt;
    &lt;span class="c"&gt;// IsEligibleForDiscount(10) returns true with &amp;gt;=, but false with &amp;gt;.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;IsEligibleForDiscount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"IsEligibleForDiscount(10) = %v, want true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;got&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-run mutest and that mutation point will now show &lt;code&gt;--- KILLED&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Impact
&lt;/h2&gt;

&lt;p&gt;I've been running mutest in CI on Go projects. The most obvious thing it finds: off-by-one errors in pagination, boundary checks in rate limiting, equality comparisons that should be inequalities. Bugs that 100% line coverage misses entirely.&lt;/p&gt;

&lt;p&gt;The less obvious effect: it changes how developers write tests. Once you start seeing surviving mutants, you naturally start adding boundary-value tests. You stop writing tests that just exercise the happy path.&lt;/p&gt;

&lt;p&gt;Speed matters more than you'd think here. A mutation testing tool that takes 30 minutes gets disabled after the first sprint. mutest finishes in seconds, so it stays on. And with diff mode, you don't need to retroactively hit a high mutation score across your whole project — just enforce it on new and changed code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;AI coding assistants are making us more productive, but they're also making it easier to ship code with untested boundary conditions. Line coverage tells you what code &lt;em&gt;ran&lt;/em&gt;; mutation testing tells you what code was actually &lt;em&gt;verified&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Think of it this way: &lt;code&gt;go vet&lt;/code&gt; catches code that compiles but is probably wrong. mutest catches tests that pass but probably aren't thorough enough. It's the next layer of quality assurance for your Go project.&lt;/p&gt;

&lt;p&gt;mutest brings mutation testing to Go without the traditional pain points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focused scope — targets comparison and equality operators, the mutations that catch real bugs&lt;/li&gt;
&lt;li&gt;Fast — compiles once per package, runs many; finishes in seconds, not minutes&lt;/li&gt;
&lt;li&gt;Non-destructive — never touches your source files; uses Go's &lt;code&gt;-overlay&lt;/code&gt; flag&lt;/li&gt;
&lt;li&gt;CI-ready — &lt;code&gt;-diff&lt;/code&gt;, &lt;code&gt;-threshold&lt;/code&gt;, and &lt;code&gt;-json&lt;/code&gt; flags built in&lt;/li&gt;
&lt;li&gt;Zero dependencies — pure Go standard library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your tests are passing but you're not sure they're actually &lt;em&gt;testing&lt;/em&gt;, give mutest a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/fchimpan/mutest@latest
mutest ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might be surprised how many mutants survive.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;mutest is open source under the MIT license. Contributions, issues, and stars are welcome at &lt;a href="https://github.com/fchimpan/mutest" rel="noopener noreferrer"&gt;github.com/fchimpan/mutest&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>codequality</category>
      <category>go</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
