skip to content

In Go, what makes go test execute a func Example() and check its output?

level: juniorimportance: should knowfreq 50%

answer

  1. compiled always, run only sometimes
  2. the last comment decides
  3. stdout is captured and compared
  4. // Output: is the trigger

basics

~20 s

A trailing // Output: comment. go test compiles every Example function in a _test.go file but only runs one that ends with an // Output: comment, comparing what the example printed to stdout against that text after trimming surrounding whitespace.

solid answer

~40 s

An example is a niladic function named `Example...` in a `_test.go` file. The test binary always compiles it, so it is at minimum a compile check on the documentation, but it is only *run* if the last comment in its body starts with `// Output:`. When that comment is present, `go test` swaps `os.Stdout` for a pipe, runs the function, and compares the captured text with the comment body; surrounding whitespace is trimmed on both sides, and a mismatch fails the package like any other test. Examples take no `*testing.T`, so there is nothing to assert with — printing is the assertion. Anything written to stderr, including the default `log` output, is not captured and therefore not compared.

code

go · 4 lines
go
func ExampleCanonical() {
	fmt.Println(Canonical("https://example.com/p?b=2&a=1"))
	// Output: https://example.com/p?a=1&b=2
}

go deeper

for a junior

Be ready to write one from scratch: a function named Example plus a symbol name, printing with fmt.Println, ending in an // Output: comment. Say plainly that without that comment go test compiles it and never runs it.

for a middle

Explain the mechanics: the runner takes the last comment in the body, redirects os.Stdout, and compares after trimming surrounding whitespace. Know that stderr and log output are outside the comparison and that there is no *testing.T to assert with.

for a senior

Show the judgment about when the check is worth having. Decide which examples can print deterministically, keep the printed output informative for a reader rather than convenient for an assertion, and treat an example with no output block as documentation only.

for a principal

Own the standard for a package other teams read first. Decide whether every exported symbol needs an example, whether unchecked examples are acceptable, and how the team keeps documentation that compiles and runs from becoming a maintenance tax nobody pays.

## What an example function is A *runnable example* is an ordinary function in a `_test.go` file whose name begins with `Example`, takes no parameters and returns nothing: ```go func ExampleCanonical() { fmt.Println(Canonical("https://example.com/p?b=2&a=1")) // Output: https://example.com/p?a=1&b=2 } ``` It serves two jobs at once. It is documentation — `go doc` and pkg.go.dev render it next to the symbol it names, so a stranger reading your package sees working code rather than prose. And it is a test — `go test` runs it and checks what it printed. ## The compile-only default Every example is compiled into the test binary regardless. That alone has value: an example that calls a function you have since renamed will not build, so the documentation cannot silently rot into something that no longer compiles. But compiling is not running. The test runner executes an example **only** when the function body ends with an output comment. The tooling takes the *last* comment block in the body and checks whether it begins with `Output:` (or `Unordered output:`). If it does, the block's text becomes the expected output. If it does not — no comment at all, or another comment placed after it — the example is registered with no expected output and is skipped. It will never fail, no matter what it does. This is the single most important fact about examples, and the reason a package can accumulate examples that all "pass" while printing nonsense. ## How the comparison works When an example does have an output comment, the runner: 1. Replaces `os.Stdout` with a pipe for the duration of the call. 2. Calls the function. 3. Reads everything the function wrote to `os.Stdout`. 4. Trims surrounding whitespace from both the captured text and the expected text, then compares them. Because only `os.Stdout` is redirected, output that goes elsewhere is invisible to the check. The standard `log` package writes to stderr by default, so `log.Println` inside an example produces noise in your terminal and contributes nothing to the comparison. `fmt.Println`, `fmt.Printf` and `os.Stdout.Write` are what count. There is no `*testing.T` in an example's signature, so there is nothing to call `t.Error` on. The printed text *is* the assertion. That constraint is deliberate: it keeps the body readable as documentation, since an example cluttered with assertions is a bad example for a reader. A panic inside an example fails the example, and its stack trace is reported the way any test panic is. ## Writing the output block Conventions that matter in practice: - Put the comment last in the body. Anything after it steals the "last comment" position. - One-line form (`// Output: 42`) and multi-line form both work: ```go func ExampleQuery_Encode() { q := Query{"a": {"1"}, "b": {"2"}} fmt.Println(q.Encode()) fmt.Println(len(q)) // Output: // a=1&b=2 // 2 } ``` - Keep the printed values deterministic. If the example prints something that varies between runs — a timestamp, an address, a randomly ordered iteration — the check will be flaky. Either make the output stable, or use the `// Unordered output:` form when only the line order varies. - Because the example is documentation first, prefer output a reader would find informative over output that is merely easy to assert on. ## Running and inspecting them `go test` runs examples along with tests. `go test -run Example -v` restricts the run to examples and prints each one it actually executed, which is how you confirm that a given example is not silently skipped. Since examples are compiled from `_test.go` files, they never ship in your package's build output. ## Why this design Go ties the test to the documentation on purpose. A prose comment describing what a function returns can drift from the code with nothing to stop it. An example that prints its result and declares that result in a comment cannot drift: the moment the behaviour changes, the package's own test suite fails. The cost of that guarantee is exactly one convention — the output comment — and the failure mode when you forget it is silence, not an error.

  • If an example has no output comment, is it worth writing at all?
    Often yes. It is still compiled into the test binary, so it fails the build if the API it calls changes shape, and it still renders on the documentation page. That is the right choice for an example whose result cannot be printed deterministically — a network call, a random id — as long as everyone understands nothing about its behaviour is being verified.
  • Why do example functions take no *testing.T?
    Because they are documentation first. The body is rendered verbatim on the package's doc page, and assertions would make it worse to read. Removing the `*testing.T` forces the example to demonstrate the API the way a caller would use it, and moves verification into the `// Output:` comment where it does not clutter the code.
  • Where does an example's output have to be written to be checked?
    To `os.Stdout`, which the test runner replaces with a pipe for the duration of the call. `fmt.Println` and friends work. The `log` package writes to stderr by default, so `log.Println` inside an example is not captured and never appears in the comparison — a common source of confusion when an example seems to print more than it is checked on.

An example with no output comment is a smoke alarm mounted with no battery in it: it is on the ceiling, it looks like protection, and it will never once go off.

saying these in an interview costs you the question

  • Thinking every Example function runs, output comment or not
  • Believing a missing // Output: comment is a build error
  • Expecting log output on stderr to be compared
  • Looking for a *testing.T parameter to assert with
  • Putting the // Output: comment above the code instead of last