<?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: Vinay Rathod</title>
    <description>The latest articles on DEV Community by Vinay Rathod (@vinay_rathod_0c28f6b8a56f).</description>
    <link>https://dev.to/vinay_rathod_0c28f6b8a56f</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%2F3218346%2Fdcd3e443-a78c-4379-b577-2a2891d7bbe6.jpg</url>
      <title>DEV Community: Vinay Rathod</title>
      <link>https://dev.to/vinay_rathod_0c28f6b8a56f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vinay_rathod_0c28f6b8a56f"/>
    <language>en</language>
    <item>
      <title>Generating unit tests is easy. Proving they help is not.</title>
      <dc:creator>Vinay Rathod</dc:creator>
      <pubDate>Tue, 03 Nov 2026 18:01:38 +0000</pubDate>
      <link>https://dev.to/vinay_rathod_0c28f6b8a56f/generating-unit-tests-is-easy-proving-they-help-is-not-4i0o</link>
      <guid>https://dev.to/vinay_rathod_0c28f6b8a56f/generating-unit-tests-is-easy-proving-they-help-is-not-4i0o</guid>
      <description>&lt;p&gt;&lt;em&gt;What I learned building a measurable workflow for AI-written tests in Go, Java and Python.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every coding agent can now produce files full of test cases. That was the hard part two years ago. It isn't anymore.&lt;/p&gt;

&lt;p&gt;The hard part is everything around it: finding the right project, not clobbering the tests that already exist, proving the new ones actually run, and knowing whether the coverage number went up for a real reason, or because a test now mirrors the implementation line by line.&lt;/p&gt;

&lt;p&gt;I built a small CLI called &lt;code&gt;testgen-unit&lt;/code&gt; around that gap. It works with Go, Java and Python, has no npm runtime dependencies, and uses your project's existing toolchain for validation and coverage.&lt;/p&gt;

&lt;p&gt;It does not try to replace your coding agent. It gives the agent a structured workflow, project context, and deterministic checks. Direct model generation prints suggestions. It does not edit files or run the loop. Your agent, or you, applies the changes. The CLI verifies what actually happened.&lt;/p&gt;

&lt;p&gt;This is the first post on this publication, so let me start with the problem that made me build it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The test that proves nothing
&lt;/h2&gt;

&lt;p&gt;A test that restates the production formula will raise coverage and protect very little.&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;discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pct&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;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&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_discount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected value is computed using the same formula as the implementation. The test repeats the implementation's reasoning instead of stating an independent business expectation.&lt;/p&gt;

&lt;p&gt;If the underlying assumption is wrong, this test may still pass for its chosen input. For example, it says nothing about how fractional results should be rounded.&lt;/p&gt;

&lt;p&gt;A better test states the expectation independently:&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_discount_applies_ten_percent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;90.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one encodes a requirement, not a formula. A useful suite would also cover rounding boundaries and invalid inputs.&lt;/p&gt;

&lt;p&gt;So the workflow has to do more than generate. It has to make the result &lt;strong&gt;inspectable&lt;/strong&gt;: what changed, what ran, what the runner actually said, and whether the new test is distinguishable from the ones already there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;Measure → Generate → Validate → Measure again → Review.&lt;/p&gt;

&lt;p&gt;Five commands. Four are deliberately boring and deterministic; only &lt;code&gt;generate-unit-tests&lt;/code&gt; introduces the uncertainty of model output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;testgen-unit unit-status        &lt;span class="nt"&gt;--language&lt;/span&gt; go &lt;span class="nt"&gt;--check&lt;/span&gt;
testgen-unit unit-coverage      &lt;span class="nt"&gt;--language&lt;/span&gt; go &lt;span class="nt"&gt;--json&lt;/span&gt;
testgen-unit generate-unit-tests &lt;span class="nt"&gt;--language&lt;/span&gt; go &lt;span class="nt"&gt;--module&lt;/span&gt; ./internal/foo
testgen-unit validate-unit-tests &lt;span class="nt"&gt;--language&lt;/span&gt; go &lt;span class="nt"&gt;--forbid-source-changes&lt;/span&gt;
testgen-unit review-unit-tests   &lt;span class="nt"&gt;--language&lt;/span&gt; go &lt;span class="nt"&gt;--fail-on-issues&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That split is the whole point.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;validate-unit-tests&lt;/code&gt; runs the project's own toolchain: &lt;code&gt;go test&lt;/code&gt;, Maven or Gradle, pytest or unittest. It doesn't parse the model's output and trust it. If the suite fails, the command fails.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;unit-coverage&lt;/code&gt; reads a &lt;strong&gt;fresh&lt;/strong&gt; report: a Go coverage profile, &lt;code&gt;coverage.py&lt;/code&gt; JSON, or JaCoCo XML produced by the build it just ran. A stale artifact from an earlier run is rejected rather than counted.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;review-unit-tests&lt;/code&gt; is static and heuristic. It flags sleeps, duplicate Go test names in a package, identical Python test bodies, files changed outside the test scope, and test files with no recognizable assertion. It is explicit that it cannot certify semantic correctness.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;--forbid-source-changes&lt;/code&gt; turns "only edit tests" from a prompt suggestion into a checked constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  The model is the least reliable component
&lt;/h2&gt;

&lt;p&gt;That framing matters, because it changes what you optimise.&lt;/p&gt;

&lt;p&gt;I ran &lt;strong&gt;&lt;code&gt;qwen2.5-coder:14b-32k&lt;/code&gt;&lt;/strong&gt;, about 9 GB, through Ollama on macOS ARM64, against three small demo repositories. Two of the three needed a corrected batch.&lt;/p&gt;

&lt;p&gt;The first Go response &lt;strong&gt;deleted the existing error-path test&lt;/strong&gt;. It was rejected, and a follow-up that explicitly asked to preserve existing tests produced the accepted batch.&lt;/p&gt;

&lt;p&gt;The first Python response &lt;strong&gt;repeated a zero-discount test&lt;/strong&gt; under a new name. That exposed a real gap in the review step, which now detects identical test bodies that differ only in docstrings.&lt;/p&gt;

&lt;p&gt;A later Python "fix" &lt;strong&gt;invented a requirement&lt;/strong&gt;: that fractional percentages raise &lt;code&gt;ValueError&lt;/code&gt;. They don't. Type annotations don't enforce that. Running the suite through the CLI rejected the assertion immediately.&lt;/p&gt;

&lt;p&gt;Java was the counterexample: its first response passed unchanged after I fixed a fixture problem that had committed build artifacts into the demo.&lt;/p&gt;

&lt;p&gt;That is the argument for this design. A model output that never gets corrected in public may simply be output that was never executed.&lt;/p&gt;

&lt;p&gt;With the applicable corrections and setup fixes, each demo moved from a deliberately partial baseline to full coverage:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Validation&lt;/th&gt;
&lt;th&gt;Review&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Statements&lt;/td&gt;
&lt;td&gt;2/3&lt;/td&gt;
&lt;td&gt;3/3&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Lines&lt;/td&gt;
&lt;td&gt;2/3&lt;/td&gt;
&lt;td&gt;3/3&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Statements&lt;/td&gt;
&lt;td&gt;3/4&lt;/td&gt;
&lt;td&gt;4/4&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;td&gt;Pass&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those demos contain &lt;strong&gt;three or four measured statements each&lt;/strong&gt;. They verify that the integration works. They are not a benchmark of production repositories, and I'm not going to pretend otherwise.&lt;/p&gt;




&lt;h2&gt;
  
  
  Don't average across languages
&lt;/h2&gt;

&lt;p&gt;Go and Python report statements. Java reports lines through JaCoCo. Different denominators.&lt;/p&gt;

&lt;p&gt;So the tool never collapses them into one number. Each language is aggregated separately, and a project that failed to measure makes that language's total &lt;strong&gt;unavailable&lt;/strong&gt; rather than quietly weighting it to zero.&lt;/p&gt;

&lt;p&gt;A single "82% overall" across three languages is a number you cannot defend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using a local model
&lt;/h2&gt;

&lt;p&gt;The appeal is obvious: no API key, no source code leaving the machine. The cost is also obvious once you try it.&lt;/p&gt;

&lt;p&gt;Two practical notes from running a 9 GB model on a laptop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raise the timeout.&lt;/strong&gt; Prompt prefill, not token generation, dominates. A 60,000-character prompt is roughly 15–20k tokens before the model writes anything. The CLI defaults to a two-minute request timeout; use something like &lt;code&gt;--timeout 600000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the default output cap.&lt;/strong&gt; It's 4096 tokens. Lowering it makes runs finish faster and produces truncated batches, which is worse than slow. Shrink the &lt;em&gt;scope&lt;/em&gt; instead, with &lt;code&gt;--module&lt;/code&gt; or &lt;code&gt;--package&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;testgen-unit generate-unit-tests &lt;span class="nt"&gt;--language&lt;/span&gt; python &lt;span class="nt"&gt;--file&lt;/span&gt; price.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provider&lt;/span&gt; ollama &lt;span class="nt"&gt;--model&lt;/span&gt; YOUR_INSTALLED_MODEL &lt;span class="nt"&gt;--timeout&lt;/span&gt; 600000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"Cover validation and error paths"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail worth knowing: &lt;code&gt;--file&lt;/code&gt; scopes &lt;strong&gt;prompt context and static review&lt;/strong&gt;, not validation or coverage. Validation runs the project's suite, which is what you want, but it means an unrelated broken test in the repo will fail your run. Fix the baseline first, or you'll blame the model for a pre-existing failure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Limits I'd rather state up front
&lt;/h2&gt;

&lt;p&gt;Review is heuristic. It catches recognizable mistakes, not missing regression value.&lt;/p&gt;

&lt;p&gt;Coverage is a denominator, not a judgement. Read the assertions.&lt;/p&gt;

&lt;p&gt;Java coverage requires an existing JaCoCo setup that emits XML. The tool does not inject build plugins into your project.&lt;/p&gt;

&lt;p&gt;Findings in files you didn't touch are labelled &lt;code&gt;[baseline]&lt;/code&gt;, so you can separate "the repo was already like this" from "the model just did this."&lt;/p&gt;

&lt;p&gt;Validation executes project code. Only run it on repositories you trust.&lt;/p&gt;




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

&lt;p&gt;You need Node.js 22+, npm and Git, plus the toolchain for the language you're testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; testgen-unit
&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your/repository
testgen-unit doctor &lt;span class="nt"&gt;--language&lt;/span&gt; go
testgen-unit install-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask your agent to measure, generate, validate and review, or drive the commands yourself.&lt;/p&gt;

&lt;p&gt;The point was never to generate more tests. It was to make each generated test survive contact with a real toolchain, and to leave behind evidence that it did.&lt;/p&gt;

&lt;p&gt;If you try it on a real repository, I'd genuinely like to know where it breaks. That's the part that makes the tool better.&lt;/p&gt;




&lt;p&gt;&lt;code&gt;testgen-unit&lt;/code&gt; is MIT-licensed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/iamvinay92/testgen-unit" rel="noopener noreferrer"&gt;github.com/iamvinay92/testgen-unit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NPM package:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/testgen-unit" rel="noopener noreferrer"&gt;npmjs.com/package/testgen-unit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this was useful, subscribe. The next post will be a full teardown of one real repository run, including the tests the model got wrong.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>unittesting</category>
      <category>aillm</category>
      <category>testgen</category>
    </item>
  </channel>
</rss>
