1. 程式人生 > 實用技巧 >Go測試

Go測試

單元測試

Go語言中的測試依賴go test命令,編寫測試程式碼和編寫普通的Go程式碼過程是類似的,並不需要學習新的語法,規則或工具。

go test命令是一個按照一定約定和組織的測試程式碼的驅動程式,在包目錄內,所有以_test.go為字尾名的原始碼檔案都是go test測試的一部分,不會被go build編譯到最終的可執行檔案。

*_test.go檔案中有三種類型的函式,單元測試函式,基準測試函式和示例函式。

型別 格式 作用
測試函式 函式名字首為Test 測試程式的一些邏輯行為是否正確
基準函式 函式字首名為Benchmark 測試函式的效能
示例函式 函式名為Example 為文件提供示例文件

go test命令會遍歷所有的*_test.go檔案中符合上述命令的規則函式,然後生成一個臨時的main包用於呼叫相應的測試函式,然後構建並執行,報告測試結果,最後清理測試中生成的臨時檔案。

測試函式

測試函式的格式

每個測試函式必須匯入testing包,測試函式的基本格式(簽名)如下:

func TestName(t *testing.T){
    // ...
}

測試函式的名字必須以Test開頭,可選的字尾名必須以大寫字母開頭,舉幾個例子:

func TestAdd(t *testing.T){...}
func TestSum(t *testing.T){...}
func TestLog(t *testing.T){...}

其種引數t用於報告測試失敗和附加的日誌資訊。

testing.T擁有如下方法:

func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string,args ...interface{})
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string,args ...interface{})
func (c *T) Name() string
func (c *T) Parallel()
func (c *T) Run(name string, f func(t *T)) bool
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interfaceP{})
func (c *T) Skipped() bool

測試函式示例

就像細胞是構成我們身體的基本單位,一個軟體程式也是由很多單元元件構成的,單元元件可以是函式,結構體,方法和終端使用者可能依賴的任意東西。總之我們需要確保這些最賤是能夠正常執行的,單元測試是一些利用各種方法測試單元元件的程式,它會將結果與預期輸出進行比較。

接下來我們定義一個split的包,包裡定義一個Split函式,具體實現如下:

package spilt

import (
	"strings"
)

func Split(s, sep string)(result []string){
	i := strings.Index(s, sep)  // 獲取到第一次的索引值
	for i > -1{   // 迴圈判斷
		result = append(result, s[:i])
		s = s[i+1:]
		i = strings.Index(s, sep)
	}
	result = append(result, s)
	return
}

在當前目錄下,我們建立一個split_test.go的測試檔案,並定義一個測試函式如下:

package spilt

import (
	"reflect"
	"testing"
)

func TestSplit(t *testing.T) {  // 測試函式名必須以Test開頭,必須接收一個*testing.T型別引數
	got := Split("a:b:c",":")  // 程式輸出的結果
	want := []string{"a","b","c"}  // 期望的結果
	if !reflect.DeepEqual(want, got){ // 因為slice不能直接比較,藉助反射保重的方法比較
		t.Errorf("excepted:%v,got:%v",want, got)  // 測試失敗輸出錯誤提示

	}

}

split包路徑下,執行go test命令,可以看到輸出的結果如下:

D:\gocode\src\Negan\split>go test
PASS
ok      _/D_/gocode/src/Negan/split     0.600s

一個測試用例有點單薄,我們在編寫一個測試使用多個字元切割字串的例子,在split_test.go中新增如下測試函式:

func TestMoreSplit(t *testing.T){
	got := Split("abcd","bc")
	want := []string{"a", "d"}
	if !reflect.DeepEqual(want, got){
		t.Errorf("excepted:%v, got:%v",want, got)
	}
}

再次執行go test命令,輸出結果如下:

D:\gocode\src\Negan\split>go test
--- FAIL: TestMoreSplit (0.00s)
    split_test.go:23: excepted:[a d], got:[a cd]
FAIL
exit status 1
FAIL    _/D_/gocode/src/Negan/split     0.708s

這一次我們的測試失敗了,我們可以在go test命令後面新增-v引數,檢視測試函式名稱和執行時間。

D:\gocode\src\Negan\split>go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestMoreSplit
    TestMoreSplit: split_test.go:23: excepted:[a d], got:[a cd]
--- FAIL: TestMoreSplit (0.00s)
FAIL
exit status 1
FAIL    _/D_/gocode/src/Negan/split     0.703s

這次我們能清除的看到TestMoreSplit這個測試沒有成功,還可以在go test命令後新增-run引數,它對應一個正則表示式,只有函式名匹配上的測試函式才會被go test命令執行。

go test -run="More"
--- FAIL: TestMoreSplit (0.00s)
    split_test.go:23: excepted:[a d], got:[a cd]
FAIL
exit status 1
FAIL    _/D_/gocode/src/Negan/split     0.735s

現在我們回過頭來解決我們程式中的問題,很顯然我們最初的split函式並沒有考慮到sep為多個字元的情況,我們來修復這個Bug:

package spilt

import (
	"strings"
)

func Split(s, sep string)(result []string){
	i := strings.Index(s, sep)  // 獲取到第一次的索引值
	for i > -1{   // 迴圈判斷
		result = append(result, s[:i])
        s = s[i+len(sep):]  // 使用len(sep)獲取sep長度
		i = strings.Index(s, sep)
	}
	result = append(result, s)
	return
}

這一次我們再來測試一下,注意當我們修改了我們的程式碼之後不要金金之星那些失敗的測試函式,我們應該完整的執行所有的測試,保證不會因為修改程式碼而引入新的問題。

go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
=== RUN   TestMoreSplit
--- PASS: TestMoreSplit (0.00s)
PASS
ok      _/D_/gocode/src/Negan/split     0.703s

測試組

我們現在還想要測試一下split函式對中文字串的支援,這個時候我們可以再編寫一個TestChineseSplit測試函式,但是我們也可以使用如下更友好的一種方式來新增更多的測試用例。

func TestSplit(t *testing.T) {
	// 定義一個測試用例型別
	type test struct{
		input string
		sep string
		want []string
	}

	// 定義一個儲存測試用例的切片
	tests := []test{
		{input:"a:b:c",sep: ":",want:[]string{"a","b","c"}},
		{input:"a:b:c",sep: ",",want:[]string{"a:b:c"}},
		{input:"abcd",sep: "bc",want:[]string{"a","d"}},
		{input:"上海自來水來自海上",sep: "自",want:[]string{"上海","來水來","海上"}},
	}

	// 遍歷切片,逐一執行測試用例
	for _, tc := range tests{
		got := Split(tc.input,tc.sep)
		if !reflect.DeepEqual(got,tc.want){
			t.Errorf("excepted:%#v, got:%#v",tc.want, got)
		}
	}
}

執行go test -v命令

go test -v
=== RUN   TestSplit
--- PASS: TestSplit (0.00s)
PASS
ok      _/D_/gocode/src/Negan/split     0.742s

子測試

如果測試用例比較多的時候,我們是沒有辦法一眼看出具體是哪個測試用例失敗了,我們可能會想到下面的解決方法:

func TestSplit(t *testing.T) {
	type test struct{
		input string
		sep string
		want []string
	}

	tests := map[string]test{
		"simple": {input:"a:b:c",sep: ":",want:[]string{"a","b","c"}},
		"wrong sep": {input:"a:b:c",sep: ",",want:[]string{"a:b:c"}},
		"more sep": {input:"abcd",sep: "bc",want:[]string{"a","d"}},
		"leading sep": {input:"上海自來水來自海上",sep: "自",want:[]string{"","上海","來水來","海上"}},
	}

	for name, tc := range tests{
		got := Split(tc.input, tc.sep)
		if !reflect.DeepEqual(got, tc.want){
			t.Errorf("name:%s excepted:%#v, got:%#v", name, tc.want, got)
		}
	}
}

上面的方法是能夠解決問題的,同時Go1.7+中新增了子測試,我們可以按照如下方式使用t.Run執行子測試:

func TestSplit(t *testing.T) {
	type test struct{
		input string
		sep string
		want []string
	}

	tests := map[string]test{
		"simple": {input:"a:b:c",sep: ":",want:[]string{"a","b","c"}},
		"wrong sep": {input:"a:b:c",sep: ",",want:[]string{"a:b:c"}},
		"more sep": {input:"abcd",sep: "bc",want:[]string{"a","d"}},
		"leading sep": {input:"上海自來水來自海上",sep: "自",want:[]string{"","上海","來水來","海上"}},
	}

	for name, tc := range tests{
		t.Run(name, func(t *testing.T) {  // 使用t.Run()執行子測試
			got := Split(tc.input, tc.sep)
			if !reflect.DeepEqual(got, tc.want){
				t.Errorf("name:%s excepted:%#v, got:%#v", name, tc.want, got)
			}
		})
	}
}

此時我們再執行go test -v命令就能夠看到更清晰的輸出內容了

go test -v
=== RUN   TestSplit
=== RUN   TestSplit/more_sep
=== RUN   TestSplit/leading_sep
    TestSplit/leading_sep: split_test.go:70: name:leading sep excepted:[]string{"", "上海", "來水來"
, "海上"}, got:[]string{"上海", "來水來", "海上"}
=== RUN   TestSplit/simple
=== RUN   TestSplit/wrong_sep
--- FAIL: TestSplit (0.00s)
    --- PASS: TestSplit/more_sep (0.00s)
    --- FAIL: TestSplit/leading_sep (0.00s)
    --- PASS: TestSplit/simple (0.00s)
    --- PASS: TestSplit/wrong_sep (0.00s)
FAIL
exit status 1
FAIL    _/D_/gocode/src/Negan/split     0.773s

我們知道可以通過-run=RegExp來指定執行的測試用例,還可以通過/指定要執行的子測試用例,例如:go test -v -run=Split/simple只會執行simple對應的子測試用例。

go test -v -run=Split/simple
=== RUN   TestSplit
=== RUN   TestSplit/simple
--- PASS: TestSplit (0.00s)
    --- PASS: TestSplit/simple (0.00s)
PASS
ok      _/D_/gocode/src/Negan/split     0.713s

測試覆蓋率

測試覆蓋率是袋中被測試套件覆蓋的百分比,通常我們使用的都是語句的覆蓋率,也就是在測試中至少被執行一次的程式碼佔總程式碼的比例。

Go提供了內建功能來檢查程式碼覆蓋率,使用go test -cover來檢視測試覆蓋率。

>go test -cover
PASS
coverage: 100.0% of statements
ok      _/D_/gocode/src/Negan/split     0.685s

從上面的結果可以看到我們的測試用例覆蓋了100%的程式碼。

Go還提供了一個額外的-coverprofile引數,用來將覆蓋率相關的記錄資訊輸出到一個檔案,例如:

>go test -cover -coverprofile=c.out
PASS
coverage: 100.0% of statements
ok      _/D_/gocode/src/Negan/split     0.812s

上面的命令會將覆蓋率相關的資訊輸出到當前檔案下面的c.out檔案中,然後我們執行go tool cover -html=c.out,使用cover工具來處理生成的記錄資訊,該命令會開啟本地的瀏覽器視窗生成一個html報告。

基準測試

基準測試函式格式

基準測試就是在一定的工作負載之下檢測程式效能的一種方法。基準測試的基本格式如下:

func BenchmarkName(b *testing.B){
    // ...
}

基準測試以Benchmark為字首,需要一個*testing.B型別的引數b,基準測試必須要執行b.N次,這樣測試才有對照性,b.N的值是系統根據實際情況去調整的,從而保證測試的穩定性,testing.B擁有的方法如下:

func (c *B) Error(args ...interface{})
func (c *B) Errorf(format string, args ...interface{})
func (c *B) Fail()
func (c *B) FailNow()
func (c *B) Failed() bool
func (c *B) Fatal(args ...interface{})
func (c *B) Fatalf(format string, args ...interface{})
func (c *B) Log(args ...interface{})
func (c *B)Logf(format string, args ...interface{})
func (c *B) Name() string
func (b *B) ReportAllocs()
func (b *B) ResetTimer()
func (b *B) Run(name string, f func(b *B)) bool
func (b *B) RunParallel(body func(*PB))
func (b *B) SetBytes(n int64)
func (b *B) SetParallelism(p int)
func (c *B) Skip(args ...interface{})
func (c *B) SkipNow()
func (c *B) Skipf(format string, args ...interface{})
func (c *B) Skipped() bool
func (b *B) StartTimer()
func (b *B) StopTimer()     

基準測試示例

我們為split包中的Split函式編寫基準測試如下:

func BenchmarkSplit(b *testing.B) {
	for i := 0; i < b.N; i++{
		Split("上海自來水來自海上","水")
	}
}

基準測試並不會預設執行,需要增加-bench引數,所以我們通過執行go test-bench=Split命令執行基準測試,輸出結果如下:

go test -bench=Split
goos: windows
goarch: amd64
BenchmarkSplit-4         4818822               246 ns/op
PASS
ok      _/D_/gocode/src/Negan/split     2.041s

其種BenchmarkSplit-4表示對Split函式進行基準測試,數字4表示GOMAXPROCS的值,這個對於併發基準測試很重要。4818822246 ns/op表示每次代用Split函式耗時246ns,這個結果是4848822次呼叫的平均值。

我們還可以為基準測試新增-benchmem引數來獲得記憶體分配的統計資料。

>go test -bench=Split -benchmem
goos: windows
goarch: amd64
BenchmarkSplit-4         4708045               256 ns/op              48 B/op          2 allocs/op
PASS
ok      _/D_/gocode/src/Negan/split     2.220s

其中48B/op表示每次操作記憶體分配了48位元組,2 allocs/op則表示每次操作進行了2次記憶體分配,我們將我們的Split函式優化如下:

func Split(s, sep string)(result []string){
	result = make([]string,0,strings.Count(s,sep)+1)  // 提前分配好記憶體
	i := strings.Index(s, sep)  // 獲取到第一次的索引值
	for i > -1{   // 迴圈判斷
		result = append(result, s[:i])
		s = s[i+len(sep):]
		i = strings.Index(s, sep)
	}
	result = append(result, s)
	return
}

這一次我們提前使用make函式將result初始化一個容量足夠大的切片,而不像之前一樣通過呼叫append函式來追加,我們來看一下這個改進會帶來多大的效能提升:

go test -bench=Split -benchmem
goos: windows
goarch: amd64
BenchmarkSplit-4         6301537               195 ns/op              32 B/op          1 allocs/op
PASS
ok      _/D_/gocode/src/Negan/split     2.072s

效能比較函式

上面的基準測試只能得到給定操作的絕對耗時,但是在很多效能問題是發生在兩個不同操作之間的相對耗時,比如同一個函式處理1000個元素的耗時與處理一萬個甚至100萬個元素的耗時差別是多少呢?

或者對於同一個任務酒精使用哪種演算法效能更佳?我們通常需要對兩個不同演算法的實現使用相同的輸入來進行基準比較測試。

效能比較函式通常是一個帶有引數的函式,被多個不同的Benchmark函式傳入不同的值來呼叫。

func benchmark(b *testing.B,size int){...}
func Benchmark10(b *testing.B){benchmark(b,10)}
func Benchmark100(b *testing.B){benchmark(b,100)}
func Benchmark1000(b *testing.B){benchmark(b,1000)}

我們編寫一個計算斐波那契數列函式如下:

// fib.go

func Fib(n int) int{
	if n < 2{
		return n
	}
	return Fib(n-1) + Fib(n-2)
}

我們編寫的效能比較函式如下:

// fib-test.go

import "testing"

func benchmarkFib(b *testing.B, n int) {
	for i:=0;i<b.N;i++{
		Fib(n)
	}
}


func BenchmarkFib1(b *testing.B) {
	benchmarkFib(b,1)
}
func BenchmarkFib2(b *testing.B) {
	benchmarkFib(b,2)
}
func BenchmarkFib3(b *testing.B) {
	benchmarkFib(b,3)
}
func BenchmarkFib10(b *testing.B) {
	benchmarkFib(b,10)
}
func BenchmarkFib20(b *testing.B) {
	benchmarkFib(b,20)
}
func BenchmarkFib40(b *testing.B) {
	benchmarkFib(b,40)
}

執行基準測試:

>go test -bench=Fib
goos: windows
goarch: amd64
BenchmarkFib1-4         380433421                3.06 ns/op
BenchmarkFib2-4         121904761                9.22 ns/op
BenchmarkFib3-4         87771267                14.1 ns/op
BenchmarkFib10-4         2137042               539 ns/op
BenchmarkFib20-4           19473             55064 ns/op
BenchmarkFib40-4               2         997070300 ns/op
PASS
ok      _/D_/gocode/src/Negan/split     11.877s

這裡需要注意的是,預設情況下,每個基準測試至少執行1秒,如果在Benchmark函式返回時沒有1秒,則b.N的值會按照1,2,5,10,20,50,...增加,並且函式再次執行。

最終的BenchmarkFib40只運行了兩次,每次執行的平均值只有不到一秒,像這種情況下我們使用-benchtime標誌增加最小基準時間,以產生更準確的結果,例如:

>go test -bench=Fib40 -benchtime=20s
goos: windows
goarch: amd64
BenchmarkFib40-4              24        1020060225 ns/op
PASS
ok      _/D_/gocode/src/Negan/split     26.215s

這一次BenchmarkFib40函式運行了24次,結果會更佳準確一些。

使用效能比較函式做測試的時候容易犯的錯誤就是把b.N作為輸入的大小,例如以下兩個例子都是錯誤示例:

// 錯誤示範1
func BenchmarkFibWrong(b *testing.B){
    for n:=0;n<b.N;n++{
        Fib(n)
    }
}

// 錯誤示例2
func BenchmarkFibWrong2(b *testing.B){
    Fib(b.N)
}

重置時間

b.ResetTimer之前的處理不會放到執行時間裡,也不會輸出到報告中,所以可以在之前做一些不計劃作為測試報告的操作。

func BenchmarkSplit(b *testing.B) {
	time.Sleep(5*time.Second)  // 架設需要做一些耗時的無關操作
	b.ResetTimer()
	for i := 0; i < b.N; i++{
		Split("上海自來水來自海上","水")
	}
}

並行測試

func (b *B) RunParallel(body func(*PB))會以並行的方式執行給定的基準測試。

RunParallel會建立多個goroutine,並將b.N分配給這些goroutine執行,其種goroutine的數量預設為GOMAXPROCS。使用者想要增加非CPU受限(non-CPU-bound)基準測試的並行性,那麼可以在RunParallel之前呼叫SetParallelismRunParallel通常會與-cpu標誌一同使用。

func BenchmarkSplitParallel(b *testing.B) {
	// b.SetParallelism(1)  // 設定使用cpu數
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next(){
			Split("上海自來水來自海上","水")
		}
	})
}

執行以下基準測試:

go test -bench=Split
goos: windows
goarch: amd64
BenchmarkSplit-4                 6023530               207 ns/op
BenchmarkSplitParallel-4        16168431                74.0 ns/op
PASS
ok      _/D_/gocode/src/Negan/split     28.532s

還可以通過測試命令後新增-cpu引數,如go test -bench=Split -cpu 1來指定使用cpu的數量。

Setup與TearDown

測試程式有時需要在測試之前進行額外的設定(setup)或在測試之後進行拆卸(teardown)。

TestMain

通過在*_test.go檔案中定義TestMain函式,可以在測試之前進行額外的設定(setup)或在測試之後進行拆卸(teardown)操作。

如果測試檔案包含函式func TestMain(m *testing.M)那麼生成的測試會先呼叫TestMain(n),然後再執行具體測試。TestMain執行在主goroutine中,可以在呼叫m.Run前後做任何設定(setup)和拆卸(teardown)。退出設施的時候應該使用m.Run的返回值作為引數呼叫os.Exit

一個使用TestMain來設定Setup和TearDown的示例如下:

func TestMain(m *testing.M){
	fmt.Println("write setup code here ...")  // 測試之前做一些設定
	// 如果TestMain使用了flags,這裡應該加上flag.Parse()'
	retCode := m.Run()
	fmt.Println("write teardown code here...") // 測試之後做一些拆卸工作
	os.Exit(retCode)
}

需要注意的是:在呼叫TestMain時,flag.Parse並沒有被呼叫。如果TestMain依賴於command-line標誌(包括testing包的標記),則應該顯示的呼叫flag.Parse

子測試的Setup與TearDown

有時候我們可能需要為每個測試集設定SetupTrardown,也有可能需要為每個子測試設定SetupTrardown。下面我們定義兩個工具函式如下:

// 測試機的Setup與Teardown
func setupTestCase(t *testing.T) func(t *testing.T){
	t.Log("如果需要再次執行:測試之前的setup")
	return func(t *testing.T){
		t.Log("如有需要再次執行:測試之後的teardown")
	}
}

// 子測試的Setup與Teardown
func setupSubTest(t *testing.T) func(t *testing.T){
	t.Log("如有需要再次執行:子測試之前的setup")
	return func(t *testing.T){
		t.Log("如有需要在此執行:子測試之後的teardown")
	}
}

使用方式如下:

func TestSplit(t *testing.T) {
   type test struct{  // 定義test結構體
      input string
      sep string
      want []string
   }
   tests := map[string]test{  // 測試用例用map儲存
      "simple":{input:"a:b:c",sep: ":",want:[]string{"a","b","c"}},
      "wrong sep":{input:"a:b:c",sep: ",",want:[]string{"a:b:c"}},
      "more sep":{input:"abcd",sep: "bc",want:[]string{"a","d"}},
      "leading sep":{input:"上海自來水來自海上",sep: "水",want:[]string{"上海自來","來自海上"}},
   }
   teardownTestCase := setupTestCase(t)  // 測試之前執行setup操作
   defer teardownTestCase(t)  // 測試之後執行testdown

   for name,tc:=range tests{
      t.Run(name,func(t *testing.T){  // 使用t.Run()執行子測試
         teardownSubTest := setupSubTest(t)  // 子測試執行之前執行setup操作
         defer teardownSubTest(t)  // 測試之後執行testdown操作
         got := Split(tc.input,tc.sep)
         if !reflect.DeepEqual(got,tc.want){
            t.Errorf("excepted:%#v,got:%#v", tc.want,got)
         }
      })
   }
}

測試結果如下:

go test -v
=== RUN   TestSplit
    TestSplit: split_test.go:104: 如果需要再次執行:測試之前的setup
=== RUN   TestSplit/simple
    TestSplit/simple: split_test.go:112: 如有需要再次執行:子測試之前的setup
    TestSplit/simple: split_test.go:114: 如有需要在此執行:子測試之後的teardown
=== RUN   TestSplit/wrong_sep
    TestSplit/wrong_sep: split_test.go:112: 如有需要再次執行:子測試之前的setup
    TestSplit/wrong_sep: split_test.go:114: 如有需要在此執行:子測試之後的teardown
=== RUN   TestSplit/more_sep
    TestSplit/more_sep: split_test.go:112: 如有需要再次執行:子測試之前的setup
    TestSplit/more_sep: split_test.go:114: 如有需要在此執行:子測試之後的teardown
=== RUN   TestSplit/leading_sep
    TestSplit/leading_sep: split_test.go:112: 如有需要再次執行:子測試之前的setup
    TestSplit/leading_sep: split_test.go:114: 如有需要在此執行:子測試之後的teardown
    TestSplit: split_test.go:106: 如有需要再次執行:測試之後的teardown
--- PASS: TestSplit (0.01s)
    --- PASS: TestSplit/simple (0.00s)
    --- PASS: TestSplit/wrong_sep (0.00s)
    --- PASS: TestSplit/more_sep (0.00s)
    --- PASS: TestSplit/leading_sep (0.00s)
PASS
ok      _/D_/gocode/src/Negan/split     0.716s

示例函式

示例函式的格式

go test特殊對待的第三種函式就是示例函式,它們的函式名以Example為字首,它們既沒有引數也沒有返回值。標準格式如下:

func ExampleName(){
    //...
}

示例函式示例

下面的程式碼使我們為Split函式編寫一個示例函式:

func ExampleSplit() {
	fmt.Println(Split("a:b:c",":"))
	fmt.Println(Split("上海自來水來自海上","水"))
	// Output:
	// [a b c]
	// [上海自來 來自海上]
}

為程式碼編寫示例程式碼有如下三個好處:

  • 示例函式能夠作為文件直接使用,例如基於web的godoc中能把示例函式與對應的函式或包相關聯;
  • 示例函式只要包含了//Ouput:也是可以通過go test執行的可執行測試
go test -run Example
PASS
ok      _/D_/gocode/src/Negan/split     0.691s
  • 示例函式提供了可以直接執行的示例程式碼,可以直接在golang.orggodoc文件伺服器上使用Go Playgoround執行示例程式碼。