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 行程報告
上一篇文章:包名
部落格索引