Go的程式碼規範指南-新人必看
- 2019 年 10 月 11 日
- 筆記

項目約定:
環境設置:*nix環境或者Mac環境,安裝go語言目錄,默認是usr/local/go,如果想要讓程式碼運行,需要將項目放到usr/local/go/src目錄下,如果忘記了go的配置目錄,可以通過go env查看go的環境變數設置。如果不希望每次使用sudo許可權執行,可以通過設置用戶目錄下創建~/.bashrc文件,增加 用戶目錄的環境變數設置比如
export GOPATH=~/go的安裝目錄 source ~/.bashrc 激活配置文件,然後可以通過go env或者echo $GOPATH命令看到變數輸出改變了。 如果不想上面那麼搞,則可以直接設置多個GOPATH目錄。 假設現在本地硬碟上有3個Go程式碼工程,分別為~/work/go-proj1、~/work2/goproj2和 ~/work3/work4/go-proj3,那麼GOPATH可以設置為如下內容: export GOPATH=~/work/go-proj1:~/work2/goproj2:~/work3/work4/go-proj3 經過這樣的設置後,你可以在任意位置對以上的3個工程進行構建。
程式碼命名:以後綴.go作為開發文件,以xx_test.go作為測試文件。
列印日誌:通過fmt包下的Pringtf函數或者log模組的日誌功能
1. 命名 命名規則涉及變數、常量、全局函數、結構、介面、方法等的命名。Go語言從語法層面進 行了以下限定: 任何需要對外暴露的名字必須以大寫字母開頭,不需要對外暴露的則應該以小寫 字母開頭。 Go推行駝峰命名法排斥下劃線命名法 2、排列 Go的程式碼左花括弧嚴格要求不能換行 3、程式碼擺放亂 go fmt xx.go格式化為標準go文件 4、go的遠程包導入 import可以導入本地包,也可以導入GitHub上的遠程包,比如如下示例 package main import ( "fmt" "github.com/myteam/exp/crc32" ) 然後,在執行go build或者go install之前,只需要加這麼一句: go get github.com/myteam/exp/crc32
目錄結構:
舉個樣例:

程式碼doc文件輸出:
版權說明注釋、包說明注釋、函數說明注釋和最後添 加的遺留問題說明,舉個例子 $ go doc foo本地輸出文檔化 godoc -http=:76 -path="." 瀏覽器訪問http://localhost:76/pkg/foo/輸出文檔化

Go的單元測試:
Go的單元測試函數分為兩類:功能測試函數和性能測試函數
執行功能單元測試非常簡單,直接執行go test命令 go test simplematch(上面目錄結構圖) 性能測試的,增加-test.bench參數 go test–test.bench add.go
程式碼編程:
變數申明:
關鍵字var,而類型資訊放在變數名之後,示例如下: var v1 int var v2 string var v3 [10]int // 數組 var v4 []int // 數組切片 var v5 struct { f int } var v6 *int // 指針 var v7 map[string]int // map,key為string類型,value為int類型 var v8 func(a int) int 變數聲明語句不需要使用分號作為結束符。 省略多個重複var的申明: var ( v1 int v2 string )
變數初始化:
var v1 int = 10 // 正確的使用方式1 var v2 = 10 // 正確的使用方式2,編譯器可以自動推導出v2的類型 v3 := 10 // 正確的使用方式3,編譯器可以自動推導出v3的類型
變數賦值:
在Go語法中,變數初始化和變數賦值是兩個不同的概念 var v10 int v10 = 123
返回值的處理:
func GetName() (firstName, lastName, nickName string) { return "May", "Chan", "Chibi Maruko" } 若只想獲得nickName,則函數調用語句可以用如下方式編寫: _, _, nickName := GetName()
常量:
在Go語言中,常量是指編譯期間就已知且不可改變的值。常量可以是數值類型(包括整型、
浮點型和複數類型)、布爾類型、字元串類型等
常量定義:
const a, b, c = 3, 4, "foo" //a=3,b=4,c="foo", 無類型整型和字元串常量 const zero = 0.0 const ( size int64 = 1024 eof = -1 // 無類型浮點常量 // 無類型整型常量 ) const Pi float64 = 3.14159265358979323846 const u, v float32 = 0, 3
預定義常量:
Go語言預定義了這些常量:true、false和iota。

枚舉:
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday numberOfDays// 這個常量沒有導出,因為大寫字母外包可見,小寫字母屬於私有 )
數組切片:
創建一個初始元素個數為5的數組切片,元素初始值為0: mySlice1 := make([]int, 5) 創建一個初始元素個數為5的數組切片,元素初始值為0,並預留10個元素的存儲空間: mySlice2 := make([]int, 5, 10) 直接創建並初始化包含5個元素的數組切片: mySlice3 := []int{1, 2, 3, 4, 5}
元素遍歷:
傳統的元素遍歷方法如下: for i := 0; i <len(mySlice); i++ { fmt.Println("mySlice[", i, "] =", mySlice[i]) } 使用range關鍵字可以讓遍歷程式碼顯得更整潔。range表達式有兩個返回值,第一個是索引, 第二個是元素的值: for i, v := range mySlice { fmt.Println("mySlice[", i, "] =", v) }
數組切片的內容複製:
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) // 只會複製slice1的前3個元素到slice2中 copy(slice1, slice2) // 只會複製slice2的3個元素到slice1的前3個位置
map的使用:
申明:var myMap map[string] PersonInfo 創建:myMap = make(map[string] PersonInfo) 性能優化-指定分配存儲空間:myMap = make(map[string] PersonInfo, 100) 元素的賦值: myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."} 元素的刪除: delete(myMap, "1234") 元素的查找: value, ok := myMap["1234"] if ok {// 找到了 // 處理找到的value }
流程式控制制的使用:
if a < 5 { return 0 } else { return 1 } 條件語句不需要使用括弧將條件包含起來(); 無論語句體內有幾條語句,花括弧{}都是必須存在的; 左花括弧{必須與if或者else處於同一行; 在if之後,條件語句之前,可以添加變數初始化語句,使用;間隔; 在有返回值的函數中,不允許將「最終的」return語句包含在if...else...結構中, 否則會編譯失敗: switch i { case 0: fmt.Printf("0") case 1: fmt.Printf("1") case 2: fallthrough case 3: fmt.Printf("3") case 4, 5, 6: fmt.Printf("4, 5, 6") default: fmt.Printf("Default") } 在使用switch結構時,我們需要注意以下幾點: 左花括弧{必須與switch處於同一行; 條件表達式不限制為常量或者整數; 單個case中,可以出現多個結果選項; 與C語言等規則相反,Go語言不需要用break來明確退出一個case; 只有在case中明確添加fallthrough關鍵字,才會繼續執行緊跟的下一個case; 可以不設定switch之後的條件表達式,在此種情況下,整個switch結構與多個 if...else...的邏輯作用等同 sum := 0 for i := 0; i < 10; i++ { sum += i } 在條件表達式中也支援多重賦值,如下所示: a := []int{1, 2, 3, 4, 5, 6} for i, j := 0, len(a) – 1; i < j; i, j = i + 1, j – 1 { a[i], a[j] = a[j], a[i] } 使用循環語句時,需要注意的有以下幾點。 左花括弧{必須與for處於同一行。 Go語言中的for循環與C語言一樣,都允許在循環條件中定義和初始化變數,唯一的區別 是,Go語言不支援以逗號為間隔的多個賦值語句,必須使用平行賦值的方式來初始化多 個變數。 Go語言的for循環同樣支援continue和break來控制循環,但是它提供了一個更高級的 break,可以選擇中斷哪一個循環 goto語句的語義非常簡單,就是跳轉到本函數內的某個標籤,如: func myfunc() { i := 0 HERE: fmt.Println(i) i++ if i < 10 { goto HERE } }
函數的使用:
1. 不定參數類型 不定參數是指函數傳入的參數個數為不定數量。為了做到這點,首先需要將函數定義為接受 不定參數類型: func myfunc(args ...int) { for _, arg := range args { fmt.Println(arg) } } 如下方式調用: myfunc(2, 3, 4) myfunc(1, 3, 7, 13) 2. 不定參數的傳遞 假設有另一個變參函數叫做myfunc3(args ...int),下面的例子演示了如何向其傳遞變參: func myfunc(args ...int) { // 按原樣傳遞 myfunc3(args...) // 傳遞片段,實際上任意的int slice都可以傳進去 myfunc3(args[1:]...) } 3. 任意類型的不定參數 之前的例子中將不定參數類型約束為int,如果你希望傳任意類型,可以指定類型為 interface{}。下面是Go語言標準庫中fmt.Printf()的函數原型: func Printf(format string, args ...interface{}) { // ... }
並發的使用:
在一個函數調用前加上go關鍵字,這次調用就會在一個新的goroutine中並發執行。當被調用 的函數返回時,這個goroutine也自動結束了。需要注意的是,如果這個函數有返回值,那麼這個 返回值會被丟棄
「不要通過共享記憶體來通訊,而應該通過通訊來共享記憶體。」
一般channel的聲明形式為: var chanName chan ElementType 與一般的變數聲明不同的地方僅僅是在類型之前加了chan關鍵字。ElementType指定這個 channel所能傳遞的元素類型。舉個例子,我們聲明一個傳遞類型為int的channel: var ch chan int 或者,我們聲明一個map,元素是bool型的channel: var m map[string] chan bool 定義一個channel也很簡單,直接使用內置的函數make()即可: ch := make(chan int) 在channel的用法中,最常見的包括寫入和讀出。將一個數據寫入(發送)至channel的語法 很直觀,如下: ch <- value 向channel寫入數據通常會導致程式阻塞,直到有其他goroutine從這個channel中讀取數據。 從 channel中讀取數據的語法是 value := <-ch 如果channel之前沒有寫入數據,那麼從channel中讀取數據也會導致程式阻塞,直到channel 中被寫入數據為止。 我們之後還會提到如何控制channel只接受寫或者只允許讀取,即單向 channel。
Go語言直接在語言級別支援select關鍵字,用於處理非同步IO 問題。 select的用法與switch語言非常類似,由select開始一個新的選擇塊,每個選擇條件由 case語句來描述。 與switch語句可以選擇任何可使用相等比較的條件相比,select有比較多的 限制, 其中最大的一條限制就是每個case語句里必須是一個IO操作,大致的結構如下 select { case <-chan1: // 如果chan1成功讀到數據,則進行該case處理語句 case chan2 <- 1: // 如果成功向chan2寫入數據,則進行該case處理語句 d efault: // 如果上面都沒有成功,則進入default處理流程 } 比如如下的死循環例子: ch := make(chan int, 1) for { select { case ch <- 0: case ch <- 1: } i := <-ch fmt.Println("Value received:", i) }
增加buffer的channel
c := make(chan int, 1024) 在調用make()時將緩衝區大小作為第二個參數傳入即可 for i := range c { fmt.Println("Received:", i) } 利用超時機制,防止死鎖問題 // 首先,我們實現並執行一個匿名的超時等待函數 timeout := make(chan bool, 1) go func() { time.Sleep(1e9) // 等待1秒鐘 timeout <- true }() // 然後我們把timeout這個channel利用起來 select { case <-ch: // 從ch中讀取到數據 case <-timeout: // 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據 }
單向channel變數的聲明非常簡單,如下: var ch1 chan int // ch1是一個正常的channel,不是單向的 var ch2 chan<- float64// ch2是單向channel,只用於寫float64數據 var ch3 <-chan int // ch3是單向channel,只用於讀取int數據 ch4 := make(chan int) ch5 := <-chan int(ch4) // ch5就是一個單向的讀取 ch6 := chan<- int(ch4) // ch6 是一個單向的寫入channel
關閉channel非常簡單,直接使用Go語言內置的close()函數即可: close(ch) 判斷是否關閉 x, ok := <-ch
多核並行化計算任務
type Vector []float64 // 分配給每個CPU的計算任務 func (v Vector) DoSome(i, n int, u Vector, c chan int) { for ; i < n; i++ { v[i] += u.Op(v[i]) } c <- 1 // 發訊號告訴任務管理者我已經計算完成了 } const NCPU = 16 func (v Vector) DoAll(u Vector) { c := make(chan int, NCPU) // 用於接收每個CPU的任務完成訊號 for i := 0; i < NCPU; i++ { go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c) } // 等待所有CPU的任務完成 for i := 0; i < NCPU; i++ { <-c // 獲取到一個數據,表示一個CPU計算完成了 } // 到這裡表示所有計算已經結束 } Go語言如果不支援多核默認的話,可以手動設置 程式碼中啟動goroutine之前先調用以下這個語句以設置使用16個CPU 核心: runtime.GOMAXPROCS(16)
對於從全局的角度只需要運行一次的程式碼,比如全局初始化操作,Go語言提供了一個Once 類型來保證全局的唯一性操作,具體程式碼如下: var a string var once sync.Once func setup() { a = "hello, world" } func doprint() { once.Do(setup) print(a) } func twoprint() { go doprint() go doprint() }
網路編程
socket編程: (1) 建立Socket:使用socket()函數。 (2) 綁定Socket:使用bind()函數。 (3) 監聽:使用listen()函數。或者連接:使用connect()函數。 (4) 接受連接:使用accept()函數。 (5) 接收:使用receive()函數。或者發送:使用send()函數 Go的網路編程: Go語言標準庫對此過程進行了抽象和封裝。無論我們期望使用什麼協議建立什麼形式的連 接,都只需要調用net.Dial()即可 TCP鏈接: conn, err := net.Dial("tcp", "192.168.0.10:2100") UDP鏈接: conn, err := net.Dial("udp", "192.168.0.12:975") ICMP鏈接(使用協議名稱): conn, err := net.Dial("ip4:icmp", "www.baidu.com") ICMP鏈接(使用協議編號): conn, err := net.Dial("ip4:1", "10.0.0.3") 目前,Dial()函數支援如下幾種網路協議:"tcp"、"tcp4"(僅限IPv4)、"tcp6"(僅限 IPv6)、"udp"、"udp4"(僅限IPv4)、"udp6"(僅限IPv6)、"ip"、"ip4"(僅限IPv4)和"ip6" (僅限IPv6) HTTP編程 HTTP(HyperText Transfer Protocol,超文本傳輸協議)是互聯網上應用最為廣泛的一種網路 協議, 定義了客戶端和服務端之間請求與響應的傳輸標準 基本方法 net/http包的Client類型提供了如下幾個方法,讓我們可以用最簡潔的方式實現 HTTP 請求: func (c *Client) Get(url string) (r *Response, err error) func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) func (c *Client) Head(url string) (r *Response, err error) func (c *Client) Do(req *Request) (resp *Response, err error) RPC編程 RPC 採用客戶端—伺服器(Client/Server)的工作模式。請求程式就是一個客戶端(Client), 而服務提供程式就是一個伺服器(Server)。當執行一個遠程過程調用時,客戶端程式首先發送一 個帶有參數的調用資訊到服務端,然後等待服務端響應。在服務端,服務進程保持睡眠狀態直到 客戶端的調用資訊到達為止。當一個調用資訊到達時,服務端獲得進程參數,計算出結果, 並向 客戶端發送應答資訊,然後等待下一個調用。最後,客戶端接收來自服務端的應答資訊, 獲得進 程結果,然後調用執行並繼續進行 一個對象中只有滿足如下這些條件的方法,才能被 RPC 服務端設置為可供遠程訪問: 必須是在對象外部可公開調用的方法(首字母大寫); 必須有兩個參數,且參數的類型都必須是包外部可以訪問的類型或者是Go內建支援的類 型; 第二個參數必須是一個指針; 方法必須返回一個error類型的值。 以上4個條件,可以簡單地用如下一行程式碼表示: func (t *T) MethodName(argType T1, replyType *T2) error JSON處理 使用json.Marshal()函數可以對一組數據進行JSON格式的編碼。json.Marshal()函數 的聲明如下: func Marshal(v interface{}) ([]byte, error) 假如有如下一個Book類型的結構體: type Book struct { Title string Authors []string Publisher string IsPublished bool Price float } 並且有如下一個 Book 類型的實例對象: gobook := Book{ "Go語言編程", ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"], "ituring.com.cn", true, 9.99 } 然後,我們可以使用 json.Marshal() 函數將gobook實例生成一段JSON格式的文本: b, err := json.Marshal(gobook) 如果編碼成功,err 將賦於零值 nil,變數b 將會是一個進行JSON格式化之後的[]byte 類型: b == []byte(`{ "Title": "Go語言編程", "Authors": ["XuShiwei", "HughLv", "Pandaman", "GuaguaSong", "HanTuo", "BertYuan", "XuDaoli"], "Publisher": "ituring.com.cn", "IsPublished": true, "Price": 9.99 }`) 在Go中,JSON轉化前後的數據類型映射如下。 布爾值轉化為JSON後還是布爾類型。 浮點數和整型會被轉化為JSON裡邊的常規數字。 字元串將以UTF-8編碼轉化輸出為Unicode字符集的字元串,特殊字元比如<將會被轉義為 u003c。 數組和切片會轉化為JSON裡邊的數組,但[]byte類型的值將會被轉化為 Base64 編碼後 的字元串,slice類型的零值會被轉化為 null。 結構體會轉化為JSON對象,並且只有結構體裡邊以大寫字母開頭的可被導出的欄位才會 被轉化輸出,而這些可導出的欄位會作為JSON對象的字元串索引。 3 轉化一個map類型的數據結構時,該數據的類型必須是 map[string]T(T可以是 encoding/json 包支援的任意數據類型)。 可以使用json.Unmarshal()函數將JSON格式的文本解碼為Go裡邊預期的數據結構。 json.Unmarshal()函數的原型如下: func Unmarshal(data []byte, v interface{}) error 在解碼JSON 數據的過程中,JSON數據裡邊的元素類型將做如下轉換: JSON中的布爾值將會轉換為Go中的bool類型; 數值會被轉換為Go中的float64類型; 字元串轉換後還是string類型; JSON數組會轉換為[]interface{}類型; JSON對象會轉換為map[string]interface{}類型; null值會轉換為nil。 在Go的標準庫encoding/json包中,允許使用map[string]interface{}和[]interface{} 類型的值來分別存放未知結構的JSON對象或數組
安全編程:
常見的單密鑰加密演算法有DES、AES、 RC4等。 私鑰和公鑰都可以被用作加密或者解密,但是用私 鑰加密的明文,必須要用對應的公鑰解密,用公鑰加密的明文,必須用對應的私鑰解密。 常見的 雙密鑰加密演算法有RSA等。 常見的哈希演算法包括MD5、SHA-1等。 證書加密HTTPS (1) 在瀏覽器中輸入HTTPS協議的網址。 (2) 伺服器向瀏覽器返回證書,瀏覽器檢查該證書的合法性 (3) 驗證合法性 (4) 瀏覽器使用證書中的公鑰加密一個隨機對稱密鑰,並將加密後的密鑰和使用密鑰加密後 的請求URL一起發送到伺服器 (5) 伺服器用私鑰解密隨機對稱密鑰,並用獲取的密鑰解密加密的請求URL (6) 伺服器把用戶請求的網頁用密鑰加密,並返回給用戶 (7) 用戶瀏覽器用密鑰解密伺服器發來的網頁數據,並將其顯示出來 SSL協議由兩層組成,上層協議包括SSL握手協議、更改密碼規格協議、警報協議,下層協 議包括SSL記錄協議。 SSL握手協議建立在SSL記錄協議之上,在實際的數據傳輸開始前,用於在客戶與伺服器之 間進行「握手」。「握手」是一個協商過程。這個協議使得客戶和伺服器能夠互相鑒別身份,協商 加密演算法。在任何數據傳輸之前,必須先進行「握手」。 在「握手」完成之後,才能進行SSL記錄協議,它的主要功能是為高層協議提供數據封裝、 壓縮、添加MAC、加密等支援
錯誤處理策略:
//1、將錯誤返回給調用者 resp, err := http.Get(url) if err != nil{ return nill, err } //2、將構造新的資訊返回給調用者 doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { return nil, fmt.Errorf("parsing %s as HTML: %v", url,err) } //3、如果錯誤發生後,程式無法繼續運行,我們就可以採用第三種策略:輸出錯誤資訊並結束程式。 //需 要注意的是,這種策略只應在main中執行。對庫函數而言,應僅向上傳播錯誤,除非該錯誤意味著 //程式內部包含不一致性,即遇到了bug,才能在庫函數中結束程式。 if err := WaitForServer(url); err != nil { fmt.Fprintf(os.Stderr, "Site is down: %vn", err) os.Exit(1) } //4、第四種策略:有時,我們只需要輸出錯誤資訊就足夠了,不需要中斷程式的運行。我們可以通過 log包提供函數 if err := Ping(); err != nil { log.Printf("ping failed: %v; networking disabled",err) } //或者標準錯誤流輸出錯誤資訊。 if err := Ping(); err != nil { fmt.Fprintf(os.Stderr, "ping failed: %v; networking disabledn", err) } //5、第五種,也是最後一種策略:我們可以直接忽略掉錯誤。 dir, err := ioutil.TempDir("", "scratch") if err != nil { return fmt.Errorf("failed to create temp dir: %v",err) } // ...use temp dir... os.RemoveAll(dir) //儘管os.RemoveAll會失敗,但上面的例子並沒有做錯誤處理。 //這是因為作業系統會定期的清理臨時 目錄。正因如此,雖然程式沒有處理錯誤, //但程式的邏輯不會因此受到影響。我們應該在每次函數 調用後,都養成考慮錯誤處理的習慣, //當你決定忽略某個錯誤時,你應該在清晰的記錄下你的意 圖。
資料庫編程:
import "database/sql" func listTracks(db sql.DB, artist string, minYear, maxYear int) { result, err := db.Exec( "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?", artist, minYear, maxYear) // ... } Exec方法使用SQL字面量替換在查詢字元串中的每個'?';SQL字面量表示相應參數的值, 它有可能 是一個布爾值,一個數字,一個字元串,或者nil空值。用這種方式構造查詢可以幫助避免SQL注入 攻擊; 如果希望通過類型開關來控制入參可以使用如下方式: func sqlQuote(x interface{}) string { switch x := x.(type) { case nil: return "NULL" case int, uint: return fmt.Sprintf("%d", x) // x has type interface{} here. case bool: if x { return "TRUE" } return "FALSE" case string: return sqlQuoteString(x) // (not shown) default: panic(fmt.Sprintf("unexpected type %T: %v", x, x)) } }
測試編程:
在 *_test.go 文件中,有三種類型的函數:測試函數、基準測試(benchmark)函數、示例函數。 一 個測試函數是以Test為函數名前綴的函數,用於測試程式的一些邏輯行為是否正確; go test命令會 調用這些測試函數並報告測試結果是PASS或FAIL。 基準測試函數是以Benchmark為函數名前綴的 函數,它們用于衡量一些函數的性能; go test命令會多次運行基準函數以計算一個平均的執行時 間。 示例函數是以Example為函數名前綴的函數,提供一個由編譯器保證正確性的示例文檔。 go test命令會遍歷所有的*_test.go文件中符合上述命名規則的函數,生成一個臨時的main包用於 調用相應的測試函數,接著構建並運行、報告測試結果,最後清理測試中生成的臨時文件。
測試函數:
每個測試函數必須導入testing包。測試函數有如下的簽名:
func TestName(t *testing.T) { // ... } 測試函數的名字必須以Test開頭,可選的後綴名必須以大寫字母開頭: func TestLog(t *testing.T) { /* ... */ } 其中t參數用於報告測試失敗和附加的日誌資訊。 比如如下例子: // Package word provides utilities for word games. package word // IsPalindrome reports whether s reads the same forward and backward. // (Our first attempt.) func IsPalindrome(s string) bool { for i := range s { if s[i] != s[len(s)‐1‐i] { return false } } return true } 在相同的目錄下,創建word_test.go測試文件 package word import "testing" func TestPalindrome(t *testing.T) { if !IsPalindrome("detartrated") { t.Error(`IsPalindrome("detartrated") = false`) } if !IsPalindrome("kayak") { t.Error(`IsPalindrome("kayak") = false`) } } func TestNonPalindrome(t *testing.T) { if IsPalindrome("palindrome") { t.Error(`IsPalindrome("palindrome") = true`) } } 參數 ‐v 可用於列印每個測試函數的名字和運行時間: go test ‐v === RUN TestPalindrome ‐‐‐ PASS: TestPalindrome (0.00s) === RUN TestNonPalindrome ‐‐‐ PASS: TestNonPalindrome (0.00s) === RUN TestFrenchPalindrome ‐‐‐ FAIL: TestFrenchPalindrome (0.00s) word_test.go:28: IsPalindrome("été") = false === RUN TestCanalPalindrome ‐‐‐ FAIL: TestCanalPalindrome (0.00s) word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false FAIL exit status 1 FAIL gopl.io/ch11/word1 0.017s 參數‐run對應一個正則表達式,只有測試函數名被它正確匹配的測試函數才會被go test測試命令 運行: go test ‐v ‐run="French|Canal" === RUN TestFrenchPalindrome ‐‐‐ FAIL: TestFrenchPalindrome (0.00s) word_test.go:28: IsPalindrome("été") = false === RUN TestCanalPalindrome ‐‐‐ FAIL: TestCanalPalindrome (0.00s) word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false FAIL exit status 1 FAIL gopl.io/ch11/word1 0.014s
報告的輸出:
$ go tool cover Usage of 'go tool cover': Given a coverage profile produced by 'go test': go test ‐coverprofile=c.out Open a web browser displaying annotated source code: go tool cover ‐html=c.out ...
基準測試:
基準測試是測量一個程式在固定工作負載下的性能。在Go語言中,基準測試函數和普通測試函數 寫法類似, 但是以Benchmark為前綴名,並且帶有一個 *testing.B 類型的參數; *testing.B 參數除 了提供和 *testing.T 類似的方法,還有額外一些和性能測量相關的方法。 它還提供了一個整數N, 用於指定操作執行的循環次數。 下面是IsPalindrome函數的基準測試,其中循環將執行N次。 import "testing" func BenchmarkIsPalindrome(b *testing.B) { for i := 0; i < b.N; i++ { IsPalindrome("A man, a plan, a canal: Panama") } } $ go test ‐cpuprofile=cpu.out $ go test ‐blockprofile=block.out $ go test ‐memprofile=mem.out go test ‐run=NONE ‐bench=ClientServerParallelTLS64 ‐cpuprofile=cpu.log net/http PASS BenchmarkClientServerParallelTLS64‐8 1000 3141325 ns/op 143010 B/op 1747 allocs/op ok net/http 3.395s $ go tool pprof ‐text ‐nodecount=10 ./http.test cpu.log 2570ms of 3590ms total (71.59%) Dropped 129 nodes (cum <= 17.95ms) Showing top 10 nodes out of 166 (cum >= 60ms)
示例函數:
第三種被go test特別對待的函數是示例函數,以Example為函數名開頭。示例函數沒有函數參數 和返回值。 func ExampleIsPalindrome() { fmt.Println(IsPalindrome("A man, a plan, a canal: Panama")) fmt.Println(IsPalindrome("palindrome")) // Output: // true // false } 示例函數有三個用處。 最主要的一個是作為文檔: 一個包的例子可以更簡潔直觀的方式來演示函數 的用法,比文字描述更直接易懂,特別是作為一個提醒或快速參考時。 一個示例函數也可以方便展 示屬於同一個介面的幾種類型或函數之間的關係,所有的文檔都必須關聯到一個地方,就像一個類 型或函數聲明都統一到包一樣。同時,示例函數和注釋並不一樣,示例函數是真實的Go程式碼,需 要接受編譯器的編譯時檢查, 這樣可以保證源程式碼更新時,示例程式碼不會脫節。 示例文檔的第二個用處是,在 執行測試的時候也會運行示例函數測試。如果示例函數內含 有類似上面例子中的 格式的注釋, 那麼測試工具會執行這個示例函數,然後檢查示例函 數的標準輸出與注釋是否匹配。 示例函數的第三個目的提供一個真實的演練場。 http://golang.org 就是由godoc提供的文檔服務
本文的內容來源於Go語言編程和Go程式設計語言,如果有需要可以購買書籍查看。
後續會持續更新Go語言的生態內容,如果有需要可以關注。或者百度搜索wolf_love666。