The Go 部落格

Testable Examples in Go

Andrew Gerrand
2015 年 5 月 7 日

簡介

Godoc 範例是 Go 程式碼片段,顯示於套件文件,並以執行測試的方式驗證。使用者也可遊覽套件的 godoc 網頁並按一下關聯的「執行」按鈕,如此一來便可以執行範例。

針對套件擁有可執行的文件,可確保當 API 進行更動時,資訊不會過時。

標準函式庫中包含許多此類範例(例如 字串 套件)。

本文會說明如何撰寫自己的範例函式。

範例就是測試

範例會編譯(以及選擇性執行)作為套件測試組的一部分。

與一般的測試一樣,範例都是存在於封裝內的 _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 函式「通過」是什麼意思?

當它執行該範例時,測試架構會擷取寫入標準輸出的資料,然後將輸出與範例的「輸出:」註解比較。如果該測試的輸出與其輸出註解相符,則該測試通過。

為了看到一個失敗的範例,我們可以將輸出註解文字改為一些明顯不正確的文字

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 會在 String 函式的文件旁顯示 ExampleString 的範例。

可以提供多個範例給定的識別碼,使用以底線開頭,後接小寫字母的字尾。這些範例都會記錄 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 旅行報告
前一篇文章:套件名稱
部落格索引