2

我想用反射实现 Go 接口以生成模拟和存根。但是如果我查看反射包,我不知道该怎么做(也许不可能)。

示例:测试 func 调用ResponseWriterWriteHeader(404)

type ResponseWriterMock struct {                              //
  status int                                                  //
}                                                             // How to replace
func (*ResponseWriterMock) Header() Header {}                 // this block with 
func (*ResponseWriterMock) Write([]byte) (i int, e error) {}  // a reflectivly 
func (m *ResponseWriterMock) WriteHeader(status int) {        // generated mock? 
  m.status = status                                           //  
}                                                             //
responseWriterMock := new(ResponseWriterMock)

funcToTest(responseWriterMock)

if responseWriterMock.status != 404 {
    // report error
}

使用 RhinoMocks (C#) 我会这样写:

var responseWriterMock = MockRepository.GenerateMock<ResponseWriter>();

funcToTest(responseWriterMock);

responseWriterMock.AssertWasCalled(rw => rw.WriteHeader(404));

如何使用反射实现 Go 接口?

附录

看来今天是不可能了。

4

2 回答 2

3

不幸的是,由于语言的静态特性,反射包有点受限,无法在运行时创建动态对象。这可能永远不可能。

我可以提出一些解决方案——所有这些都涉及在设计时创建任何存根/模拟。

  1. 手动编写你的模拟。

    我见过人们走这条路,一旦代码库变得更大,它很快就会变得难以忍受。

    一般来说,这个想法是编写一个实现你的接口的结构。在每个方法中,您添加的代码会增加一些计数器(以跟踪调用),并且通常会返回一个已硬编码或作为结构配置提供的值。

  2. 使用证明工具包。

    在撰写本文时,这似乎是提供模拟支持的最受欢迎的项目。然而,由于该项目提供了其他功能,因此有多少喜欢是由于模拟功能本身造成的。

    我没有使用过这个工具包,也无法详细分析它是否有用。

    查看自述文件,它似乎采用了一种与手动模拟非常相似的方法(如上所述)。它在顶部提供了一些帮助代码,但仍然需要手动输入伪造的方法。然后通过字符串指定方法来完成测试中的存根,这违背了 Go 语言的静态特性,并且可能对某些用户来说是个问题。

  3. 使用 Go 的官方模拟工具。

    自从我上次写这篇文章以来,这个工具似乎已经流行起来。此外,与官方 Go 项目一样,可以预期该工具将很快采用任何新的 Go 功能。它对 go 模块的支持就是一个很好的例子,而其他工具则落后。

    它确实采用了一种特定的模拟方法。您需要在测试代码的开头指定您的测试期望。在测试结束时,框架会检查是否满足所有期望。意外调用会导致框架测试失败。

    如果您使用的是像Ginkgo这样的自定义测试框架,那么干净地集成可能会很棘手。在我的项目中,我编写了辅助函数来弥合差距。

    虽然它确实为您生成了假方法,但它生成的断言和期望方法采用interface{}类型。这允许框架提供更高级的参数匹配器,但也意味着您需要确保以正确的顺序传递正确数量的参数。

    以我的经验,用这个工具编写的测试往往更大,有时阅读起来也更复杂。从好的方面来说,他们经常更彻底地测试事物并且更具表现力。

  4. 使用造假工具。

    该工具已在 Cloud Foundry 生态系统中大量使用,并已证明自己是一个可行的选择。

    它允许您跟踪调用给定方法的次数、用于调用该方法的参数,并使用自定义实现来伪造整个方法或指定要返回的结果值。

    使它成为一个不错选择的原因是它会产生非常明显的假货。开发人员不需要使用字符串名称来指定方法,也不需要记住参数的顺序和数量。生成的辅助方法与原始辅助方法非常相似,具有正确类型和顺序的参数。


我上面提供的一些选项要求您运行命令行工具来为您的模拟生成源代码。随着需要模拟的接口数量迅速增长,您可以使用一个很酷的技巧来跟踪所有内容。

您可以利用go:generateGo 中的功能轻松生成所有存根/伪造品,而无需调用任何命令参数或为每个接口手动运行该工具。

您需要做的就是go:generate在包含您的界面的文件中添加正确的注释。

嘲笑

//go:generate mockgen -source person.go

type Person interface {
  Name() string
  Age() int
}

造假者

//go:generate counterfeiter ./ Person

type Person interface {
  Name() string
  Age() int
}

然后,您可以在项目的根目录中运行该go generate ./...命令,所有存根/伪造文件都将为您重新生成。如果您正在与多个合作者一起处理一个项目,这可能是一个救命稻草。

于 2015-07-22T19:14:33.280 回答
2

据我所知和这个相关问题的答案,不可能在运行时创建新类型。

您可能想尝试应该支持在其宇宙中定义新类型的go-eval包。

于 2012-10-18T12:00:01.443 回答