简体   繁体   English

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

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

I want to implement Go interfaces with reflection to generate mocks and stubs.我想用反射实现 Go 接口来生成模拟和存根。 But if I look at the reflect package, I get no idea how to do it (maybe it is not possible).但是如果我查看反射包,我不知道该怎么做(也许这是不可能的)。

Example: Testing that a func calls WriteHeader(404) on ResponseWriter :示例:测试 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
}

With RhinoMocks (C#) I would write it like this:使用 RhinoMocks (C#) 我会这样写:

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

funcToTest(responseWriterMock);

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

How can I implement Go interfaces with reflection?如何使用反射实现 Go 接口?

Addendum附录

Seems like it is not possible today.看来今天是不可能了。

Unfortunately, due to the static nature of the language, the reflection package is a bit limited and there is no way to create dynamic objects at runtime.不幸的是,由于语言的静态特性,反射包有点受限,无法在运行时创建动态对象。 It might never be possible.这可能永远不可能。

I can propose a few solutions - all involving the creation of any stubs/mocks at design time.我可以提出一些解决方案 - 所有这些都涉及在设计时创建任何存根/模拟。

  1. Write your mocks manually.手动编写你的模拟。

    I've seen people go down that road and it quickly becomes unbearable once the codebase becomes larger.我见过人们走这条路,一旦代码库变大,它很快就会变得难以忍受。

    In general, the idea is to write a structure that implements your interface.一般来说,这个想法是编写一个实现你的接口的结构。 In each method you add code that increments some counter (in order to track invocations) and often returns a value that has been hardcoded or provided as a configuration to the structure.在每个方法中,您添加的代码会增加一些计数器(以跟踪调用),并且通常会返回一个已硬编码或作为结构配置提供的值。

  2. Use the testify toolkit.使用testify工具包。

    At the time of writing, this appears to be the most popular project out there providing mocking support.在撰写本文时,这似乎是提供模拟支持的最受欢迎的项目。 However, since the project provides other features, it is questionable how many of the likes are due to the mocking capability itself.但是,由于该项目提供了其他功能,因此有多少赞是由于模拟功能本身造成的,这是值得怀疑的。

    I have not used this toolkit and cannot provide detailed analysis as to whether it is any good.我没有使用过这个工具包,无法详细分析它是否有用。

    Looking at the README, it appears that it takes an approach very similar to the manual mocking one (explained above).查看自述文件,它似乎采用了一种与手动模拟(如上所述)非常相似的方法。 It provides some helper code on top but the fake methods still need to be typed manually.它在顶部提供了一些辅助代码,但仍然需要手动输入假方法。 Stubbing in the tests is then done by specifying the method via a string, which is going against the static nature of the Go language and might be a problem for some users.然后通过字符串指定方法来完成测试中的存根,这违背了 Go 语言的静态特性,可能对某些用户来说是个问题。

  3. Use the official mock tool from Go.使用 Go 的官方模拟工具。

    This tool seems to have gained popularity since I last wrote about it.自从我上次写这篇文章以来,这个工具似乎越来越受欢迎。 Furthermore, as on official Go project, one can expect that this tool will quickly adopt any new Go features.此外,作为官方 Go 项目,可以预期该工具将很快采用任何新的 Go 功能。 The support it has for go modules is a good example, where other tools lag behind.它对 go 模块的支持就是一个很好的例子,其他工具落后了。

    It does take a specific approach to mocking.它确实采用了特定的模拟方法。 You need to specify your test expectations at the beginning of the test code.您需要在测试代码的开头指定您的测试期望。 At the end of the test, the framework checks whether all expectations have been met.在测试结束时,框架会检查是否满足所有预期。 Unexpected calls cause the framework to fail the test.意外调用会导致框架无法通过测试。

    If you are using a custom testing framework like Ginkgo , it might be tricky to cleanly integrate.如果您使用像Ginkgo这样的自定义测试框架,干净地集成可能会很棘手。 In my projects, I have written helper functions to bridge the gap.在我的项目中,我编写了辅助函数来弥补差距。

    While it does generate the fake methods for you, the assertion and expectation methods it generates take interface{} types.虽然它确实为您生成了假方法,但它生成的断言和期望方法采用interface{}类型。 This allows the framework to provide more advanced argument matchers but also means that you need to ensure you pass the correct number of arguments and in the correct order.这允许框架提供更高级的参数匹配器,但也意味着您需要确保以正确的顺序传递正确数量的参数。

    In my experience, tests written with this tool tend to be bigger in side and at times more complicated to read.根据我的经验,使用此工具编写的测试往往更大,有时阅读起来更复杂。 On the plus side, they often test things more thoroughly and are more expressive.从好的方面来说,他们经常更彻底地测试事物并且更具表现力。

  4. Use the counterfeiter tool.使用造假工具。

    This tool has been used a lot in the Cloud Foundry ecosystem and has proven itself as a viable option.该工具已在 Cloud Foundry 生态系统中大量使用,并已证明它是一个可行的选择。

    It allows you to track the number of times a given method has been called, the arguments that were used to call the method, and to fake either the whole method with a custom implementation or specify the result values to be returned.它允许您跟踪给定方法被调用的次数、用于调用该方法的参数,以及使用自定义实现伪造整个方法或指定要返回的结果值。

    What makes it a good choice is that it produces very explicit fakes.使它成为一个不错的选择的是,它会产生非常明显的假货。 Developers don't need to use string names to specify a method, nor do they need to remember the order and number of arguments.开发人员不需要使用字符串名称来指定方法,也不需要记住参数的顺序和数量。 The generated helper methods mimic closely the original ones, with arguments of the correct type and order.生成的辅助方法与原始方法非常相似,具有正确类型和顺序的参数。


Some of the options I provided above require that you run a command line tool to generate the source code for your mocks.我上面提供的一些选项要求您运行命令行工具来为您的模拟生成源代码。 As the number of interfaces that need to be mocked can quickly grow, there is a cool trick you could use to keep track of everything.由于需要模拟的接口数量会迅速增加,因此您可以使用一个很酷的技巧来跟踪所有内容。

You can take benefit of the go:generate functionality in Go to easily generate all your stubs/fakes, without having to recall any command parameters or to manually run the tool for each interface.您可以利用 Go 中的go:generate功能轻松生成所有存根/伪造品,而无需调用任何命令参数或为每个界面手动运行该工具。

All you need to do is to add the proper go:generate comment in the file that contains your interface.您需要做的就是在包含您的界面的文件中添加正确的go:generate注释。

mock嘲笑

//go:generate mockgen -source person.go

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

counterfeiter造假者

//go:generate counterfeiter ./ Person

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

You can then run the go generate ./... command in the root of your project and all the stubs/fakes will be regenerated for you.然后,您可以在项目的根目录中运行go generate ./...命令,所有存根/假货都将为您重新生成。 This can be a lifesaver if you are working on a project with multiple collaborators.如果您正在与多个合作者一起开展项目,这可以成为救命稻草。

To the best of my knowledge and the answers of this related question , it is not possible to create new types at runtime.据我所知以及这个相关问题的答案,在运行时创建新类型是不可能的。

You may want to try the go-eval package which should support defining new types in it's universe.您可能想尝试go-eval,它应该支持在它的 Universe 中定义新类型。

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

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