我很难弄清楚在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
之类的工具,您似乎无法使用结构和软件包功能。
处理此问题的惯用方法是什么?我非常适合控制控制和不同模式(例如依赖注入和服务定位器),但我听到无数次这不是惯用的。
GO的一个重要设计功能是 deconpling (观看Bill Kennedy关于该主题的出色演讲)。在您的方法中,有一些依赖性,可以解耦。这种耦合方法使其无法真正测试。
您应该重构的东西:
-
tp := github.BasicAuthTransport
:您不应初始化方法内部的授权。它应该作为参数进入您的githubservice。在您的方法调用中,您可以通过s.tp
访问IST。您也可以使其成为方法的输入参数。 -
github.NewClient()
和client.Repositories.CreatePullRequest(...)
只需阅读Peter Bourgon的Golang最佳实践,使依赖关系明确!替代方法是创建一个包含所有称为函数的接口。此界面应该是您方法的输入。
脱钩后,您可以非常简单地模拟所有内容。如果将接口用作输入,则可以创建一个模拟结构,该结构实现接口。如果使依赖项明确,则可以覆盖它们。在最后一个情况下,存储呼叫值的代码不是那么干净,但也有效。惯用方法是使用接口。
我也有一个类似的问题,看起来唯一的选择是在其中有另一层将您称为您的"无法锁定"客户端。
例如对于嘲笑GoVMi客户端(vmware客户端的SDK,用于Golang),我必须有一个" mycustomclient"具有接口和结构来拨打govmi.client.client.anymethod ..
我可以为" mycustomclient"生成模拟。
oiggen -source mycustomclient.go -package mypackage -destination mycustomclientmock.go
您可以通过:get get github.com/golang/mock