JavaRush /Courses /Go SELF /Example tests in go test: verifying output via // Output:...

Example tests in go test: verifying output via // Output:

Go SELF
Level 31, Lesson 3
Available

1. Example tests

When we read documentation or a README, we subconsciously trust the examples. The problem is that code changes, while examples often stay the same — and turn into “documentation archaeology.”

In Go, this pain was solved quite elegantly: an example can be made executable and verifiable. Such an example lives next to tests, and go test will verify that the example really prints what it promises.

The idea is simple: if an example “rots,” the test fails — and you find out immediately when running tests, not six months later from a colleague. It is self-checking documentation: the example becomes part of quality control.

Why Example tests do not replace unit tests

It is very easy to start using Example tests “instead of everything.” They look nice, read like a demo, and you do not need to write t.Fatalf. But they have a different specialization.

A regular TestXxx is great when you want to check many cases, compare values carefully, verify errors, edge cases, and get precise messages about what broke.

An Example is great when you want to show a person what using a function looks like and what text they will see in stdout. In practice it often looks like this: unit tests verify the logic “for real,” while Example tests pin down a couple of the most obvious scenarios so the documentation does not go stale.

2. Minimal Example and the role of // Output:

Let’s be precise: an Example test is a function in a file *_test.go whose name starts with Example. Unlike a regular test TestXxx(t *testing.T), an Example function has no arguments.

Here is the most “bare-bones” example that compiles and already looks like an Example test:


package greet

import "fmt"

func ExampleHello() {
	fmt.Println("hello")
	// Output: hello
}

Notice three things: it is in *_test.go, the name starts with Example, and inside there is a print to stdout and a // Output: comment — that is exactly what turns the example into a verifiable test.

How stdout verification via // Output: works

The most important thing about Example tests is that during execution the framework captures everything you wrote to standard output (stdout) and compares it to the text in the // Output: comment. If it matches, the example passes. If it does not, it fails, and in the test output you will see “got/want.”

Let’s take a small example. Suppose we have a function that reverses a string (the implementation is not important right now — we care about the example’s behavior):

package reverse

import "fmt"

func ExampleString() {
	fmt.Println(String("hello"))
	// Output: olleh
}

When you run go test -v, you will see that the example runs as part of the tests, almost like a regular Test...:

=== RUN   ExampleString
--- PASS: ExampleString (0.00s)

And if you (accidentally or “for science”) write the wrong expectation:

func ExampleString() {
	fmt.Println(String("hello"))
	// Output: golly
}

Then with go test you will get a failure with a clear comparison:

--- FAIL: ExampleString (0.00s)
got:
olleh
want:
golly
FAIL

If there is no // Output:

Sometimes an example is needed, but running it in tests is impossible or pointless: the code hits the network, depends on the environment, requires manual setup, or is simply too “heavy” for every run.

In Go there is a simple rule: if an Example function has no // Output:, it compiles, but does not run.

This is useful for two reasons. First, you still guarantee that the example did not become outdated to the point of “does not compile.” Second, you do not turn go test into a lottery of “the internet is down — tests are down.”

“Compile-only” example:

package greet

import "fmt"

func ExampleHello_compilesOnly() {
	fmt.Println(Hello("Gopher"))
	// No Output comment: the example compiles, but does not run.
}

If you run go test -v, you will notice that such an example does not appear in the list of executed Example tests (because it does not run).

3. Example function names and binding to the API

As a project grows, a package ends up with many functions, and you also want to keep examples “organized.” Go has a naming convention: the name of an Example function can be tied to a specific function, type, or method. This affects how the documentation shows examples and helps people quickly find the right one at a glance.

Here are the main forms:

Example function name Binding idea
Example()
example for the whole package
ExampleFoo()
example for the identifier
Foo
(function or type)
ExampleBar_Qux()
example for method
Qux
on type
Bar

One more nice feature: you can make multiple examples for the same identifier. To do this, add a suffix after an underscore, usually starting with a lowercase letter.

func ExampleString()        { /* ... */ }
func ExampleString_second() { /* ... */ }
func ExampleString_third()  { /* ... */ }

From the go test perspective, these are just different Example functions. From a reader’s perspective, it is a way to show a “basic case,” an “edge case,” and a “more advanced example” without turning one example into half a page.

4. Example: package taskfmt

Now let’s build a small piece of a “shared app” that can be developed further: a task formatting utility. For now, no CLI and no files — only the string formatting logic.

Imagine we have a package taskfmt. It is responsible only for how a task looks in text. The task is primitive: ID, Title, Done.

File task.go:

package taskfmt

type Task struct {
	ID    int
	Title string
	Done  bool
}

Now a function that turns a task into a string is an ideal candidate for an Example: input → readable output.

File format.go:

package taskfmt

import "fmt"

func FormatTaskLine(t Task) string {
	mark := " "
	if t.Done {
		mark = "x"
	}
	return fmt.Sprintf("%d. [%s] %s", t.ID, mark, t.Title)
}

Example with a single output line

And here is an Example test that looks like documentation but behaves like a test.

File format_test.go:

package taskfmt

import "fmt"

func ExampleFormatTaskLine() {
	t := Task{ID: 3, Title: "Buy milk", Done: false}
	fmt.Println(FormatTaskLine(t))
	// Output: 3. [ ] Buy milk
}

Here we use fmt.Println: it adds a newline, and the Example test runner compares the output line by line with what is written after // Output:.

Example with multiple lines

A single line is good, but sometimes you want to show behavior on multiple objects. In Example tests that is fine: just print multiple lines and list them in // Output: line by line. The main thing is that the output must be stable and predictable.

Let’s make an example that prints two tasks: one done and one not.

package taskfmt

import "fmt"

func ExampleFormatTaskLine_twoTasks() {
	fmt.Println(FormatTaskLine(Task{ID: 1, Title: "Read docs", Done: true}))
	fmt.Println(FormatTaskLine(Task{ID: 2, Title: "Write code", Done: false}))
	// Output:
	// 1. [x] Read docs
	// 2. [ ] Write code
}

The format is: after // Output: come the expected output lines, each starting with // (and a space). Essentially, you are fixing a “golden” console output.

5. Determinism: so an Example is not flaky

There is an important psychological trap: an Example feels “simple,” so you want to show something live — for example, output from a map, the current time, or a random number. And then suddenly the example starts failing: the key order is different, the time is different, the random number is different.

An Example with // Output: is a test, and a test loves repeatability.

For example, if you do this:

package taskfmt

import "fmt"

func Example_bad_NonDeterministic() {
	m := map[string]int{"b": 2, "a": 1}
	for k, v := range m {
		fmt.Println(k, v)
	}
	// Output:
	// a 1
	// b 2
}

Then this Example may fail, because the iteration order of a map is not guaranteed.

The right approach at this level is not to try to “prove to the runner that order does not matter,” but to make the output deterministic: collect the keys into a slice, sort them, and print in order.

The point is simple: an Example test is an agreement of “same input → same output.”

6. How to run Example tests and verify they run

When you have just added an Example test, you want to make sure it actually runs. The friendliest way is go test -v: it shows which tests were actually executed, including Example....

If you do not see your Example... in -v, it usually means one of two things: either you forgot // Output: (then it compiles but does not run), or the name/file/package is not set up the way you think.

A simple discipline helps here: when debugging tests, first turn on -v and make sure exactly what you think should be running is actually running.

7. Typical mistakes when writing Example tests

Mistake #1: you forgot // Output: and are surprised why the example does not run.
This is very common: you wrote func Example...(), printed something nice, run go test, and… silence. The reason is that an example without // Output: compiles but does not execute. This behavior is normal and even a useful feature for “non-executable” examples, but if you want stdout verification, // Output: is required.

Mistake #2: whitespace and newline mismatches.
Example tests compare text literally: one extra space, a different newline — and you will get got/want. This is not “Go being picky,” it is protection against output drifting. So it is better to print succinctly, predictably, and not try to demonstrate “all formatting in the world” in one example.

Mistake #3: nondeterministic output and the “fails sometimes” effect.
If the output depends on map iteration order, the current time, or randomness, an Example turns into a generator of flaky tests (tests that fail “sometimes”). In learning examples, it is better to follow the rule from the start: an Example must be repeatable. If you want to show a map, produce stable output by sorting keys; and if you want to show working with an unstable environment, remove // Output: and leave the example “compile-only.”

Mistake #4: writing to stderr and expecting // Output: to check it.
Example tests compare what went to standard output. If some messages go to stderr, you get a weird situation: something prints in the console, but the Example seems to “not see” that text. For learning examples, stick to fmt.Print/fmt.Println without os.Stderr.

Mistake #5: one huge Example “for half the package.”
An Example should illustrate one idea: one main usage scenario for a function or a small chunk of behavior. When an example grows, it becomes hard to read as documentation and fragile as a test. It is much better to create two Example tests with suffixes _second, _third, than one monster.

1
Task
Go SELF, level 31, lesson 3
Locked
Greeting by example
Greeting by example
1
Task
Go SELF, level 31, lesson 3
Locked
Two lines
Two lines
1
Task
Go SELF, level 31, lesson 3
Locked
Two examples
Two examples
1
Task
Go SELF, level 31, lesson 3
Locked
Stable dictionary
Stable dictionary
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION