Golang之應用測試

Go 應用測試

測試的覆蓋率

命令:

go test ./ -v -cover

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

image-20201215213156322

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

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

果然,執行測試的時候添加參數-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結構,這個結構可以把響應存儲起來以便進行後續的檢查。

image-20201216170322579

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服務中使用依賴注入設計模式。

具體看圖內的解耦過程:

原流程:

image-20201216171429956

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

image-20201216171447925

Tags: