簡體   English   中英

在 Golang 中測試包裝函數的慣用方法是什么?

[英]What is the idiomatic way to test wrapper functions in Golang?

這並不罕見,當我們有一個帶有很多參數的 function 時,創建一些使用帶有預定義參數的通用 function 的伴隨函數。 這是定義參數默認值的通常替代方法,Go 不允許這樣做。

一個非常簡單的示例是創建一個SquareArea function,它使用更通用的RectangleArea function。

func SquareArea(side int) int {
    return RectangleArea(side, side)
}

func RectangleArea(width int, length int) int {
    if width < 0 || length < 0 {
        return 0
    }
    return width * length
}

對於這些功能,可以創建(有點)表測試

func TestSquareArea(t *testing.T) {
    tests := []struct {
        side     int
        expected int
    }{
        {side: -1, expected: 0},
        {side: 0, expected: 0},
        {side: 1, expected: 1},
        {side: 2, expected: 4},
        {side: 100, expected: 10000},
    }
    for idx, test := range tests {
        if result := SquareArea(test.side); test.expected != result {
            t.Errorf("test #%d expected != result (%d!=%d)", idx, test.expected, result)
        }
    }
}

func TestRectangleArea(t *testing.T) {
    tests := []struct {
        width    int
        length   int
        expected int
    }{
        {width: -1, length: -1, expected: 0},
        {width: -1, length: 1, expected: 0},
        {width: 1, length: -1, expected: 0},
        {width: 2, length: 1, expected: 2},
        {width: 1, length: 2, expected: 2},
        {width: 3, length: 2, expected: 6},
        {width: 2, length: 3, expected: 6},
        {width: 10, length: 100, expected: 1000},
        {width: 100, length: 10, expected: 1000},
    }
    for idx, test := range tests {
        if result := RectangleArea(test.width, test.length); test.expected != result {
            t.Errorf("test #%d expected != result (%d!=%d)", idx, test.expected, result)
        }
    }
}

這是正確的方法嗎?

對於更復雜的功能和更多的配套功能,這種方法似乎會創建大量測試,同時實質上會多次測試相同的底層代碼(即RectangleArea )。

我覺得測試SquareArea應該只確保使用正確的參數調用RectangleArea

這種代碼方法(同時具有RectangleAreaSquareArea功能)在 Golang 中是慣用的嗎? 在 Golang 上還有其他方法嗎?

使用像 Python 這樣的語言,我會模擬RectangleArea function。

為了模仿這一點,我會做類似的事情:

var rectangleAreaFunction = RectangleArea

func SquareArea(side int) int {
    return rectangleAreaFunction(side, side)
}

func RectangleArea(width int, length int) int {
    if width < 0 || length < 0 {
        return 0
    }
    return width * length
}

通過測試:

func TestSquareArea(t *testing.T) {
    call:= ""
    mock:= func(width int, length int) int {
        call = fmt.Sprintf("func(%d,%d)", width, length)
        return 0
    }
    rectangleAreaFunction = mock
    tests := []struct {
        side     int
        expected string
    }{
        {side: -1, expected: "func(-1,-1)"},
        {side: 0, expected: "func(0,0)"},
        {side: 1, expected: "func(1,1)"},
        {side: 2, expected: "func(2,2)"},
        {side: 100, expected: "func(100,100)"},
    }
    for idx, test := range tests {
        call = ""
        SquareArea(test.side)
        if test.expected != call {
            t.Errorf("test #%d expected != call (%s!=%s)", idx, test.expected, call)
        }
    }
}


func TestRectangleArea(t *testing.T) {
    tests := []struct {
        width    int
        length   int
        expected int
    }{
        {width: -1, length: -1, expected: 0},
        {width: -1, length: 1, expected: 0},
        {width: 1, length: -1, expected: 0},
        {width: 2, length: 1, expected: 2},
        {width: 1, length: 2, expected: 2},
        {width: 3, length: 2, expected: 6},
        {width: 2, length: 3, expected: 6},
        {width: 10, length: 100, expected: 1000},
        {width: 100, length: 10, expected: 1000},
    }
    for idx, test := range tests {
        if result := RectangleArea(test.width, test.length); test.expected != result {
            t.Errorf("test #%d expected != result (%d!=%d)", idx, test.expected, result)
        }
    }
}

但是似乎使用變量來保存實際的 function 來進行測試是不好的。

這種情況的另一個例子是通過第一次分類 function 處理 http 請求方法時:

func Triage(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    // some other common code like adding some context
    *r = *(r.WithContext(context.WithValue(r.Context(), "key", "value")))
    switch r.Method {
    case POST:
        triagePost(w, r)
    default:
        triageGet(w, r)
    }
}

func triagePost(w http.ResponseWriter, r *http.Request) {
    // some code
}

func triageGet(w http.ResponseWriter, r *http.Request) {
    // some code
}

對這 3 個功能的完整測試將有很多無用的重復將Triage function 的測試限制為:

  • 檢查公共代碼是否正確執行
  • 檢查是否根據r.Method正確調用了正確的 function( triagePosttriageGet

將在測試實際行為時防止此類重復。

或者在 Golang 中擁有這種類型的代碼(分類功能)是完全不正確的?

Go盡量避免mocking。 讓我們嘗試遵循慣用的方法(測試真實代碼),看看它能帶給我們多遠。

Function SquareArea 使用相同的兩個參數值調用 RectangleArea。 測試 SquareArea 並不關心 RectangleArea 的正確性。

SquareArea 的單個測試用例將提供 100% 的測試覆蓋率,甚至不需要表驅動測試。

如果擔心在給定兩個相同值的參數時測試 RectangleArea 的正確性,則應將此測試用例直接添加到 RectangleArea 的表驅動測試中。

當然,如果 SquareArea 更復雜,那么我們將需要一個表驅動測試。 但在這種情況下,我們也必須關注 SquareArea 的作用,而不是RectangleArea 的作用。

換句話說,在決定如何測試 function(或任何實際的東西)時,我發現以下方法有助於減少疑慮:

  1. 不斷重復自己:我到底在測試什么???
  2. 使用代碼覆蓋來指導我。 代碼覆蓋率(並固定在百分比上)可能是一把雙刃劍,但明智地用作指導會非常有用。
  3. 挑戰測試的存在。 測試的問題在於它們的存在往往證明被測試代碼的存在是合理的。 如果測試的代碼沒有在其他地方使用,刪除它和它的測試。 根據我的經驗,這是 TDD 最有價值的一課(如果適用)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM