| Testable Examples in Go |
| 7 May 2015 |
| Tags: godoc, testing |
| |
| Andrew Gerrand |
| |
| * Introduction |
| |
| Godoc [[https://golang.org/pkg/testing/#hdr-Examples][examples]] are snippets of |
| Go code that are displayed as package documentation and that are verified by |
| running them as tests. |
| They can also be run by a user visiting the godoc web page for the package |
| and clicking the associated "Run" button. |
| |
| Having executable documentation for a package guarantees that the information |
| will not go out of date as the API changes. |
| |
| The standard library includes many such examples |
| (see the [[https://golang.org/pkg/strings/#Contains][`strings` package]], |
| for instance). |
| |
| This article explains how to write your own example functions. |
| |
| * Examples are tests |
| |
| Examples are compiled (and optionally executed) as part of a package's test |
| suite. |
| |
| As with typical tests, examples are functions that reside in a package's |
| `_test.go` files. |
| Unlike normal test functions, though, example functions take no arguments |
| and begin with the word `Example` instead of `Test`. |
| |
| The [[https://godoc.org/github.com/golang/example/stringutil/][`stringutil` package]] |
| is part of the [[https://github.com/golang/example][Go example repository]]. |
| Here's an example that demonstrates its `Reverse` function: |
| |
| package stringutil_test |
| |
| import ( |
| "fmt" |
| |
| "github.com/golang/example/stringutil" |
| ) |
| |
| func ExampleReverse() { |
| fmt.Println(stringutil.Reverse("hello")) |
| // Output: olleh |
| } |
| |
| This code might live in `example_test.go` in the `stringutil` directory. |
| |
| Godoc will present this example alongside the `Reverse` function's documentation: |
| |
| .image examples/reverse.png |
| |
| Running the package's test suite, we can see the example function is executed |
| with no further arrangement from us: |
| |
| $ go test -v |
| === RUN TestReverse |
| --- PASS: TestReverse (0.00s) |
| === RUN: ExampleReverse |
| --- PASS: ExampleReverse (0.00s) |
| PASS |
| ok github.com/golang/example/stringutil 0.009s |
| |
| * Output comments |
| |
| What does it mean that the `ExampleReverse` function "passes"? |
| |
| As it executes the example, |
| the testing framework captures data written to standard output |
| and then compares the output against the example's "Output:" comment. |
| The test passes if the test's output matches its output comment. |
| |
| To see a failing example we can change the output comment text to something |
| obviously incorrect |
| |
| func ExampleReverse() { |
| fmt.Println(stringutil.Reverse("hello")) |
| // Output: golly |
| } |
| |
| and run the tests again: |
| |
| $ go test |
| --- FAIL: ExampleReverse (0.00s) |
| got: |
| olleh |
| want: |
| golly |
| FAIL |
| |
| If we remove the output comment entirely |
| |
| func ExampleReverse() { |
| fmt.Println(stringutil.Reverse("hello")) |
| } |
| |
| then the example function is compiled but not executed: |
| |
| $ go test -v |
| === RUN TestReverse |
| --- PASS: TestReverse (0.00s) |
| PASS |
| ok github.com/golang/example/stringutil 0.009s |
| |
| Examples without output comments are useful for demonstrating code that cannot |
| run as unit tests, such as that which accesses the network, |
| while guaranteeing the example at least compiles. |
| |
| |
| * Example function names |
| |
| Godoc uses a naming convention to associate an example function with a |
| package-level identifier. |
| |
| func ExampleFoo() // documents the Foo function or type |
| func ExampleBar_Qux() // documents the Qux method of type Bar |
| func Example() // documents the package as a whole |
| |
| Following this convention, godoc displays the `ExampleReverse` example |
| alongside the documentation for the `Reverse` function. |
| |
| Multiple examples can be provided for a given identifier by using a suffix |
| beginning with an underscore followed by a lowercase letter. |
| Each of these examples documents the `Reverse` function: |
| |
| func ExampleReverse() |
| func ExampleReverse_second() |
| func ExampleReverse_third() |
| |
| |
| * Larger examples |
| |
| Sometimes we need more than just a function to write a good example. |
| |
| For instance, to demonstrate the [[https://golang.org/pkg/sort/][`sort` package]] |
| we should show an implementation of `sort.Interface`. |
| Since methods cannot be declared inside a function body, the example must |
| include some context in addition to the example function. |
| |
| To achieve this we can use a "whole file example." |
| A whole file example is a file that ends in `_test.go` and contains exactly one |
| example function, no test or benchmark functions, and at least one other |
| package-level declaration. |
| When displaying such examples godoc will show the entire file. |
| |
| Here is a whole file example from the `sort` package: |
| |
| package sort_test |
| |
| import ( |
| "fmt" |
| "sort" |
| ) |
| |
| type Person struct { |
| Name string |
| Age int |
| } |
| |
| func (p Person) String() string { |
| return fmt.Sprintf("%s: %d", p.Name, p.Age) |
| } |
| |
| // ByAge implements sort.Interface for []Person based on |
| // the Age field. |
| type ByAge []Person |
| |
| func (a ByAge) Len() int { return len(a) } |
| func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
| func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age } |
| |
| func Example() { |
| people := []Person{ |
| {"Bob", 31}, |
| {"John", 42}, |
| {"Michael", 17}, |
| {"Jenny", 26}, |
| } |
| |
| fmt.Println(people) |
| sort.Sort(ByAge(people)) |
| fmt.Println(people) |
| |
| // Output: |
| // [Bob: 31 John: 42 Michael: 17 Jenny: 26] |
| // [Michael: 17 Jenny: 26 Bob: 31 John: 42] |
| } |
| |
| A package can contain multiple whole file examples; one example per file. |
| Take a look at the [[https://golang.org/src/sort/][`sort` package's source code]] |
| to see this in practice. |
| |
| * Conclusion |
| |
| Godoc examples are a great way to write and maintain code as documentation. |
| They also present editable, working, runnable examples your users can build on. |
| Use them! |