简体   繁体   English

Golang测试模拟功能最佳实践

[英]Golang test mock functions best practices

I am developing some tests for my code (using the testing package), and I am wondering what's the best way to mock functions inside the tested function: 我正在为我的代码开发一些测试(使用测试包),我想知道在测试的函数中模拟函数的最佳方法是什么:

Should I pass the function as parameter? 我应该将函数作为参数传递吗? In that case, what if that function calls another function? 在这种情况下,如果该函数调用另一个函数怎么办? Should I pass both the first and second function as parameters in the tested one? 我应该在测试的函数中同时传递第一个和第二个函数作为参数吗?

Note: some of the functions are called on objects (ie someObj.Create()) and use HTTP API calls. 注意:某些函数是在对象上调用的(即someObj.Create()),并使用HTTP API调用。

UPDATE for clarification: 更新以进行澄清:

Example: functions 示例:函数

func f1() error {
  ... //some API call
}

func (s *SomeStruct) f2() error {
  return f1
}

func f3() error {
  return nil
}

func f4() error {
  ...
  err = obj.f2()
  ...
  err = f3()
  ...
}

For the above: if I want to test f4, what's the best way to mock f2 and f3? 对于以上内容:如果我要测试f4,模拟f2和f3的最佳方法是什么?

If I pass f2 and f3 to f4 as parameters it would work, but then what for the f2 test? 如果我将f2和f3传递给f4作为参数,它将起作用,但是f2测试又如何呢? Should I pass f1 to f2 as parameter? 我应该将f1传递给f2作为参数吗?

And if that's it, should then f4 have f1 as well in the parameters? 如果是这样,那么f4的参数中也应该也有f1吗?

As a general guideline, functions aren't very mockable so its in our best interests to mock structs that implement a certain interface that may be passed into functions to test the different branches of code. 作为一般准则,函数不是很容易模拟,因此为了我们最大的利益,模拟实现某些接口的结构,这些接口可以传递给函数以测试不同的代码分支。 See below for a basic example. 请参阅下面的基本示例。

package a

type DoSomethingInterface interface {
    DoSomething() error
}


func DoSomething(a DoSomethingInterface) {
    if err := a.DoSomething(); err != nil {
        fmt.Println("error occurred")
        return
    }
    fmt.Println("no error occurred")
    return
}

package a_test

import (
    "testing"
    "<path to a>/a"
)

type simpleMock struct {
    err error
}

func (m *simpleMock) DoSomething() error {
    return m.err
}

func TestDoSomething(t *testing.T) {
    errorMock := &simpleMock{errors.New("some error")}
    a.DoSomething(errorMock)
    // test that "an error occurred" is logged

    regularMock := &simpleMock{}
    a.DoSomething(regularMock)
    // test "no error occurred" is logged
}

In the above example, you would test the DoSomething function and the branches that happens eg. 在上面的示例中,您将测试DoSomething函数以及发生的分支。 you would create an instance of the mock with an error for one test case and create another instance of the mock without the error to test the other case. 您将为一个测试用例创建一个带有错误的模拟实例,并创建一个没有错误的模拟实例以测试另一种情况。 The respective cases are to test a certain string has been logged to standard out; 各自的情况是测试某个字符串已经被记录为标准输出; in this case it would be "error occurred" when simpleMock is instantiated with an error and "no error occurred" when there simpleMock is not instantiated with an error. 在这种情况下,这将是"error occurred" ,当simpleMock进行实例化一个错误, "no error occurred"时,有simpleMock不是一个错误实例化。

This can of course be expanded to other cases eg. 当然,这可以扩展到其他情况,例如。 the DoSomething function actually returns some kind of value and you want to make an assertion on the value. DoSomething函数实际上返回某种值,并且您想对该值进行assertion

Edit: 编辑:

I updated the code with the concern that the interface lives in another package. 我更新了代码,担心接口位于另一个软件包中。 Note that the new updated code has a package a that contains the interface and the function under test and a package a_test that is merely a template of how to approach testing a.DoSomething . 需要注意的是新更新的代码有一个包a包含接口和被测功能和包装a_test这仅仅是如何处理测试模板a.DoSomething

I'm not sure what you're trying to do here but I'll explain how testing should be done in Go. 我不确定您要在这里做什么,但是我将解释如何在Go中进行测试。

Lets say we have an application with the following directory hierarchy: 可以说我们有一个具有以下目录层次结构的应用程序:

root/
  pack1/
    pack1.go
    pack1_test.go
  pack2/
    pack2.go
    pack2_test.go
  main.go
  main_test.go

We'll assume that pack2.go has the functions you want to test: 我们假设pack2.go具有您要测试的功能:

package pack2 

func f1() error {
  ... //some API call
}

func (s *SomeStruct) f2() error {
  return f1
}

func f3() error {
  return nil
}

func f4() error {
  ...
  err = obj.f2()
  ...
  err = f3()
  ...
}

Looks good so far. 到目前为止看起来不错。 Now if you want to test the functions in pack2, you would create a file called pack2_test.go . 现在,如果要测试pack2中的功能,您将创建一个名为pack2_test.go的文件。 All test files in go are named similarly (packagename_test.go). go中的所有测试文件都使用类似的名称(packagename_test.go)。 Now lets see the inside of a typical test for a package (pack2_test.go in this example): 现在,让我们看一下典型的软件包测试内部(在此示例中为pack2_test.go):

package pack2

import (
  "testing"
  "fmt"
)

TestF1(*testing.T) {
  x := "something for testing"
  f1() // This tests f1 from the package "pact2.go"
}


TestF2(*testing.T) {
    y := new(somestruct) 
    y.f2() // tests f2 from package "pact2.go"
}


TestF3(*testing.T) {
   /// some code
   f3() // tests f3
}


TestF4(*testing.T) {
    /// code
    f3() // you get the gist
}

Let me explain. 让我解释。 Notice how in pack2_test.go, the first line says that the package is pack2 . 请注意,在pack2_test.go中,第一行说的是包是pack2 In a nutshell, this means that we're in the "scope" of the package pack2 and thus all the functions found in pack2 can be called as if you're within pack2 . 简而言之,这意味着我们位于pack2的“作用域”中,因此可以像在pack2pack2调用在pack2找到的所有函数。 Thats why, within the Testf* functions, we could've called the functions from pack2 . 因此,在Testf *函数中,我们可以从pack2调用这些函数。 Another thing to note is the imported package "testing". 要注意的另一件事是导入的软件包“ testing”。 This helps with two things: 这有两点帮助:

First, it provides some functionality for running tests. 首先,它提供了一些用于运行测试的功能。 I won't go into that. 我不会说的。 Second, it helps identify the functions that go test should run. 其次,它有助于识别功能go test应该运行。

Now to the functions. 现在到功能了。 Any function within a test package that has the prefix "Test" and the parameters "t *testing.T" (you can use "*testing.T" when you don't need to use the testing functionality) will be executed when you run go test . 测试包中任何带有前缀“ Test”和参数“ t * testing.T”的函数(不需要使用测试功能时都可以使用“ * testing.T”)将在您执行时执行go test You use the variable t to reference the testing functionality I mentioned. 您使用变量t引用了我提到的测试功能。 You can also declare functions without the prefix and call them within the prefixed functions. 您也可以声明不带前缀的函数,并在带前缀的函数中调用它们。

So, if I go to my terminal and run go test , it will execute the functions you want to test, specified in pack2_test.go 因此,如果我转到终端并运行go test ,它将执行pack2_test.go指定的要测试的功能。

You can learn more about testing here and here 您可以在此处此处了解有关测试的更多信息

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM