亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

模擬/測試基本請求

模擬/測試基本請求

Go
BIG陽 2022-09-19 17:28:42
我傾向于編寫單元測試,并且我想知道對基本請求進行單元測試的正確方法。http.get我在網上找到了一個返回假數據的API,并編寫了一個基本程序來獲取一些用戶數據并打印出一個ID:package mainimport (    "encoding/json"    "fmt"    "io/ioutil"    "log"    "net/http")type UserData struct {    Meta interface{} `json:"meta"`    Data struct {        ID     int    `json:"id"`        Name   string `json:"name"`        Email  string `json:"email"`        Gender string `json:"gender"`        Status string `json:"status"`    } `json:"data"`}func main() {    resp := sendRequest()    body := readBody(resp)    id := unmarshallData(body)    fmt.Println(id)}func  sendRequest() *http.Response {    resp, err := http.Get("https://gorest.co.in/public/v1/users/1841")    if err != nil {        log.Fatalln(err)    }    return resp}func readBody(resp *http.Response) []byte {    body, err := ioutil.ReadAll(resp.Body)    if err != nil {        log.Fatalln(err)    }    return body}func unmarshallData(body []byte) int {    var userData UserData    json.Unmarshal(body, &userData)    return userData.Data.ID}這適用于1841年并打印出來。然后,我想編寫一些測試來驗證代碼的行為是否符合預期,例如,如果返回錯誤,代碼將正確失敗,返回的數據是否可以取消編組。我一直在閱讀和查看示例,但它們都比我覺得我想要實現的目標要復雜得多。我從以下測試開始,以確保傳遞給函數的數據可以取消編組:unmarshallDatapackage mainimport (    "testing")func Test_unmarshallData(t *testing.T) {    type args struct {        body []byte    }    tests := []struct {        name string        args args        want int    }{        {name: "Unmarshall", args: struct{ body []byte }{body: []byte("{\"meta\":null,\"data\":{\"id\":1841,\"name\":\"Piya\",\"email\":\"[email protected]\",\"gender\":\"female\",\"status\":\"active\"}}")}, want: 1841},    }        for _, tt := range tests {        t.Run(tt.name, func(t *testing.T) {            if got := unmarshallData(tt.args.body); got != tt.want {                t.Errorf("unmarshallData() = %v, want %v", got, tt.want)            }        })    }}任何關于從這里去哪里的建議將不勝感激。
查看完整描述

1 回答

?
躍然一笑

TA貢獻1826條經驗 獲得超6個贊

在繼續測試之前,你的代碼有一個嚴重的流程,如果你在未來的編程任務中不關心它,這將成為一個問題。


https://pkg.go.dev/net/http請參閱第二個示例


客戶端必須在完成響應正文后將其關閉


現在讓我們解決這個問題(我們稍后將不得不回到這個主題),兩種可能性。


1/ 在 中,使用延遲在耗盡該資源后將其關閉;main


func main() {

    resp := sendRequest()

    defer body.Close()

    body := readBody(resp)

    id := unmarshallData(body)

    fmt.Println(id)

}

2/ 在readBody


func readBody(resp *http.Response) []byte {

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {

        log.Fatalln(err)

    }

    return body

}

使用延遲是關閉資源的預期方式。它有助于讀者識別資源的生存期并提高可讀性。

注意:我不會使用太多的表測試驅動模式,但你應該,就像你在你的OP中所做的那樣。

轉到測試部分。

測試可以寫在同一個包或其同一版本下,并帶有尾隨,例如 。這在兩個方面具有影響。_test[package target]_test

  • 使用單獨的包,它們將在最終版本中被忽略。這將有助于生成更小的二進制文件。

  • 使用單獨的包,以黑盒方式測試API,只能訪問它顯式公開的標識符。

您當前的測試是白盒的,這意味著您可以訪問 的任何聲明,無論是否公開。main

關于 ,圍繞這一點編寫測試并不是很有趣,因為它做得太少,并且您的測試不應該編寫來測試std庫。sendRequest

但是,為了演示,并且出于充分的理由,我們可能不希望依賴外部資源來執行我們的測試。

為了實現這一點,我們必須使其中消耗的全局依賴項成為注入的依賴項。因此,以后可以替換它所依賴的一個反應物,即http。獲取方法。

func  sendRequest(client interface{Get() (*http.Response, error)}) *http.Response {

    resp, err := client.Get("https://gorest.co.in/public/v1/users/1841")

    if err != nil {

        log.Fatalln(err)

    }

    return resp

}

在這里,我使用內聯接口聲明。interface{Get() (*http.Response, error)}


現在,我們可以添加一個新的測試,該測試注入一段代碼,該代碼段將準確返回將觸發我們要在代碼中測試的行為的值。



type fakeGetter struct {

    resp *http.Response

    err  error

}


func (f fakeGetter) Get(u string) (*http.Response, error) {

    return f.resp, f.err

}

func TestSendRequestReturnsNilResponseOnError(t *testing.T) {


    c := fakeGetter{

        err: fmt.Errorf("whatever error will do"),

    }


    resp := sendRequest(c)

    if resp != nil {

        t.Fatal("it should return a nil response when an error arises")

    }

}

現在運行此測試并查看結果。它不是決定性的,因為您的函數包含對 log 的調用。致命,它依次執行操作系統。退出;我們無法對此進行檢驗。


如果我們試圖改變這一點,我們可能會認為我們可能會呼吁恐慌,因為我們可以恢復。


我不建議這樣做,在我看來,這是臭的和壞的,但它存在,所以我們可能會考慮。這也是對函數簽名的最小可能更改。返回錯誤會破壞更多的當前簽名。我想在那個演示中盡量減少這一點。但是,根據經驗,請返回錯誤并始終檢查它們。


在函數中,將此調用替換為 并更新測試以捕獲死機。sendRequestlog.Fatalln(err)panic(err)


func TestSendRequestReturnsNilResponseOnError(t *testing.T) {

    var hasPanicked bool

    defer func() {

        _ = recover() // if you capture the output value or recover, you get the error gave to the panic call. We have no use of it.

        hasPanicked = true

    }()


    c := fakeGetter{

        err: fmt.Errorf("whatever error will do"),

    }


    resp := sendRequest(c)

    if resp != nil {

        t.Fatal("it should return a nil response when an error arises")

    }

    if !hasPanicked {

        t.Fatal("it should have panicked")

    }

}

現在,我們可以轉到另一個執行路徑,即非錯誤返回。

為此,我們鍛造了所需的*http。我們想要傳遞到函數中的響應實例,然后我們將檢查其屬性,以確定函數執行的操作是否與我們預期的內聯。

我們將考慮要確保返回時未修改: /

下面的測試只設置兩個屬性,我將使用它來演示如何使用 NopCloser 和字符串設置 Body。新閱讀器,因為以后使用Go語言時經常需要它;

我也使用反射。DeepEqual作為蠻力相等檢查器,通常你可以更細粒度,得到更好的測試。 在這種情況下,它做了這項工作,但它引入了復雜性,不能證明系統地使用它是合理的。DeepEqual

func TestSendRequestReturnsUnmodifiedResponse(t *testing.T) {


    c := fakeGetter{

        err: nil,

        resp: &http.Response{

            Status: http.StatusOK,

            Body:   ioutil.NopCloser(strings.NewReader("some text")),

        },

    }


    resp := sendRequest(c)

    if !reflect.DeepEqual(resp, c.resp) {

        t.Fatal("the response should not have been modified")

    }

}

在這一點上,你可能已經認為這個小功能不好,如果你不這樣做,我向你保證它不是。它做的太少,它只是包裝了方法,它的測試對業務邏輯的生存沒有多大興趣。sendRequesthttp.Get

繼續運作。readBody

所有申請的評論也適用于此。sendRequest

  • 它做得太少

  • 它是os.Exit

有一件事不適用。由于對 的調用不依賴于外部資源,因此嘗試注入該依賴項毫無意義。我們可以四處測試。ioutil.ReadAll

