繁体   English   中英

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

[英]How to implement or mock a Go interface with reflection?

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

示例:测试 func 在ResponseWriter上调用WriteHeader(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 接口?

附录

看来今天是不可能了。

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

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

  1. 手动编写你的模拟。

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

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

  2. 使用testify工具包。

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

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

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

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

    自从我上次写这篇文章以来,这个工具似乎越来越受欢迎。 此外,作为官方 Go 项目,可以预期该工具将很快采用任何新的 Go 功能。 它对 go 模块的支持就是一个很好的例子,其他工具落后了。

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

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

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

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

  4. 使用造假工具。

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

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

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


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

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

您需要做的就是在包含您的界面的文件中添加正确的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 ./...命令,所有存根/假货都将为您重新生成。 如果您正在与多个合作者一起开展项目,这可以成为救命稻草。

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

您可能想尝试go-eval,它应该支持在它的 Universe 中定义新类型。

暂无
暂无

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

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