《Go程序設計語言》筆記四
包和 go 工具
包
Go 通過 import 語句導入的包一般在 $GOROOT、$GOPATH 下,$GOROOT 下導入的是 Go build-in 的包,比如 math/rand
;而 $GOPATH 下導入的是通過 go get
命令從互聯網上拉下的包,或者是自己之前寫的程序。
- 不管包的導入路徑是什麼,如果該包定義了一條命令(可執行 Go 程序),那麼它總是使用名稱 main。告訴 go build 必須調用連接器生成可執行文件
- 目錄中可能有一些名字以 _test.go 結尾,包名中會出現以 _test 結尾。這樣的目錄包含兩個包一個普通的,一個外部測試包。
_test
後綴告訴 go test 兩個包都要構建,並且指明文件屬於哪個包。外部測試包避免所依賴的導入圖中的循環依賴 - 有一些依賴管理工具會在包導入路徑的尾部追加版本號後綴,如
"gokpg.in/yaml.v2"
。包名不包含後綴,因此這種情況下包名為 yaml
重命名導入
import ( "crypto/rand" mrand "math/rand")
空導入
import ( _ "github.com/lib/pq")
空導入將包的名字重命名為 _,也就是無法在代碼中引用該包中的變數和函數。這會產生什麼影響呢?
雖然無法在代碼只能夠使用該包中的變數和函數,但該包下的包級別變數還是會進行聲明和初始化,init()
函數還是會執行。如上空導入的作用就是載入 postgres 的資料庫驅動,完成資料庫驅動的註冊。
go 工具
工作空間組織
$GOPATH 下有三個文件夾,分別是:src、pkg、bin
- src 存儲源代碼
- pkg 存儲構建工具編譯後的包,go install 命令將會在 pkg 產生相應的 .a 文件
- bin 存儲可執行文件
go build 命令構建所有需要的包以及它們所有的依賴性,然後丟棄除了最終可執行程序之外的所有編譯後的代碼。顯然如果當項目變得複雜,引用的包多了以後,重新編譯依賴性的時間明顯變慢。
go install 命令和 go build 命令相似,區別是它會保存每一個包的編譯代碼和命令,而不是把他們丟棄,結果保存在 $GOPATH/pkg 目錄下。
go build 命令會特殊對待導入路徑中包含路徑片段 internal 的情況,這些包叫內部包。內部包只能夠被位於以 internal 目錄的父目錄為根目錄的樹中。例如,給定下面的包,net/http/internal/chunked 可以從 net/http/httputil 或 net/http 導入,但是不能從 net/url 進行導入。
測試
在 *_test.go 文件中,是那種函數需要特殊對待,即功能測試函數、基準測試函數和示例函數。
- 功能測試函數 是以 Test 前綴命名的函數,用來檢測一些程序邏輯的正確性,go test 運行測試函數,並且報告結果是 PASS 還是 FAIL。
- 基準測試函數 的名稱以 Benchmark 開頭,用來測試某些操作的性能,go test 彙報操作的平均執行時間。
- 示例函數 以 Example 開頭,用來提供機器檢查過的文檔。
每一個測試文件必須導入 testing 包,這些函數簽名如下:
func TestXxx(t *testing.T) {}
- go test -v 輸出包中每個測試用力的名稱和執行的時間
- go test -run="regexp" -run 參數是一個正則表達式,它可以使得 go test 只運行那些測試函數名稱匹配給定模式的函數,而不用重新運行所有的測試用例
外部測試包
在編寫測試用例的時候,為了避免包之間的循環引用。比如 net/http 依賴於 net/url,那麼當我們在 net/url 保證寫測試時候引用到了 net/http,這就出現了循環引用。解決方法是將測試寫在 net/url_test 包中。
查看某個包內的 go 文件:
go list -f={{.GoFiles}} fmt
查看某個包內的測試文件:
go list -f={{.TestGoFiles}} fmt
查看某個包的外部測試包:
go list -f={{.XTestGoFiles}} fmt
如果一個包內的某些函數需要進行白盒測試,而這些函數又正巧是不可導出的呢?
這問題可以通過寫像 fmt/export_test.go 類似的文件,使用一個可導出的別名指向該函數:
package fmtvar IsSpace = isSpacevar Parsenum = parsenum
cover 覆蓋率
從本質上看,測試從來不會結束,因為可能的輸入有無限多種情況。無論多少測試都無法證明一個包是沒 bug 的,最好的情況下,這些包是可以在很多重要場景下使用的。
go tool cover
工具為我們查看測試代碼的覆蓋率起到了很好的幫助,它可以輸出一個 html 文檔,在瀏覽器中打開大大增強了可閱讀性。
benchmark 基準測試
基準測試就是在一定的工作負載之下檢測程序性能的一種方法。在 Go 裡面,基準測試函數看上去像一個測試函數,但是前綴是 Benchmark 並且擁有 *testing.B 參數用來提供大多數和 *testing.T 相同的方法,額外增加了一些與性能測試相關方法,其中提供了一個整形成員 N,用來指定被檢測操作的執行次數。
import "testing"func BenchmarkXxx(b *testing.B) { for i := 0; i < b.N; i++ { // test code }}
默認情況下不會執行任何基準測試,標記 -bench 參數指定了要運行的基準測試,它是一個匹配 Benchmark 函數名稱的正則表達式,它有默認值不匹配任何函數。模式 "." 使它匹配包 word 中所有的基準測試函數。
go test -bench="."go test -bench="Xxx"
profile 性能剖析
性能剖析是通過自動化手段在程序執行過程中給予一些性能事件的採樣來進行性能評價,然後再從這些採樣中推斷分析,得到統計報告。
Go 支持很多性能剖析方式,每一個都和一個不同方面的性能指標相關,但是它們都需要記錄一些相關事件,每一個都有一個相關的棧信息————在事件發生時活躍的函數調用棧。工具 go test 內置支持一些類別的性能剖析。
CPU 性能剖析識別執行過程中需要 CPU 最多的函數。
go test -cpuprofile=cpu.out
堆性能剖析識別出負責分配最多內存的語句。
go test -blockprofile=block.out
阻塞性能剖析識別出那些阻塞協程最久的操作。
go test -memprofile=mem.out
性能剖析並非只能夠通過命令行來啟動,對於長時間運行的程序也可以開啟性能剖析,Go 運行時的性能剖析特性可以讓程序員通過 runtime API 來啟用。
在獲取性能分析結果後,需要使用 pprof 工具來分析它,基本的參數有兩個,產生性能剖析結果的可執行文件和性能剖析日誌。
go tool pprof
Example 函數
函數以 Example 開頭,既沒有參數也沒有返回結果,如:
func ExampleXxx() { fmt.Println(Xxx())}
示例函數有三個目的:
1. 作為文檔,比起乏味的描述,舉例子是描述函數功能最簡潔直觀的方法。和普通文檔不一樣,ExampleXxx 函數是 Go 代碼必須要通過編譯檢查,它會隨著代碼的改變而改變。2. 可以通過 go test 運行的可執行測試。3. 提供手動實驗代碼。推薦閱讀: