Make Your Go Docs Self‑Testing with Executable Examples
The author discovered that stale Go documentation caused runtime panics, then leveraged Go's hidden feature of executable examples to turn code snippets into live tests that run on pkg.go.dev, integrate into CI, and keep docs and code in sync, dramatically improving developer efficiency.
Step 1: Turn a simple function into an executable example
Assume a basic string‑reversal function:
// reverse/reverse.go
package reverse
func String(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}Write an Example function in the test file:
// reverse/reverse_test.go
func ExampleString() {
fmt.Println(String("hello"))
// Output:
// olleh
}Key point: the comment // Output: is the expected result; go test compares actual output with this expectation.
If the output mismatches, the test fails, forcing you to update the documentation or the code.
Running go test -v ./reverse shows the example passing:
$ go test -v ./reverse
=== RUN ExampleString
--- PASS: ExampleString (0.00s)
PASSThe example appears on pkg.go.dev with a "Run" button, letting users try the code in the browser without installing Go.
Step 2: Add "live" documentation to common utility functions
Example 1 – Factorial with boundary handling
// factorial/factorial.go
func Calc(n int) (int, error) {
if n < 0 {
return 0, fmt.Errorf("negative number: %d", n)
}
if n <= 1 {
return 1, nil
}
result := 1
for i := 2; i <= n; i++ {
result *= i
}
return result, nil
} // factorial/factorial_test.go
func ExampleCalc_normal() {
result, _ := Calc(5)
fmt.Println(result)
// Output:
// 120
}
func ExampleCalc_error() {
_, err := Calc(-3)
fmt.Println(err)
// Output:
// negative number: -3
}Example 2 – Handling unordered output
When deduplicating a slice with a map, iteration order is nondeterministic. Use the special comment // Unordered output: to indicate that order does not matter.
// unique/unique.go
func Ints(nums []int) []int {
seen := make(map[int]bool)
var result []int
for _, n := range nums {
if !seen[n] {
seen[n] = true
result = append(result, n)
}
}
return result
} // unique/unique_test.go
func ExampleInts() {
nums := []int{3, 1, 2, 1, 3}
unique := Ints(nums)
sort.Ints(unique) // ensure deterministic order for the example
fmt.Println(unique)
// Output:
// [1 2 3]
// Or, to ignore order:
// Unordered output:
// [1 2 3]
}Step 3: Advanced usage – grouping examples
Package‑level example
// mathutil/mathutil_test.go
func Example() {
a := Add(2, 3) // 5
b := Multiply(a, 4) // 20
fmt.Println(IsEven(b)) // true
// Output:
// true
}Method example for a type
// counter/counter.go
type Counter struct { count int }
func (c *Counter) Inc() { c.count++ }
func (c *Counter) Value() int { return c.count } // counter/counter_test.go
func ExampleCounter_basic() {
var c Counter
c.Inc()
c.Inc()
fmt.Println(c.Value())
// Output:
// 2
}
func ExampleCounter_reset() {
c := Counter{count: 10}
c.count = 0 // direct reset for demo
fmt.Println(c.Value())
// Output:
// 0
}Step 4: Integrate into the development workflow
Local development – examples are tests
# Run only examples
$ go test ./... -run Example
# All examples pass → safe to publish docsCI/CD – prevent "doc rot"
# .github/workflows/test.yml
- name: Verify examples
run: go test -run Example ./...
# If an example fails, the PR is blocked, forcing documentation updates.Online docs – users can "play" code
Visit pkg.go.dev/your‑tool‑library Click "Run" on an example to see the result instantly in the browser.
No need to git clone or go install – zero‑friction experience.
Retrospective – why this feature is a game‑changer
Documentation stays in sync with code, never becomes outdated.
Examples double as tests, so code changes must pass documentation checks first.
Users can try functions directly online, lowering the barrier to adoption.
One write‑once approach boosts efficiency for library authors and maintainers.
Ideal for open‑source projects that need many code demonstrations.
Naming convention: Example + TypeName + _ + MethodName – the doc tool links them automatically.
In a later incident, a colleague asked whether the UniqueInts function returns nil or an empty slice for an empty input. Instead of digging through code and sending screenshots, I pointed them to the live example, changed the input to []int{}, clicked Run, and the output clarified the behavior in seconds.
That moment reinforced the belief that the best documentation is not the prettiest prose, but something the user can interact with directly.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Golang Shines
We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