但是,為了演示,現在是時候討論缺少的呼叫了。defer resp.Body.Close()

讓我們假設我們去介紹中提出的第二個命題并對此進行測試。

該結構充分地將其接收方公開為接口。http.ResponseBody

為了確保代碼調用'關閉,我們可以為它編寫一個存根。該存根將記錄是否進行了該調用,然后測試可以檢查該調用,如果不是,則觸發錯誤。

type closeCallRecorder struct {

    hasClosed bool

}


func (c *closeCallRecorder) Close() error {

    c.hasClosed = true

    return nil

}

func (c *closeCallRecorder) Read(p []byte) (int, error) {

    return 0, nil

}


func TestReadBodyCallsClose(t *testing.T) {


    body := &closeCallRecorder{}

    res := &http.Response{

        Body: body,

    }


    _ = readBody(res)

    if !body.hasClosed {

        t.Fatal("the response body was not closed")

    }

}

同樣,為了便于演示,我們可能希望測試該函數是否調用了 。Read


type readCallRecorder struct {

    hasRead bool

}


func (c *readCallRecorder) Read(p []byte) (int, error) {

    c.hasRead = true

    return 0, nil

}


func TestReadBodyHasReadAnything(t *testing.T) {


    body := &readCallRecorder{}

    res := &http.Response{

        Body: ioutil.NopCloser(body),

    }


    _ = readBody(res)

    if !body.hasRead {

        t.Fatal("the response body was not read")

    }

}

我們還驗證了身體之間沒有修改,


func TestReadBodyDidNotModifyTheResponse(t *testing.T) {

    want := "this"

    res := &http.Response{

        Body: ioutil.NopCloser(strings.NewReader(want)),

    }


    resp := readBody(res)

    if got := string(resp); want != got {

        t.Fatal("invalid response, wanted=%q got %q", want, got)

    }

}

我們差不多完成了,讓我們把一個移到函數上。unmarshallData


你已經寫了一個關于它的測試。不過,這是可以的,我會這樣寫,以使其更精簡:


type UserData struct {

    Meta interface{} `json:"meta"`

    Data Data        `json:"data"`

}

type Data struct {

    ID     int    `json:"id"`

    Name   string `json:"name"`

    Email  string `json:"email"`

    Gender string `json:"gender"`

    Status string `json:"status"`

}


func Test_unmarshallData(t *testing.T) {

    type args struct {

        body []byte

    }

    tests := []UserData{

        UserData{Data: Data{ID: 1841}},

    }

    for _, u := range tests {

        want := u.ID

        b, _ := json.Marshal(u)

        t.Run("Unmarshal", func(t *testing.T) {

            if got := unmarshallData(b); got != want {

                t.Errorf("unmarshallData() = %v, want %v", got, want)

            }

        })

    }

}

然后,通常適用:

  • 不記錄。致命

  • 你在測試什么?編組者 ?

最后,現在我們已經收集了所有這些部分,我們可以重構以編寫更合理的函數,并重新使用所有這些部分來幫助我們測試此類代碼。

我不會這樣做,但這是一個啟動器,它仍然令人恐慌,我仍然不推薦,但是前面的演示已經顯示了測試返回錯誤的版本所需的一切。

type userFetcher struct {

    Requester interface {

        Get(u string) (*http.Response, error)

    }

}


func (u userFetcher) Fetch() int {

    resp, err := u.Requester.Get("https://gorest.co.in/public/v1/users/1841") // it does not really matter that this string is static, using the requester we can mock the response, its body and the error.

    if err != nil {

        panic(err)

    }

    defer resp.Body.Close() //always.

    body, err := ioutil.ReadAll(resp.Body)

    if err != nil {

        panic(err)

    }

    var userData UserData

    err = json.Unmarshal(body, &userData)

    if err != nil {

        panic(err)

    }

    return userData.Data.ID

}


查看完整回答
反對 回復 2022-09-19
  • 1 回答
  • 0 關注
  • 99 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號