【问题标题】:Golang test mock functions best practicesGolang 测试模拟函数最佳实践
【发布时间】:2016-08-09 21:20:55
【问题描述】:

我正在为我的代码开发一些测试(使用 testing 包),我想知道在测试函数中模拟函数的最佳方法是什么:

我应该将函数作为参数传递吗? 在这种情况下,如果该函数调用另一个函数怎么办?我应该将第一个和第二个函数都作为参数传递给被测函数吗?

注意:一些函数在对象上调用(即 someObj.Create())并使用 HTTP API 调用。

更新澄清:

示例:函数

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

对于以上内容:如果我想测试 f4,模拟 f2 和 f3 的最佳方法是什么?

如果我将 f2 和 f3 作为参数传递给 f4,它会起作用,但是 f2 测试呢?我应该将 f1 传递给 f2 作为参数吗?

如果是这样,那么 f4 是否应该在参数中也包含 f1 ?

【问题讨论】:

  • 当你在测试函数中说模拟函数时,你的意思是什么?
  • 用一个例子更新了问题。我希望它更清楚。我对测试很陌生,我什至不能 100% 确定如何提问
  • 你需要澄清更多。这些功能是否在您正在测试的包中?
  • f1和f2不在被测包中,f3在(但实际代码中f2在被测包内的另一个函数中调用,所以多了一层)

标签: testing go


【解决方案1】:

作为一般准则,函数不是非常可模拟的,因此模拟实现特定接口的结构体最符合我们的利益,这些结构体可以传递到函数中以测试不同的代码分支。请参阅下面的基本示例。

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
}

在上面的示例中,您将测试DoSomething 函数和发生的分支,例如。您将为一个测试用例创建一个带有错误的模拟实例,并创建另一个没有错误的模拟实例来测试另一个用例。各个案例都是为了测试某个字符串是否已经被记录到标准输出;在这种情况下,当 simpleMock 被实例化并出现错误时,它将是 "error occurred",而 "no error occurred"simpleMock 未被实例化时出现错误。

这当然可以扩展到其他情况,例如。 DoSomething 函数实际上返回某种值,您想在该值上创建一个 assertion

编辑:

我更新了代码,担心接口存在于另一个包中。请注意,新更新的代码有一个包a,其中包含被测接口和函数,还有一个包a_test,它只是一个如何进行测试的模板a.DoSomething

【讨论】:

  • 您好,感谢您的回答。问题是我需要使用的接口不在我需要测试的同一个包中。是否可以在不同的包中实现接口?我正在寻找但找不到示例
  • 是的,你仍然可以在另一个包中实现另一个接口。 Go 与 Java 的不同之处在于它不需要显式的“实现”来遵守接口,相反,它实际上更像是动态语言中的鸭子类型。您所要做的就是对特定结构具有相同的方法,并且该结构可用于满足函数期望的接口。
  • @Ciack404 我更新了代码示例以模板化可能感兴趣的接口代码。让我知道这是否有帮助。
【解决方案2】:

我不确定您在这里要做什么,但我会解释在 Go 中应该如何进行测试。

假设我们有一个具有以下目录层次结构的应用程序:

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

我们假设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()
  ...
}

目前看起来不错。现在,如果您想测试 pack2 中的函数,您将创建一个名为 pack2_test.go 的文件。 go 中的所有测试文件都以类似的方式命名(packagename_test.go)。现在让我们看看一个包的典型测试的内部(在这个例子中是 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
}

让我解释一下。注意在 pack2_test.go 中,第一行说包是pack2。简而言之,这意味着我们在包pack2 的“范围”内,因此可以像在pack2 内一样调用pack2 中的所有函数。这就是为什么在 Testf* 函数中,我们可以调用来自pack2 的函数。另外需要注意的是导入的包“testing”。这有助于两件事:

首先,它提供了一些运行测试的功能。我不会进入那个。 其次,它有助于识别go test 应该运行的功能。

现在到函数。测试包中具有前缀“Test”和参数“t *testing.T”(不需要使用测试功能时可以使用“*testing.T”)的任何函数都将在您执行运行go test。您使用变量t 来引用我提到的测试功能。您还可以声明不带前缀的函数,并在带前缀的函数中调用它们。

所以,如果我去我的终端并运行go test,它将执行你想要测试的功能,在pack2_test.go中指定

您可以了解有关测试herehere 的更多信息

【讨论】:

  • 您好,感谢您的回复。但是,如果 package2 中的函数正在使用 package1 中我需要模拟的函数(接口),会发生什么?如果我在您的示例中正确地测试了同一个包中的所有功能,对吗?
  • 所以你需要从pack1调用一个函数?这样做:`import pack1; pack1.Function() // 调用函数 但是函数需要是公共的(以大写字母开头)。
  • 但问题是如何模拟 pack1.Function(),因为您不想从 pack1 包中运行真正的函数,而是使用模拟版本来控制输入和输出.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
  • 2011-06-29
  • 2020-02-05
  • 2018-07-25
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多