模拟时如何不使用接口?

How do I not use an interface when mocking?

我试图避免使用接口,因为我的用例不需要它(https://www.ardanlabs.com/blog/2016/10/avoid-interface-pollution.html 中同样提到)。但是,要创建模拟(使用 testify),我不需要模拟接口吗?我可以创建一个,但它看起来很乏味。所有使用我的代码的地方都需要为它们的模拟编写接口,而不是为它们的实际使用编写接口。有解决方法吗?

你需要一个模拟接口,因为模拟 是你的第二个实现,所以如果你在单元测试中使用模拟,那么你的用例 需要它。该接口用作占位符,用于填充该功能的某些实现。您计划至少有两个这样的实现:一个模拟,一个生产。为了能够互换使用它们进行测试,您必须使用接口。

如果接口的唯一可能用途是测试,我同意,那可能是一个糟糕的接口,你应该避免它。

最好的方法是重构您的系统以依赖于有用的 小接口,而不是过于复杂结构的一次性“模拟”。 net.Listener 接口就是一个很好的例子。建立在 net.Listener 之上的系统很容易模拟,是的,但这不是您实现 net.Listener 的原因。您使用该接口是因为它允许您交换 许多 可能的实现,其中之一恰好用于测试。

另一种强大的方法是将函数链接在一起,而不是将函数硬编码为方法。 http.HandlerFunc 就是一个很好的例子,它也展示了出色的界面设计。查看许多 return 一个 http.Handler 的“点击在一起”功能,而不是一个您必须模拟以进行测试的大型“处理程序”结构。这是 IMO 最好的 Go。

记住函数在 Go 中是第一个 class,通过传递和 returning 函数可以获得很大的灵活性,而不是将它们绑定到接口。当你这样做时,你也可以将它们捆绑在一起作为一个结构。这可以提供很多功能,而这些功能恰好对测试也很有用。例如,请参阅 tls.Config,它允许您包含自己的 GetCertificate 函数(以及其他函数)。但是 tls.Config 结构也有合理的默认值,所以你不必每次都配置每个部分。通过传递 tls.Config 的专门版本,您可以在不“模拟”任何东西的情况下测试 TLS 功能。

这里一贯的主题是使您的系统在其实现方面具有灵活性,并且作为一个很好的副作用,它使测试更容易。根据我的经验,这是一种比模拟更好的思考问题的方法。

为了减少模拟较大接口的重复次数,似乎可以使用 vectra/mockery 之类的东西来生成模拟。然而,这不是消除对接口需求的解决方案,但我认为 Rob 在下面提到了一些好的想法。