<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blog on gotest</title><link>https://mvrahden.github.io/go-test/blog/</link><description>Recent content in Blog on gotest</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://mvrahden.github.io/go-test/blog/feed.xml" rel="self" type="application/rss+xml"/><item><title>How Go Actually Tests: Data From 1,000 Repositories</title><link>https://mvrahden.github.io/go-test/blog/how-go-actually-tests/</link><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/how-go-actually-tests/</guid><description>&lt;p&gt;The tests of the 1,000 most-starred Go repositories spend at least &lt;strong&gt;251 hours asleep&lt;/strong&gt;. Not blocked on I/O, not waiting for a condition. Sleeping, in &lt;code&gt;time.Sleep&lt;/code&gt; calls with constant durations, written into test code on purpose. That is ten and a half days of deliberate waiting per full run of the corpus, and it is a floor: we could only count durations that are compile-time constants.&lt;/p&gt;
&lt;p&gt;We measured this because we couldn&amp;rsquo;t find anyone who had. There is no shortage of opinions about how Go tests should be written. There is very little data about how they are written. So we parsed them.&lt;/p&gt;</description></item><item><title>gotest VS Code Extension: Suite-Aware Go Testing</title><link>https://mvrahden.github.io/go-test/blog/your-editor-knows-your-tests/</link><pubDate>Thu, 23 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/your-editor-knows-your-tests/</guid><description>&lt;p&gt;You write gotest suites, open VS Code&amp;rsquo;s Testing sidebar — and it&amp;rsquo;s empty. No test tree, no CodeLens Run buttons above your suite methods, nothing to debug. Standard Go tooling has nothing to show you, because as far as gopls and the Go Test Explorer are concerned, your suites aren&amp;rsquo;t tests at all.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=mvrahden.gotest"&gt;gotest VS Code extension&lt;/a&gt; exists to close this gap. Because it&amp;rsquo;s purpose-built for suite-based testing rather than a generic test runner, it can surface things standard tooling can&amp;rsquo;t: the suite hierarchy, the behavioral spec view, and coverage that survives across runs.&lt;/p&gt;</description></item><item><title>Go Tests in GitHub Actions: The gotest CI Setup Guide</title><link>https://mvrahden.github.io/go-test/blog/gotest-in-ci/</link><pubDate>Wed, 22 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/gotest-in-ci/</guid><description>&lt;p&gt;Running gotest locally is straightforward. Making it work well in CI — with clear failure output, PR annotations, coverage enforcement, and safety guards — takes a bit of setup. This post covers the official GitHub Action, the &lt;code&gt;summary&lt;/code&gt; command, coverage thresholds, and the CI mode that catches debugging artifacts before they reach your main branch.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t written a gotest suite yet, start with &lt;a href="https://mvrahden.github.io/go-test/blog/zero-to-suite/"&gt;Your First Go Test Suite in 10 Minutes&lt;/a&gt; and come back — everything here builds on that workflow.&lt;/p&gt;</description></item><item><title>Contract Testing Go Interfaces with Generic Test Suites</title><link>https://mvrahden.github.io/go-test/blog/contract-testing-generics/</link><pubDate>Tue, 21 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/contract-testing-generics/</guid><description>&lt;p&gt;Go interfaces define contracts. An &lt;code&gt;io.Reader&lt;/code&gt; promises &lt;code&gt;Read(p []byte) (n int, err error)&lt;/code&gt;. A &lt;code&gt;Storage&lt;/code&gt; interface promises &lt;code&gt;Get&lt;/code&gt;, &lt;code&gt;Put&lt;/code&gt;, &lt;code&gt;Delete&lt;/code&gt;. But who verifies that every implementation actually honors the contract? Usually, each implementation has its own tests, written independently, checking slightly different things. The contract drifts.&lt;/p&gt;
&lt;p&gt;Generic suites fix this. Write one test suite parameterized by a type, then instantiate it for every implementation. The same behaviors are verified against every implementation. If you add a contract requirement, every implementation is tested. If an implementation deviates, the specific behavior that fails is named in the spec.&lt;/p&gt;</description></item><item><title>Advanced Go Test Fixtures: Composition, Containers, Config</title><link>https://mvrahden.github.io/go-test/blog/advanced-fixture-patterns/</link><pubDate>Mon, 20 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/advanced-fixture-patterns/</guid><description>&lt;p&gt;A single fixture gets you started: start a thing, stop a thing, point your tests at it. Real projects need more. Fixtures that compose into dependency graphs, containers that survive flaky startup, per-test isolation that keeps parallel tests from stepping on each other, and configuration that adapts to each environment. These are the patterns that emerge when fixtures grow beyond a single struct with two hooks.&lt;/p&gt;
&lt;p&gt;If you are new to gotest fixtures, &lt;a href="https://mvrahden.github.io/go-test/blog/test-fixtures-in-go/"&gt;Test Fixtures in Go&lt;/a&gt; covers the fundamentals: struct types with lifecycle hooks, automatic dependency resolution, DAG-based ordering. &lt;a href="https://mvrahden.github.io/go-test/blog/shared-fixtures/"&gt;Sharing Test Fixtures Across Go Packages&lt;/a&gt; covers cross-package sharing through JSON state transfer and subprocess lifecycle management. This post assumes you have built your first fixture.&lt;/p&gt;</description></item><item><title>Sharing Test Fixtures Across Go Packages</title><link>https://mvrahden.github.io/go-test/blog/shared-fixtures/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/shared-fixtures/</guid><description>&lt;p&gt;&lt;code&gt;go test&lt;/code&gt; compiles each package into a separate test binary and runs it as a separate OS process. That design buys you isolation, and it makes one thing structurally difficult: sharing a fixture &lt;em&gt;across&lt;/em&gt; packages. A Postgres container that &lt;code&gt;pkg/user&lt;/code&gt;, &lt;code&gt;pkg/order&lt;/code&gt;, and &lt;code&gt;pkg/billing&lt;/code&gt; all need. A Redis instance that three packages query. A schema migration that should run once for the entire test run, not once per package. Sharing any of these means crossing process boundaries.&lt;/p&gt;</description></item><item><title>Go Tests as Living Documentation: The Spec View</title><link>https://mvrahden.github.io/go-test/blog/tests-as-documentation/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/tests-as-documentation/</guid><description>&lt;p&gt;Documentation drifts. A README written six months ago describes code that no longer exists. An architecture doc from last quarter names services that have been renamed. Wiki pages go stale the moment they are published.&lt;/p&gt;
&lt;p&gt;Tests don&amp;rsquo;t drift. If the code changes and the test doesn&amp;rsquo;t match, the build fails. This makes tests the most reliable form of documentation in any project — if you can read them. The problem is that most test output is designed for machines, not humans. &lt;code&gt;go test -v&lt;/code&gt; produces a flat stream of PASS/FAIL lines with no hierarchy. You can see that everything passed, but you can&amp;rsquo;t see what the system &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Snapshot Testing in Go</title><link>https://mvrahden.github.io/go-test/blog/snapshot-testing-in-go/</link><pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/snapshot-testing-in-go/</guid><description>&lt;p&gt;Some test assertions are about structure, not specific values. JSON API responses, rendered HTML, error messages, log output — things where the exact expected value is long, tedious to maintain by hand, and changes often enough to be annoying. Snapshot testing solves this by storing the expected output in a file and comparing against it on subsequent runs.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve used Jest, you know the pattern. In Go, it&amp;rsquo;s less common — but &lt;a href="https://github.com/mvrahden/go-test"&gt;gotest&lt;/a&gt; has a built-in implementation that&amp;rsquo;s thread-safe, works inside parallel tests, and integrates with CI safety guards.&lt;/p&gt;</description></item><item><title>Testing Async Code in Go Without time.Sleep</title><link>https://mvrahden.github.io/go-test/blog/testing-async-go/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/testing-async-go/</guid><description>&lt;p&gt;Most Go code that tests async behavior uses &lt;code&gt;time.Sleep&lt;/code&gt;. It&amp;rsquo;s slow, flaky, or both. The real problem is that sleeping conflates two separate concerns: how long to wait and how often to check. This post shows how to separate them with &lt;code&gt;Eventually&lt;/code&gt; and &lt;code&gt;Consistently&lt;/code&gt;, and why that separation makes async tests both faster and more reliable.&lt;/p&gt;
&lt;h2 id="the-sleep-problem"&gt;The sleep problem&lt;/h2&gt;
&lt;p&gt;Suppose you have a notification dispatcher that processes messages in a background goroutine. You send a notification and want to assert that it was delivered. The most common approach:&lt;/p&gt;</description></item><item><title>Go Test Watch Mode and Focused Tests: The Inner Loop</title><link>https://mvrahden.github.io/go-test/blog/the-inner-loop/</link><pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/the-inner-loop/</guid><description>&lt;p&gt;The &amp;ldquo;inner loop&amp;rdquo; is the cycle a developer repeats hundreds of times a day: change code, run tests, read results. The speed of this loop directly affects how you write code. A 30-second cycle means you batch changes and debug in your head. A 1-second cycle means you try things.&lt;/p&gt;
&lt;p&gt;gotest has three features that compress the inner loop: watch mode for automatic re-runs, focus prefixes for narrowing scope, and spec output for readable results. Separately, each one removes a specific friction point. Together, they change how you develop.&lt;/p&gt;</description></item><item><title>Go Test Lifecycle: Setup, Teardown, and Execution Order</title><link>https://mvrahden.github.io/go-test/blog/go-test-lifecycle/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/go-test-lifecycle/</guid><description>&lt;p&gt;Every test framework has setup and teardown. The interesting question is not &lt;em&gt;whether&lt;/em&gt; they exist, but what guarantees the lifecycle gives you. Can &lt;code&gt;AfterAll&lt;/code&gt; run if &lt;code&gt;BeforeAll&lt;/code&gt; panics? Does &lt;code&gt;AfterEach&lt;/code&gt; still execute on &lt;code&gt;t.Fatal&lt;/code&gt;? Where do fixture hooks fit relative to suite hooks? If you have ever guessed at these answers while debugging a leaked database connection, this post is for you.&lt;/p&gt;
&lt;p&gt;Other posts in this series cover &lt;a href="https://mvrahden.github.io/go-test/blog/test-fixtures-in-go/"&gt;fixture patterns&lt;/a&gt; and &lt;a href="https://mvrahden.github.io/go-test/blog/zero-to-suite/"&gt;getting started with suites&lt;/a&gt;. This post is different. It maps the full lifecycle from the inside out, so you build the correct mental model once and stop guessing.&lt;/p&gt;</description></item><item><title>BDD in Go: What Behavior-Driven Development Actually Means</title><link>https://mvrahden.github.io/go-test/blog/what-bdd-means-in-go/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/what-bdd-means-in-go/</guid><description>&lt;p&gt;BDD — Behavior-Driven Development — is one of the most misunderstood ideas in testing. Most developers encounter it as syntax: &lt;code&gt;describe&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;. They learn the keywords, use them in a few test files, and assume they are doing BDD. But BDD is not syntax. It is a communication practice. And Go&amp;rsquo;s type system makes it possible to do BDD without the ceremony that other languages require.&lt;/p&gt;
&lt;p&gt;This is not a tutorial on &lt;code&gt;When&lt;/code&gt; and &lt;code&gt;It&lt;/code&gt; — that is covered in &lt;a href="https://mvrahden.github.io/go-test/blog/readable-tests-with-bdd/"&gt;Readable Go Tests with BDD-Style Subtests&lt;/a&gt;. This post is about the idea behind BDD, why most implementations of it carry unnecessary weight, and how Go&amp;rsquo;s type system maps to BDD concepts in a way that is unique among programming languages.&lt;/p&gt;</description></item><item><title>Your First Go Test Suite in 10 Minutes: A gotest Tutorial</title><link>https://mvrahden.github.io/go-test/blog/zero-to-suite/</link><pubDate>Sun, 12 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/zero-to-suite/</guid><description>&lt;p&gt;Plain &lt;code&gt;go test&lt;/code&gt; carries you a long way — until you need setup that runs before every test, related cases grouped under a shared context, and output you can actually read. At that point most of us start hand-rolling lifecycle helpers and squinting at walls of &lt;code&gt;--- PASS&lt;/code&gt; lines. In the next 10 minutes you&amp;rsquo;ll go from &lt;code&gt;go install&lt;/code&gt; to a structured test suite with lifecycle hooks, BDD-style grouping, and spec output — all running on standard &lt;code&gt;go test&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Why Your Go Tests Are Slow (and What to Do About It)</title><link>https://mvrahden.github.io/go-test/blog/go-testing-at-scale/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/go-testing-at-scale/</guid><description>&lt;p&gt;&lt;code&gt;go test ./...&lt;/code&gt; takes 90 seconds. You&amp;rsquo;ve profiled the hot tests and they&amp;rsquo;re fast. The database setup is cached. The assertions are trivial. So where does the time go?&lt;/p&gt;
&lt;p&gt;Usually the answer isn&amp;rsquo;t slow tests. It&amp;rsquo;s slow structure: the way tests are organized determines how much work runs sequentially, how much setup gets repeated, and how much parallelism the runtime can actually use. This post looks at the three structural patterns that make Go test suites slow, and what to do about each one. The payoff is real: the benchmark later in the post takes the same example suite from 6 seconds to 0.5 seconds through structural changes alone, without touching a single test.&lt;/p&gt;</description></item><item><title>Migrating from `testify/suite` to gotest</title><link>https://mvrahden.github.io/go-test/blog/testify-migration-guide/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/testify-migration-guide/</guid><description>&lt;p&gt;&lt;code&gt;gotest migrate ./...&lt;/code&gt; rewrites most of a testify/suite codebase automatically: struct renames, lifecycle hooks, assertion calls, imports, and the &lt;code&gt;suite.Run&lt;/code&gt; boilerplate. The catch is a handful of patterns it can&amp;rsquo;t convert safely and leaves for manual review. This guide shows what the tool converts, what it leaves for you, and how to migrate package by package without breaking the rest of the codebase.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;testify/suite&lt;/code&gt; is the most widely used Go test suite framework, and for good reason. It gives you struct-based test grouping and lifecycle hooks on top of the standard library. Many teams have hundreds of suites built on it. This guide is for teams that have decided to try gotest alongside or in place of those suites.&lt;/p&gt;</description></item><item><title>Code Generation vs Reflection in Go Test Frameworks</title><link>https://mvrahden.github.io/go-test/blog/code-generation-not-reflection/</link><pubDate>Thu, 09 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/code-generation-not-reflection/</guid><description>&lt;p&gt;Every Go test framework that supports suites needs to solve the same problem: how do you connect a struct full of test methods to the &lt;code&gt;go test&lt;/code&gt; runner? The runner only knows about top-level &lt;code&gt;func Test*(t *testing.T)&lt;/code&gt; functions. Your suite is a struct with methods. Something has to bridge the gap.&lt;/p&gt;
&lt;p&gt;Most frameworks use reflection. gotest uses code generation. This post explains why, and what the code generation pipeline actually does.&lt;/p&gt;</description></item><item><title>Readable Go Tests with BDD-Style Subtests</title><link>https://mvrahden.github.io/go-test/blog/readable-tests-with-bdd/</link><pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/readable-tests-with-bdd/</guid><description>&lt;p&gt;When a test fails in CI, the first thing you read is its name. And for most Go projects, that name looks like this:&lt;/p&gt;
&lt;div class="gotest-block"&gt;
 &lt;span class="gotest-label"&gt;go test -v output&lt;/span&gt;
 &lt;pre class="gotest-body"&gt;
=== RUN TestCreateUser_WhenEmailInvalid_ReturnsError
--- FAIL: TestCreateUser_WhenEmailInvalid_ReturnsError (0.01s)
&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;You can decode it. &lt;code&gt;TestCreateUser&lt;/code&gt; is the subject, &lt;code&gt;WhenEmailInvalid&lt;/code&gt; is the condition, &lt;code&gt;ReturnsError&lt;/code&gt; is the expectation. But it takes a moment. And when you&amp;rsquo;re scanning twenty failures across five packages, those moments add up.&lt;/p&gt;
&lt;p&gt;This post looks at why Go test output is hard to read at scale, how BDD-style structure helps, and what it takes to turn test runs into something that reads like a specification.&lt;/p&gt;</description></item><item><title>Test Fixtures in Go: Patterns That Scale</title><link>https://mvrahden.github.io/go-test/blog/test-fixtures-in-go/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/test-fixtures-in-go/</guid><description>&lt;p&gt;Most Go projects eventually need shared test infrastructure: a database connection, a message queue, a container that takes seconds to start. The stdlib doesn&amp;rsquo;t have a built-in answer for this. So every team invents its own fixture pattern, and most of those patterns quietly break the day you add &lt;code&gt;t.Parallel()&lt;/code&gt; or a second package that needs the same Postgres.&lt;/p&gt;
&lt;p&gt;This post looks at the common approaches, where each one breaks down, and what a fixture system needs to handle the cases that real projects run into.&lt;/p&gt;</description></item><item><title>Organizing Go Tests Beyond `func TestX`</title><link>https://mvrahden.github.io/go-test/blog/organizing-go-tests/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/organizing-go-tests/</guid><description>&lt;p&gt;A test file with 10 test functions is easy to hold in your head. At 50, you&amp;rsquo;re scrolling to find what you need. At 200, you&amp;rsquo;re &lt;code&gt;grep&lt;/code&gt;-ing your own tests to figure out what&amp;rsquo;s covered and what isn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Go&amp;rsquo;s testing package is deliberately simple. No annotations, no test classes, no dependency injection: just functions that start with &lt;code&gt;Test&lt;/code&gt; and a &lt;code&gt;*testing.T&lt;/code&gt;. This simplicity is one of Go&amp;rsquo;s genuine strengths. But it creates a gap that every growing project eventually falls into.&lt;/p&gt;</description></item><item><title>Why Go's testing Package Needs a Suite Layer</title><link>https://mvrahden.github.io/go-test/blog/why-gotest/</link><pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate><guid>https://mvrahden.github.io/go-test/blog/why-gotest/</guid><description>&lt;p&gt;Go&amp;rsquo;s &lt;code&gt;testing&lt;/code&gt; package is one of the best things about the language: no framework to install, no annotations to learn, just &lt;code&gt;func Test*&lt;/code&gt; and &lt;code&gt;go test&lt;/code&gt;. But past a few dozen test files, most projects hit the same wall. There is no way to group related tests, no per-test setup hook, and no way for two packages to share a database container.&lt;/p&gt;
&lt;p&gt;That is not because the design is wrong, but because &lt;code&gt;testing&lt;/code&gt; is deliberately minimal. It gives you a test runner and an assertion-free &lt;code&gt;T&lt;/code&gt; type. Everything else is left to the developer: organizing tests into groups, managing setup and teardown, sharing expensive infrastructure, expressing what a test &lt;em&gt;means&lt;/em&gt; rather than just what it does.&lt;/p&gt;</description></item></channel></rss>