Go 博客

Go 中的可测试示例

Andrew Gerrand
2015 年 5 月 7 日

简介

Godoc 示例 是 Go 代码片段,它们以包文档的形式显示,并通过作为测试运行来验证。用户访问包的 godoc 网页并点击相关的“Run”按钮时,也可以运行它们。

包拥有可执行文档可以保证信息不会随着 API 的变化而过时。

标准库包含许多此类示例(例如,参见 strings)。

本文解释了如何编写自己的示例函数。

示例即测试

示例作为包的测试套件的一部分被编译(并可选地执行)。

与典型测试一样,示例是位于包的 _test.go 文件中的函数。然而,与普通测试函数不同,示例函数不接受参数,并且以 Example 而非 Test 开头。

reverseGo 示例仓库的一部分。这里有一个示例,展示了它的 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 行程报告
上一篇文章:包名
博客索引