Golang中如何模拟外部结构体依赖

4

我发现在golang中编写可测试代码的习惯用法很难理解。我知道接口的重要性以及它们在测试中的使用,但是我还没有找到如何模拟/测试外部结构依赖关系。

举个例子,我编写了下面的代码来模拟在GitHub上创建拉取请求的包装器。

type GitHubService interface {

}

type gitHubService struct {
    CreatePullRequest(...) (PullRequest,error)
}

func (s gitHubService) CreatePullRequest(...) (PullRequest,error) {
  tp := github.BasicAuthTransport{
      Username: strings.TrimSpace(/*.....*/),
      Password: strings.TrimSpace(/*.....*/),
    }

    client := github.NewClient(tp.Client())
  pr,err := client.Repositories.CreatePullRequest(...)

  ...
}

func TestPullRequest(t *testing.T) {
  service := gitHubService{}
  pr,err := service.CreatePullRequest(...)
  ...
}

如果我要为GitHubService.CreatePullRequest(...)编写单元测试,我希望模拟调用client.Repositories.CreatePullRequest(...)甚至是github.NewClient(...)并返回我可以控制的模拟实现。
使用像gomock这样的工具似乎无法处理结构体和包函数。
如何处理这种情况? 我非常熟悉控制反转以及不同的模式,例如依赖注入和服务定位器,但我已经听到过无数次这不是惯用方法。

一个单元测试应该只测试 CreatePullRequest 的逻辑,我对集成测试不感兴趣。 - TheJediCowboy
我的意思是这里没有逻辑需要测试——一切都发生在具有自己测试的github包内。如果您确实在“...”后面隐藏了其他逻辑,则将其拆分为单独的部分进行测试。 - JimB
@JimB 如果我将 ... 后面的逻辑拆分成自己的测试,那么我测试了这些内容,但没有测试 CreatePullRequest(...) 中的逻辑。我仍然希望有一个单元测试来测试这个代码单元的逻辑流程。 - TheJediCowboy
2个回答

4
Go语言的一个重要设计特点是解耦(观看Bill Kennedy关于这个主题的精彩演讲)。在你的方法中,存在一些依赖关系,可以进行解耦。这种耦合的方法使得它不太容易测试。
需要重构的事情:
  • tp := github.BasicAuthTransport:你不应该在方法内部初始化授权。它应该作为参数移动到你的gitHubService中。在你的方法调用中,你可以通过s.tp访问它。你也可以将其作为方法的输入参数。
  • github.NewClient()client.Repositories.CreatePullRequest(...)只需阅读Peter Bourgon的Golang最佳实践明确依赖关系!。另一种选择是创建一个包含所有被调用函数的接口。这个接口应该作为你的方法的输入。
在您的代码解耦后,您可以非常轻松地模拟一切。如果您将接口用作输入,则可以创建一个实现该接口的模拟结构。如果您明确制定依赖项,则可以覆盖它们。在最后一种情况下,存储调用值的代码不太清晰,但也可以正常工作。惯用的 Go 方法是使用接口。

3
我曾经遇到过类似的问题,看起来你唯一的选择是在中间添加另一个层次来调用你的“无法模拟”客户端。
例如,为了模拟govmi客户端(用于golang的VMware客户端SDK),我必须拥有一个“myCustomClient”,其中包含接口和结构体以调用govmi.Client.AnyMethod...。
然后,我可以为“myCustomClient”生成模拟数据。
mockgen -source myCustomClient.go -package myPackage -destination myCustomClientMock.go
您可以通过以下方式安装它:`got get github.com/golang/mock`

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接