简体   繁体   English

在 Golang 中测试包装函数的惯用方法是什么?

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

It is not unusual, when we have a function with a lot of parameters, to create some companion-functions that use the general function with pre-defined parameters.这并不罕见,当我们有一个带有很多参数的 function 时,创建一些使用带有预定义参数的通用 function 的伴随函数。 This is the usual alternative to defining default value for parameters, something Go doesn't allow.这是定义参数默认值的通常替代方法,Go 不允许这样做。

A very simple example would be to create a SquareArea function that uses a more general RectangleArea function.一个非常简单的示例是创建一个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
}

For theses functions one can create the (kinda) table tests对于这些功能,可以创建(有点)表测试

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)
        }
    }
}

Is this the right way?这是正确的方法吗?

For more complex functions, and more companion-functions , it seems that this approach would create a lot of tests while essentially testing several times the same underlying code (ie RectangleArea ).对于更复杂的功能和更多的配套功能,这种方法似乎会创建大量测试,同时实质上会多次测试相同的底层代码(即RectangleArea )。

I feel that testing SquareArea should only ensure that RectangleArea is called with the correct parameters.我觉得测试SquareArea应该只确保使用正确的参数调用RectangleArea

Is this code approach (having there both functions RectangleArea and SquareArea ) idiomatic in Golang?这种代码方法(同时具有RectangleAreaSquareArea功能)在 Golang 中是惯用的吗? Is there another way to do it on Golang?在 Golang 上还有其他方法吗?

With a language like Python, I would mock the RectangleArea function.使用像 Python 这样的语言,我会模拟RectangleArea function。

To mimic this, I would do something like:为了模仿这一点,我会做类似的事情:

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
}

with the tests:通过测试:

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)
        }
    }
}

But it seems that using a variable to hold the actual function in order to do the tests is bad.但是似乎使用变量来保存实际的 function 来进行测试是不好的。

Another example of this kind of situation would be when treating the http request method through a first triage 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
}

Full fledged tests for the 3 functions would have a lot of useless repetitions Limiting the test of the Triage function to:对这 3 个功能的完整测试将有很多无用的重复将Triage function 的测试限制为:

  • check that the common code is correctly executed检查公共代码是否正确执行
  • check that the correct function ( triagePost or triageGet ) is correctly called based on the r.Method检查是否根据r.Method正确调用了正确的 function( triagePosttriageGet

would prevent such repetitions while testing the actual behavior.将在测试实际行为时防止此类重复。

Or having this type of code (triage function) is totally incorrect in Golang?或者在 Golang 中拥有这种类型的代码(分类功能)是完全不正确的?

Go refrains from mocking as much as possible. Go尽量避免mocking。 Let's try to follow the idiomatic approach (test the real code) and see how far it brings us.让我们尝试遵循惯用的方法(测试真实代码),看看它能带给我们多远。

Function SquareArea calls RectangleArea with the same value for the two parameters. Function SquareArea 使用相同的两个参数值调用 RectangleArea。 Testing SquareArea is not concerned about the correctness of RectangleArea.测试 SquareArea 并不关心 RectangleArea 的正确性。

A single test case for SquareArea will give 100% test coverage of it, there is not even the need for a table driven test. SquareArea 的单个测试用例将提供 100% 的测试覆盖率,甚至不需要表驱动测试。

If there is concern to test the correctness of RectangleArea when given the two parameters with the same value, then this test case should be added directly to the table-driven test for RectangleArea.如果担心在给定两个相同值的参数时测试 RectangleArea 的正确性,则应将此测试用例直接添加到 RectangleArea 的表驱动测试中。

For sure, if SquareArea were more complicated, then we would need a table driven test.当然,如果 SquareArea 更复杂,那么我们将需要一个表驱动测试。 But also in this case, we would have to focus on what SquareArea does, not on what RectangleArea does.但在这种情况下,我们也必须关注 SquareArea 的作用,而不是RectangleArea 的作用。

Said in another way, when deciding how to test a function (or anything actually), I have found that the following approach helps reducing doubts:换句话说,在决定如何测试 function(或任何实际的东西)时,我发现以下方法有助于减少疑虑:

  1. Keep repeating myself: what am I actually testing???不断重复自己:我到底在测试什么???
  2. Use code coverage to guide me.使用代码覆盖来指导我。 Code coverage (and fixating on a percentage) can be a double-edged sword, but used wisely as guidance can be very useful.代码覆盖率(并固定在百分比上)可能是一把双刃剑,但明智地用作指导会非常有用。
  3. Challenge the existence of a test.挑战测试的存在。 The problem with tests is that their mere existence tends to justify the existence of the tested code.测试的问题在于它们的存在往往证明被测试代码的存在是合理的。 If the tested code is not used anywhere else, delete it and its tests.如果测试的代码没有在其他地方使用,删除它和它的测试。 This is, in my experience, the most valuable lesson of TDD (when applicable).根据我的经验,这是 TDD 最有价值的一课(如果适用)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在 Go 中使用 HTTP 请求测试功能的最惯用方法是什么? - What's the most idiomatic way of testing functions with HTTP requests in Go? 模仿Perl的Test :: More :: done_testing最惯用的方法是什么? - What is the most idiomatic way to emulating Perl's Test::More::done_testing? Go中测试代码的最惯用方法是什么,它依赖于大量方法的结构? - What is the most idiomatic way in Go to test code which has dependency on structure with big amount of methods? 测试私有功能的惯用方式是什么? - What is the idiomatic way to have a private function tested? golang中如何测试主要的包函数? - How to test the main package functions in golang? 对处理 csv 文件的函数进行单元测试的最佳方法是什么? - What's the best way to unit test functions that handle csv files? 测试在新实例上调用方法的函数的“正确”方法是什么? - What's the “right” way to test functions that call methods on new instances? 包装器是测试静态依赖项的唯一方法吗? - Is a wrapper the only way to test a static dependency? jest 是否运行用于测试“toThrowError”的包装函数? - Does jest run the wrapper functions used to test with "toThrowError"? 有没有一种方法可以编写SKPhysicsContactDelegate函数的测试? - Is there a way to write a test of a `SKPhysicsContactDelegate` functions?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM