gopl-zh.github.com/ch11/ch11-02-4.md

3.7 KiB
Raw Blame History

11.2.4. 擴展測試包

考慮下這兩個包net/url包提供了URL解析的功能net/http包提供了web服務和HTTP客戶端的功能。如我們所料上層的net/http包依賴下層的net/url包。然後net/url包中的一個測試是演示不同URL和HTTP客戶端的交互行爲。也就是説一個下層包的測試代碼導入了上層的包。

這樣的行爲在net/url包的測試代碼中會導致包的循環依賴正如圖11.1中向上箭頭所示同時正如我們在10.1節所講的Go語言規范是禁止包的循環依賴的。

不過我們可以通過測試擴展包的方式解決循環依賴的問題也就是在net/url包所在的目録聲明一個獨立的url_test測試擴展包。其中測試擴展包名的_test後綴告訴go test工具它應該建立一個額外的包來運行測試。我們將這個擴展測試包的導入路徑視作是net/url_test會更容易理解但實際上它併不能被其他任何包導入。

因爲測試擴展包是一個獨立的包所以可以導入測試代碼依賴的其他的輔助包包內的測試代碼可能無法做到。在設計層面測試擴展包是在所以它依賴的包的上層正如圖11.2所示。

通過迴避循環導入依賴,擴展測試包可以更靈活的編寫測試,特别是集成測試(需要測試多個組件之間的交互),可以像普通應用程序那樣自由地導入其他包。

我們可以用go list命令査看包對應目録中哪些Go源文件是産品代碼哪些是包內測試還哪些測試擴展包。我們以fmt包作爲一個例子GoFiles表示産品代碼對應的Go源文件列表也就是go build命令要編譯的部分。

{% raw %}

$ go list -f={{.GoFiles}} fmt
[doc.go format.go print.go scan.go]

{% endraw %}

TestGoFiles表示的是fmt包內部測試測試代碼以_test.go爲後綴文件名不過隻在測試時被構建

{% raw %}

$ go list -f={{.TestGoFiles}} fmt
[export_test.go]

{% endraw %}

包的測試代碼通常都在這些文件中不過fmt包併非如此稍後我們再解釋export_test.go文件的作用。

XTestGoFiles表示的是屬於測試擴展包的測試代碼也就是fmt_test包因此它們必須先導入fmt包。同樣這些文件也隻是在測試時被構建運行

{% raw %}

$ go list -f={{.XTestGoFiles}} fmt
[fmt_test.go scan_test.go stringer_test.go]

{% endraw %}

有時候測試擴展包也需要訪問被測試包內部的代碼例如在一個爲了避免循環導入而被獨立到外部測試擴展包的白盒測試。在這種情況下我們可以通過一些技巧解決我們在包內的一個_test.go文件中導出一個內部的實現給測試擴展包。因爲這些代碼隻有在測試時才需要因此一般會放在export_test.go文件中。

例如fmt包的fmt.Scanf函數需要unicode.IsSpace函數提供的功能。但是爲了避免太多的依賴fmt包併沒有導入包含鉅大表格數據的unicode包相反fmt包有一個叫isSpace內部的簡易實現。

爲了確保fmt.isSpace和unicode.IsSpace函數的行爲一致fmt包謹慎地包含了一個測試。是一個在測試擴展包內的白盒測試是無法直接訪問到isSpace內部函數的因此fmt通過一個祕密出口導出了isSpace函數。export_test.go文件就是專門用於測試擴展包的祕密出口。

package fmt

var IsSpace = isSpace

這個測試文件併沒有定義測試代碼它隻是通過fmt.IsSpace簡單導出了內部的isSpace函數提供給測試擴展包使用。這個技巧可以廣泛用於位於測試擴展包的白盒測試。