在Go语言中测试不返回结果的方法。

huangapple go评论89阅读模式
英文:

testing non returning method in go

问题

我有一个简单的方法,它只是检查一个参数是否为空,然后根据结构体字段调用两个方法中的一个。我该如何测试它?

在JavaScript中,我会在doFunctionOne()上创建一个spy并模拟对象。在Go中,模拟也很好用,但是如何实现“spying”部分呢?或者还有其他惯用的方法来测试这种类型的方法吗?

英文:

I have a simple method, which just checks if a parameter is empty and then calls one of 2 methods based on a struct field.
How would I test it?

func (cT *customType) someMethod(message string) {
    if message == ""{
	    return
    }

    if cT.value == nil {
	    cT.doFunctionOne()
    } else {
	    cT.doFunctionTwo()
    }
}

In Javascript, I would create a spy on doFunctionOne() and mock up the object.
Mocking up works well in Go as well, but how would I do the 'spying' part?
Or is there another idiomatic way to test this kind of method?

答案1

得分: 3

首先,你不应该将一个方法命名为"doFunctionOne",而应该命名为"methodOne" 在Go语言中测试不返回结果的方法。

如果"doFunctionOne"和"doFunctionTwo"都没有任何可观察的效果,那么测试它们就没有任何意义。所以我们可以假设它们确实有可观察的副作用,无论是对环境还是对它们所调用的customType。

现在只需测试这些副作用即可。如果这两个方法都有返回值,那么这很简单。如果它们启动了一个无限循环,那就变得更难了,但仍然可行。

在我看来,没有必要以"测试它是否根据值调用One或Two"的方式来"测试"这个方法:对我来说,这太低级了,增加了无意义的覆盖率计数。如果你调用某个方法,它必须做一些事情(可观察的效果),你应该检查这个效果,而不是内部工作原理。

英文:

First: You wouldn't name a method "doFunctionOne" but "methodOne" 在Go语言中测试不返回结果的方法。

If neither doFunctionOne nor doFunctionTwo has any observable effect, then there is absolutely no point in testing it. So we may assume that they do have observable side effect, either on the environment or on the customType they have been invoked on.

Now just test these side effects. This is trivial if both methods do return. If they spin up an endless loop it becomes harder, but still doable.

IMHO there is no need to "test" this method in the sense of "test whether it calls One or Two depending on value": For me this is to lowlevel, too much increasing coverage count for nothing. If you call some method it has to do something (observable effect) and you should check this effect, not the inner workings.

答案2

得分: 3

在Go语言中,模拟对象的惯用方式是显式地进行。一个健康的接口应该可以自我测试。所以,如果我们有如下代码:

type customType struct {
    value int
}

func (c customType) doFunctionOne() {
    fmt.Println("Method #1")
}

func (c customType) doFunctionTwo() {
    fmt.Println("Method #2")
}

func (c customType) someMethod() {
    if c.value <= 0 {
        c.doFunctionOne()
    } else {
        c.doFunctionTwo()
    }
}

我们需要提供一种显式地改变doFunctionOnedoFunctionTwo实现的方式。我们可以使用接口来泛化someMethod的行为:

type customType struct {
    myValue int
}

func (c customType) doFunctionOne() {
    fmt.Println("Method #1")
}

func (c customType) doFunctionTwo() {
    fmt.Println("Method #2")
}

func (c customType) value() int {
    return c.myValue
}

type Interface interface {
    value() int
    doFunctionOne()
    doFunctionTwo()
}

func someMethod(i Interface) {
    if i.value() <= 0 {
        i.doFunctionOne()
    } else {
        i.doFunctionTwo()
    }
}

type customTestingType struct {
    t *testing.T
}

func (c customTestingType) doFunctionOne() {
    c.t.Log("Working")
}

func (c customTestingType) doFunctionTwo() {
    c.t.Error("Not working")
}

func (c customTestingType) value() int {
    return 0
}

func TestInterface(t *testing.T) {
    someMethod(customTestingType{t})
}

当然,提供这种行为的方式可能有很多,但这取决于你的类型的具体声明。例如,你可以查看httptest包。也就是说,如果你真的想以这种方式模拟你的类型(非惯用方式),你可以使用一些不安全的猴子补丁

package main

import (
    "fmt"
    "reflect"

    "github.com/bouk/monkey"
)

type customType struct {
    myValue int
}

func (c customType) doFunctionOne() {
    fmt.Println("Method #1")
}

func (c customType) doFunctionTwo() {
    fmt.Println("Method #2")
}

func (c customType) someMethod() {
    if c.myValue <= 0 {
        c.doFunctionOne()
    } else {
        c.doFunctionTwo()
    }
}

func main() {
    c := customType{0}
    monkey.PatchInstanceMethod(reflect.TypeOf(c), "doFunctionOne",
        func(c customType) {
            fmt.Println("Method #1, but patched")
        })
    monkey.PatchInstanceMethod(reflect.TypeOf(c), "doFunctionTwo",
        func(c customType) {
            fmt.Println("Method #2, but patched")
        })
    c.someMethod()
}
英文:

The idiomatic way of mocking an object in Go is to make it explicit. A healthy interface should be testable by itself. So if we have something like this:

type customType struct {
	value int
}

func (c customType) doFunctionOne() {
	fmt.Println(&quot;Method #1&quot;)
}

func (c customType) doFunctionTwo() {
	fmt.Println(&quot;Method #2&quot;)
}

func (c customType) someMethod() {
	if c.value &lt;= 0 {
		c.doFunctionOne()
	} else {
		c.doFunctionTwo()
	}
}

We have to provide a way to change the implementation of doFunctionOne and doFunctionTwo explicitly. We can generalize the someMethod behavior using interfaces:

type customType struct {
	myValue int
}

func (c customType) doFunctionOne() {
	fmt.Println(&quot;Method #1&quot;)
}

func (c customType) doFunctionTwo() {
	fmt.Println(&quot;Method #2&quot;)
}

func (c customType) value() int {
	return c.myValue
}

type Interface interface {
	value() int
	doFunctionOne()
	doFunctionTwo()
}

func someMethod(i Interface) {
	if i.value() &lt;= 0 {
		i.doFunctionOne()
	} else {
		i.doFunctionTwo()
	}
}

type customTestingType struct {
	t *testing.T
}

func (c customTestingType) doFunctionOne() {
	c.t.Log(&quot;Working&quot;)
}

func (c customTestingType) doFunctionTwo() {
	c.t.Error(&quot;Not working&quot;)
}

func (c customTestingType) value() int {
	return 0
}

func TestInterface(t *testing.T) {
	someMethod(customTestingType{t})
}

Surely there will be more ways to provide this behavior but it depends on the particular declaration of your type. As an example, you can look at httptest package. That said, if you really want to mock your type in that way (nonidiomatic), you can some unsafe monkey patching:

package main

import (
	&quot;fmt&quot;
	&quot;reflect&quot;

	&quot;github.com/bouk/monkey&quot;
)

type customType struct {
	myValue int
}

func (c customType) doFunctionOne() {
	fmt.Println(&quot;Method #1&quot;)
}

func (c customType) doFunctionTwo() {
	fmt.Println(&quot;Method #2&quot;)
}

func (c customType) someMethod() {
	if c.myValue &lt;= 0 {
		c.doFunctionOne()
	} else {
		c.doFunctionTwo()
	}
}

func main() {
	c := customType{0}
	monkey.PatchInstanceMethod(reflect.TypeOf(c), &quot;doFunctionOne&quot;,
		func(c customType) {
			fmt.Println(&quot;Method #1, but patched&quot;)
		})
	monkey.PatchInstanceMethod(reflect.TypeOf(c), &quot;doFunctionTwo&quot;,
		func(c customType) {
			fmt.Println(&quot;Method #2, but patched&quot;)
		})
	c.someMethod()
}

答案3

得分: 0

  1. 在每个函数调用之前,你可以添加日志。
  2. 注入你自己的日志记录实现,例如 []string。
  3. 检查字符串切片是否有匹配的字符串。

但是,如果你正在创建一个函数工厂,最好将函数返回给调用者,由调用者来运行函数。
这样测试就会很简单。

第二个问题是,只有两种类型的函数,流程函数和逻辑函数。
你把流程和逻辑混在同一个函数中。
测试困难只是这种不良实践的一个症状。

英文:
  1. You can add log before each function call.
  2. Inject your own logger implementation, []string for example.
  3. Check the string slice for a matching strings.

But, if you are creating a function factory it would be better if you return the function to the caller and the caller will run the function.
Then testing is straightforward.

Second but, there is only two kind of functions, flow function and logic functions.
You mixed flow and logic in the same function.
Testing difficulty is just one symptom of this bad practice.

huangapple
  • 本文由 发表于 2015年6月5日 08:41:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/30656537.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定