1. 程式人生 > 實用技巧 >Golang之應用測試

Golang之應用測試

Go 應用測試

測試的覆蓋率

命令:

go test ./ -v -cover

在《Go Web 程式設計》一書中,有以下結論:

這並不是絕對的,測試檔案可以在不同的包,進行測試也是不會出現問題的。

但是這樣的說法引起了我的興趣。

果然,執行測試的時候新增引數-cover的時候,如果不在同一個包,將會輸出以下內容:

coverage: [no statements]

是沒有辦法得到,相關測試程式碼覆蓋率的資料的。


如果測試檔案和被測試檔案處於同一包下,才可以得到測試程式碼覆蓋率相關資料的輸出。

 coverage: 95.0% of statements

並行測試

命令:

go test ./ -v -cover -parallel 3

-parallel 3表示最多希望 3個測試並行執行。

併發測試,利用多核優勢,使用 Parallel 方法的函式必須 > 1,否則無法使用併發優勢只有一個測試函式使用 Parallel 是沒有效果的。

可通過執行以下程式碼,並對比測試時間即可驗證結論:

package main

 import (
   "testing"
   "time"
 )

 func TestParallel_1(t *testing.T) {
   t.Parallel()
   time.Sleep(1 * time.Second)
 }

 func TestParallel_2(t *testing.T) {
   t.Parallel()
   time.Sleep(2 * time.Second)
 }

 func TestParallel_3(t *testing.T) {
   t.Parallel()
   time.Sleep(3 * time.Second)
 }

基準測試

命令:

go test -v -cover -bench="BenchmarkQueryUser" -run x

上面的命令既運行了基準測試,也運行了功能測試。如果需要,使用者也可以通過執行標誌-run來忽略功能測試。-run標誌用於指定需要被執行的功能測試用例,如果使用者把一個不存在的功能測試名字用作-run標誌的引數,那麼所有功能測試都將被忽略。

上面的命令中使用-run x,如果測試中不存在任何名字為x的功能測試用例,因此所有功能測試都不會被執行。

基準測試函式格式為:

func BenchmarkXX×(*testing. B){ ... }

舉例:

//基準測試
func BenchmarkQueryUser(b *testing.B)  {
	for i := 0; i < b.N; i++ {
		user,err := userinfo.QueryUserById(1)
		if err != nil{
			b.Fatal("測試不通過,出現異常,err :",err)
		}else {
			fmt.Println("查詢到資料:Result User=",*user)
		}
	}
}

輸出如下:

BenchmarkQueryUser-8        4711            282508 ns/op
PASS
coverage: 0.0% of statements
ok      Go_test/src/unitTest    1.416s

注意for迴圈裡的 b.N表示迴圈塊內的語句將執行b.N次。

在進行基準測試時,測試用例的迭代次數是由Go自行決定的,雖然使用者可以通過限制基準測試的執行時間達到限制迭代次數的目

的,但使用者是無法直接指定迭代次數的——測試程式將進行足夠多次的迭代,直到獲得一個準確的測量值為止。

HTTP 測試

Go不經為單元測試提供了包,還為Go Web應用提供了專有的包--testing-httptest包。

Go Web應用的單元測試可以通過testing/httptest包來完成。這個包提供了模擬一個Web伺服器所需的設施,使用者可以利用net/http包中的客戶端函式向這個伺服器傳送HTTP請求,然後獲取模擬伺服器返回的HTTP響應。

例子:

package main

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestHandleGet(t *testing.T) {
	mux := http.NewServeMux()   //建立一個多路複用器,用於執行測試
	mux.HandleFunc("/post/", handleRequest)  //繫結需要測試的處理器

	writer := httptest.NewRecorder()   //建立記錄器,用於獲取HTTP響應
	request, _ := http.NewRequest("GET", "/post/1", nil)   //為被測試的處理器建立相應的請求
	mux.ServeHTTP(writer, request)  //傳送測試請求

    //對請求返回的響應結果進行檢查處理
	if writer.Code != 200 { 
		t.Errorf("Response code is %v", writer.Code)
	}
	var post Post
	json.Unmarshal(writer.Body.Bytes(), &post)
	if post.Id != 1 {
		t.Errorf("Cannot retrieve JSON post")
	}
}

//與上面的測試方法同理
func TestHandlePut(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/post/", handleRequest)

	writer := httptest.NewRecorder()
	json := strings.NewReader(`{"content":"Updated post","author":"Sau Sheong"}`)
	request, _ := http.NewRequest("PUT", "/post/1", json)
	mux.ServeHTTP(writer, request)

	if writer.Code != 200 {
		t.Errorf("Response code is %v", writer.Code)
	}
}

以上例子中,每個測試用例都會獨立執行並啟動各自獨立的用於測試的Web伺服器。

程式需要建立一個多路複用器並將handleRequest處理器與其進行繫結。除此之外,為了獲取伺服器返回的HTTP響應,程式使用httptest.New Recorder函式建立了一個 ResponseRecorder結構,這個結構可以把響應儲存起來以便進行後續的檢查。

Go 的testing包允許使用者通過TestMain 函式,在進行測試時執行相應的預設( setup )操作或者拆卸( teardown)操作。一個典型的TestMain 函式看上去是下面這個樣子的:

func TestMain (m *testing.M){
	setUp ()
	code := m. Run ( )tearDown ()
	os.Exit (code)
}

setUp函式和tearDown函式分別定義了測試在預設階段以及拆卸階段需要執行的工作。需要注意的是,setUp函式和tearDown函式是為所有測試用例設定的,它們在整個測試過程中只會被執行一次,其中setup 函式會在所有測試用例被執行之前執行,而tearDown函式則會在所有測試用例都被執行完畢之後執行。至於測試程式中的各個測試用例,則由testing.M結構的Run方法負責呼叫,該方法在執行之後將返回一個退出碼( exit code),使用者可以把這個退出碼傳遞給os.Exit函式。

測試替身、依賴注入

測試替身( test double)是一種能夠讓單元測試用例變得更為獨立的常用方法。當測試不方便使用實際的物件、結構或者函式時,我們就可以使用測試替身來模擬它們。因為測試替身能夠提高被測試程式碼的獨立性,所以自動單元測試環境經常會使用這種技術。

實現測試替身的一種設計方法是使用依賴注入(dependency injection)設計模式。這種模式迪過向被呼叫的物件、結構或者函式傳人依賴關係,然後由依賴關係代替被呼叫者執行實際的操作,以此來解耦軟體中的兩個或多個層( layer ),而在Go語言當中,(被傳入的依賴關係通常會是一種介面型別。接下來,就讓我們來看看,如何在第7章介紹的簡單Web服務中使用依賴注入設計模式。

具體看圖內的解耦過程:

原流程:

自頂向下進行解耦依賴關係: