溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言之單元測試

發(fā)布時間:2020-07-08 22:40:17 來源:網絡 閱讀:463 作者:baby神 欄目:編程語言
什么是單元測試


相信我們做程序員的,對單元測試都不陌生。單元測試一般是用來測試我們的代碼邏輯有沒有問題,有沒有按照我們期望的運行,以保證代碼質量。


大多數(shù)的單元測試,都是對某一個函數(shù)方法進行測試,以盡可能的保證沒有問題或者問題可被我們預知。為了達到這個目的,我們可以使用各種手段、邏輯,模擬不同的場景進行測試。


這里我們在package main里定義一個函數(shù)Add,求兩個數(shù)之和的函數(shù),然后我們使用單元測試進行求和邏輯測試。


main.go

func Add(a,b int) int{
    return a+b
}


main_test.go

func TestAdd(t *testing.T) {
    sum := Add(1,2)
    if sum == 3 {
        t.Log("the result is ok")
    } else {
        t.Fatal("the result is wrong")
    }
}


然后我們在終端的項目目錄下運行go test -v就可以看到測試結果了。


?  hello go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)        main_test.go:26: the result is ok PASS ok      flysnow.org/hello       0.007s


有測試成功PASS標記,并且打印出我們想要的結果。


  • Go語言為我們提供了測試框架,以便幫助我們更容易的進行單元測試,但是要使用這個框架,需要遵循如下幾點規(guī)則:


  • 含有單元測試代碼的go文件必須以_test.go結尾,Go語言測試工具只認符合這個規(guī)則的文件。


  • 單元測試文件名_test.go前面的部分最好是被測試的方法所在go文件的文件名,比如例子中是main_test.go,因為測試的Add函數(shù),在main.go文件里。


  • 單元測試的函數(shù)名必須以Test開頭,是可導出公開的函數(shù)。


  • 測試函數(shù)的簽名必須接收一個指向testing.T類型的指針,并且不能返回任何值。


  • 函數(shù)名最好是Test+要測試的方法函數(shù)名,比如例子中是TestAdd,表示測試的是Add這個這個函數(shù)。


遵循以上規(guī)則,我們就可以很容易的編寫單元測試了,單元測試的重點在于測試代碼的邏輯,場景等,以便盡可能的測試全面,保障代碼質量邏輯。


表組測試


還有一種單元測試方法叫表組測試,這個和基本的單元測試非常相似,只不過它是有好幾個不同的輸入以及輸出組成的一組單元測試。


比如上個例子中,我們測試了1+2,如果我們再加上3+4,9+2等,這就有了好幾個輸入,同時對應的也有好幾個輸出,這種一次性測試很多個輸入輸出場景的測試,就是表組測試。


func TestAdd(t *testing.T) {
    sum := Add(1,2)
    if sum == 3 {
        t.Log("the result is ok")
    } else {
        t.Fatal("the result is wrong")
    }

    sum=Add(3,4)    
    if sum == 7 {
        t.Log("the result is ok")
    } else {
        t.Fatal("the result is wrong")
    }
}


模擬調用


單元測試的原則,就是你所測試的函數(shù)方法,不要受到所依賴環(huán)境的影響,比如網絡訪問等,因為有時候我們運行單元測試的時候,并沒有聯(lián)網,那么總不能讓單元測試因為這個失敗吧?所以這時候模擬網絡訪問就有必要了。


針對模擬網絡訪問,標準庫了提供了一個httptest包,可以讓我們模擬http的網絡調用,下面舉個例子了解使用。


首先我們創(chuàng)建一個處理HTTP請求的函數(shù),并注冊路由。


package commonimport (
    "net/http"
    "encoding/json")func Routes(){
    http.HandleFunc("/sendjson",SendJSON)}func SendJSON(rw http.ResponseWriter,r *http.Request){
    u := struct {
        Name string
    }{
        Name:"張三",
    }

    rw.Header().Set("Content-Type","application/json")
    rw.WriteHeader(http.StatusOK)
    json.NewEncoder(rw).Encode(u)
}


非常簡單,這里是一個/sendjsonAPI,當我們訪問這個API時,會返回一個JSON字符串?,F(xiàn)在我們對這個API服務進行測試,但是我們又不能時時刻刻都啟動著服務,所以這里就用到了外部終端對API的網絡訪問請求。


func init()  {
    common.Routes()}func TestSendJSON(t *testing.T){
    req,err:=http.NewRequest(http.MethodGet,"/sendjson",nil)
    if err!=nil {
        t.Fatal("創(chuàng)建Request失敗")
    }

    rw:=httptest.NewRecorder()
    http.DefaultServeMux.ServeHTTP(rw,req)

    log.Println("code:",rw.Code)

    log.Println("body:",rw.Body.String())
}


運行這個單元測試,就可以看到我們訪問/sendjsonAPI的結果里,并且我們沒有啟動任何HTTP服務就達到了目的。這個主要利用httptest.NewRecorder()創(chuàng)建一個http.ResponseWriter,模擬了真實服務端的響應,這種響應時通過調用http.DefaultServeMux.ServeHTTP方法觸發(fā)的。


還有一個模擬調用的方式,是真的在測試機上模擬一個服務器,然后進行調用測試。


func mockServer() *httptest.Server {
    //API調用處理函數(shù)
    sendJson := func(rw http.ResponseWriter, r *http.Request) {
        u := struct {
            Name string
        }{
            Name: "張三",
        }

        rw.Header().Set("Content-Type", "application/json")
        rw.WriteHeader(http.StatusOK)
        json.NewEncoder(rw).Encode(u)
    }    
    //適配器轉換
    return httptest.NewServer(http.HandlerFunc(sendJson))
}
func TestSendJSON(t *testing.T) {    //創(chuàng)建一個模擬的服務器    server := mockServer()        defer server.Close()        //Get請求發(fā)往模擬服務器的地址    resq, err := http.Get(server.URL)        if err != nil {        t.Fatal("創(chuàng)建Get失敗")    }    defer resq.Body.Close()    log.Println("code:", resq.StatusCode)    json, err := ioutil.ReadAll(resq.Body)        if err != nil {        log.Fatal(err)    }    log.Printf("body:%s\n", json)
}


模擬服務器的創(chuàng)建使用的是httptest.NewServer函數(shù),它接收一個http.Handler處理API請求的接口。


代碼示例中使用了Hander的適配器模式,http.HandlerFunc是一個函數(shù)類型,實現(xiàn)了http.Handler接口,這里是強制類型轉換,不是函數(shù)的調用。


這個創(chuàng)建的模擬服務器,監(jiān)聽的是本機IP127.0.0.1,端口是隨機的。接著我們發(fā)送Get請求的時候,不再發(fā)往/sendjson,而是模擬服務器的地址server.URL,剩下的就和訪問正常的URL一樣了,打印出結果即可。


測試覆蓋率


我們盡可能的模擬更多的場景來測試我們代碼的不同情況,但是有時候的確也有忘記測試的代碼,這時候我們就需要測試覆蓋率作為參考了。


由單元測試的代碼,觸發(fā)運行到的被測試代碼的代碼行數(shù)占所有代碼行數(shù)的比例,被稱為測試覆蓋率,代碼覆蓋率不一定完全精準,但是可以作為參考,可以幫我們測量和我們預計的覆蓋率之間的差距,go test工具,就為我們提供了這么一個度量測試覆蓋率的能力。


main.go

func Tag(tag int){
    switch tag {    
    case 1:
        fmt.Println("Android")    
    case 2:
        fmt.Println("Go")    
    case 3:
        fmt.Println("Java")    
    default:
        fmt.Println("C")

    }
}


main_test.go

func TestTag(t *testing.T) {
    Tag(1)
    Tag(2)
}


現(xiàn)在我們使用go test工具運行單元測試,和前幾次不一樣的是,我們要顯示測試覆蓋率,所以要多加一個參數(shù)-coverprofile,所以完整的命令為:go test -v -coverprofile=c.out,-coverprofile是指定生成的覆蓋率文件,例子中是c.out,這個文件一會我們會用到?,F(xiàn)在我們看終端輸出,已經有了一個覆蓋率。


=== RUN   TestTag
Android
Go
--- PASS: TestTag (0.00s)
PASS coverage: 60.0% of statements ok      flysnow.org/hello       0.005s


coverage: 60.0% of statements,60% 的測試覆蓋率,還沒有到 100% ,那么我們看看還有那些代碼沒有被測試到。這就需要我們剛剛生成的測試覆蓋率文件c.out生成測試覆蓋率報告了。生成報告有go為我們提供的工具,使用go tool cover -html=c.out -o=tag.html,即可生成一個名字為tag.html的HTML格式的測試覆蓋率報告,這里有詳細的信息告訴我們哪一行代碼測試到了,哪一行代碼沒有測試到。



從上圖中可以看到,標記為綠色的代碼行已經被測試了;標記為紅色的還沒有測試到,有兩行的,現(xiàn)在我們根據(jù)沒有測試到的代碼邏輯,完善我的單元測試代碼即可。


func TestTag(t *testing.T) {
    Tag(1)
    Tag(2)
    Tag(3)
    Tag(6)
}


單元測試完善為如上代碼,再運行單元測試,就可以看到測試覆蓋率已經是 100% 了,大功告成。


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI