先看看效果吧
當不小心點開網頁文檔的時候,是不是有看到過這樣的展示示例代碼的tab?
或者在IDE裡看很多函數的簡介時是不是有這樣的:
有沒心生敬畏,覺得很神奇?
其實這就是Golang的testing提供的一種能力,把單測代碼同時作為文檔,我們來看看要怎麼實作吧。
可測試的Example
Example測試會被展示進package的文檔,并可以如單測一樣被驗證。可以在godoc展示的web網頁中特别展示出來。
Example本身也是測試,它會和正常的測試一起編譯和執行,和它們一樣放在xxx_test.go的檔案中,但是它的函數簽名不一樣。
函數簽名
Example的函數簽名以Example開頭,後面緊跟被示例的函數的名字,并且沒有入參:
func ExampleFuncName() {
……
}
比如我現在實作一個函數 AdjacentMixString:
util.go
……
// 互動地混合兩個字元串
func AdjacentMixString(str1, str2 string) string {
builder := strings.Builder{}
builder.Grow(len(str1) + len(str2))
i := 0
for ; i < len(str1) && i < len(str2); i++ {
builder.WriteByte(str1[i])
builder.WriteByte(str2[i])
}
if len(str1) > i {
builder.WriteString(str1[i:])
}
if len(str2) > i {
builder.WriteString(str2[i:])
}
return builder.String()
}
那它的示例就應該這麼寫:
func ExampleAdjacentMixString() {
……
}
執行測試
和普通的測試一樣,你可以通過go test指令以及相關的option來運作Example測試。
我們來随便寫個Example到util_test.go檔案中:
……
func ExampleAdjacentMixString() {
// should output 1a2b3c
AdjacentMixString("123", "abc")
}
已經可以看到效果了:
然後來運作它:
admin@……/util$ go test -v -run=AdjacentMixString
=== RUN ExampleAdjacentMixString
--- PASS: ExampleAdjacentMixString (0.00s)
PASS
ok ……/util 1.229s
執行通過了,但好像啥也沒測。
目前為止這個Example還不具備“測試”的作用,僅僅是一個能編譯通過的“文檔”。
Output注釋
Example測試不提供Fail等方法,但是可以通過标準輸出來實作簡單的斷言。
在執行Example測試時,測試架構會截獲标準輸出,并與"output:"後的注釋進行比較。如果相等的話,就能通過測試。
這樣,我們改造我們的Example。
func ExampleAdjacentMixString() {
fmt.Println(AdjacentMixString("", "abc"))
fmt.Println(AdjacentMixString("123", ""))
fmt.Println(AdjacentMixString("1", "abc"))
fmt.Println(AdjacentMixString("123", "a"))
fmt.Println(AdjacentMixString("123", "abcd"))
fmt.Println(AdjacentMixString("1234", "abc"))
// output:
// abc
// 123
// 1abc
// 1a23
// 1a2b3cd
// 1a2b3c4
}
再次運作。又通過了。和上面的輸出幾乎一緻就不貼了。
我們故意改錯一點,把output後的abc改成a看看是什麼樣的:
admin@……/util ‹release*›$ go test -v -run=AdjacentMixString
=== RUN ExampleAdjacentMixString
--- FAIL: ExampleAdjacentMixString (0.00s)
got:
abc
123
1abc
1a23
1a2b3cd
1a2b3c4
want:
a
123
1abc
1a23
1a2b3cd
1a2b3c4
FAIL
exit status 1
FAIL ……/util 1.316s
可以看到,這個Example成功發揮了一個“測試”的功能。
而文檔也變成了這個樣子:
Example的命名約定
Godoc使用特定的命名約定來關聯Example與其對應的被示例對象。
func ExampleFoo() // 關聯到Foo函數或類型
func ExampleA_Go() // 關聯到A類型的Go方法
func Example() // 關聯整個package
還可以通過下劃線加小寫字母的字尾,給同一個對象指定多個Example。比如可以這麼搞:
func ExampleAdjacentMixString() {
fmt.Println(AdjacentMixString("123", "abcd"))
// output:
// 1a2b3cd
}
func ExampleAdjacentMixString_thisIsB() {
fmt.Println(AdjacentMixString("123", ""))
// output:
// 123
}
func ExampleAdjacentMixString_thisIsC() {
fmt.Println(AdjacentMixString("", "abc"))
// output:
// abc
}
這樣文檔中的效果就是:
整個檔案作為Example
有的時候一個Example函數不足以表達整個子產品的功能,這個時候可以使用整個檔案作為Example。比如我給util package做一個Example檔案:
util_test.go
package util
import "fmt"
type A struct {
hi string
}
func (a *A) Go() {
fmt.Println("This is a file Example")
}
func Example() {
(&A{}).Go()
// Output:
// This is a file Example
}
這樣,我就可以在godoc上看到整個檔案了:
同樣的,你也可以建多個檔案,每個檔案中的Example函數用不同的下劃線字尾,來為同一個package建多個示例。
unordered output
最後,如果你的輸出是無法确定順序的,可以使用unordered output來代替output:
func ExampleUnorderedOuput() {
m := map[string]string{
"a": "b",
"c": "d",
"e": "f",
}
for k, v := range m {
fmt.Println(k, v)
}
// unordered output:
// a b
// c d
// e f
}
結語
今天我們學習了golang的一個很有意思的特性,Example測試。又漲了點姿勢對吧哈哈哈。