Go 博客
Go 中的可测试示例
引言
Godoc 中的 示例 是 Go 代码片段,它们会显示为包文档,并通过运行它们作为测试来验证。用户访问包的 godoc 网页并点击相应的“运行”按钮也可以运行它们。
为包提供可执行的文档可以保证信息不会随着 API 的更改而过时。
标准库包含许多此类示例(例如,请参阅 strings
包)。
本文将介绍如何编写自己的示例函数。
示例也是测试
示例会作为包测试套件的一部分进行编译(并且可以选择性地执行)。
与典型测试一样,示例是位于包的 _test.go
文件中的函数。但与普通测试函数不同的是,示例函数不接受任何参数,并且以单词 Example
而不是 Test
开头。
reverse
包 是 Go 示例仓库的一部分。下面是一个演示其 String
函数的示例:
package reverse_test
import (
"fmt"
"golang.org/x/example/hello/reverse"
)
func ExampleString() {
fmt.Println(reverse.String("hello"))
// Output: olleh
}
此代码可能位于 reverse
目录下的 example_test.go
文件中。
Go 包文档服务器 pkg.go.dev 将此示例与 String
函数的文档一起展示。

运行包的测试套件,我们可以看到示例函数已经执行,无需我们进一步的安排。
$ go test -v
=== RUN TestString
--- PASS: TestString (0.00s)
=== RUN ExampleString
--- PASS: ExampleString (0.00s)
PASS
ok golang.org/x/example/hello/reverse 0.209s
输出注释
ExampleString
函数“通过”是什么意思?
在执行示例时,测试框架会捕获写入标准输出的数据,然后将输出与示例的“Output:”注释进行比较。如果测试的输出与其输出注释匹配,则测试通过。
要查看失败的示例,我们可以将输出注释文本更改为明显不正确的文本
func ExampleString() {
fmt.Println(reverse.String("hello"))
// Output: golly
}
然后再次运行测试。
$ go test
--- FAIL: ExampleString (0.00s)
got:
olleh
want:
golly
FAIL
如果我们完全移除输出注释
func ExampleString() {
fmt.Println(reverse.String("hello"))
}
那么示例函数将被编译但不会执行。
$ go test -v
=== RUN TestString
--- PASS: TestString (0.00s)
PASS
ok golang.org/x/example/hello/reverse 0.110s
没有输出注释的示例对于演示无法作为单元测试运行的代码(例如访问网络的代码)非常有用,同时又能保证示例至少可以编译通过。
示例函数名称
Godoc 使用命名约定将示例函数与包级标识符关联起来。
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
遵循此约定,godoc 会将 ExampleString
示例与 String
函数的文档一起显示。
可以通过使用以下划线后跟小写字母开头的后缀,为给定的标识符提供多个示例。这些示例都记录了 String
函数。
func ExampleString()
func ExampleString_second()
func ExampleString_third()
大型示例
有时,编写一个好的示例需要的不只是一个函数。
例如,要演示 sort
包,我们应该展示 sort.Interface
的实现。由于方法不能在函数体内声明,因此示例除了示例函数外,还需要包含一些上下文。
要实现这一点,我们可以使用“整个文件示例”。整个文件示例是一个以 _test.go
结尾的文件,其中包含恰好一个示例函数、没有测试或基准测试函数,并且至少有一个其他包级声明。显示此类示例时,godoc 将显示整个文件。
这是 sort
包的一个整个文件示例:
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]
}
一个包可以包含多个整个文件示例;每个文件一个示例。请查看 sort
包的源代码以实际了解其用法。
结论
Godoc 示例是编写和维护代码作为文档的绝佳方式。它们还提供了可编辑、可用、可运行的示例供用户构建。请使用它们!
下一篇文章:GopherChina 之行报告
上一篇文章:包名称
博客索引