为数据存储库创建模拟对象是网络应用程序开发中的一个常见需求。在这篇文章中,我将告诉你如何使用GoMock为你的接口自动生成模拟对象并在单元测试中使用它们。
当你有一个依赖资源库获取数据的服务时,这个资源库很可能会被连接到一个数据库。一个典型的事件序列可能是这样的:
- 服务 A 从资源库 B 请求一个对象 X
- 存储库B连接到数据库C并获取请求的行
- 存储库B以适当的格式将X返回给服务A
但你当然想为服务A写单元测试。最简单的解决方案是在单元测试中也依赖数据库,但这有多个缺点(其中包括:数据库在运行测试时需要一直保持一致;测试会慢很多;由于数据库问题导致的测试失败会指向错误的方向)。
嘲弄框架通过提供 "假的 "对象来解决这个问题,我们可以用它来代替原来的存储库。它们不连接到数据库,而是返回硬编码的对象(在最简单的情况下)。
一个简单的例子--管理约会
我们将通过一个具体的例子来了解这个嘲弄过程。假设我们想让我们的Go应用程序跟踪约会(自定义结构,比如说有一个开始日期,一个名字和一个姓氏;但我们在这里并不关心他们的具体细节)。这些约会应该被持久化到一个关系数据库中,我们使用一个资源库来访问这个数据库。
此外,还有一个AppointmentService ,它基于约会对象来执行业务逻辑。它使用AppointmentRepository 来获取和持久化约会。
为了允许嘲弄,我们将AppointmentRepository 分成一个接口(IAppointmentRepository )和实现(AppointmentRepository )。现在,只有一个方法被支持,即GetAllAppointments() ,它从数据库中获取所有持久化的约会。最后,我们要创建IAppointmentRepository 的第二个实现:一个名为MockIAppointmentRepository 的模拟实现。它将由GoMock自动生成,我们可以命令它在调用GetAllAppointments() 方法时返回一个特定的约会集。
综上所述,我们的目标是创建以下的类。

安装GoMock并生成mock对象
为了开始,打开一个终端,将工作目录改为你的项目根目录,然后输入
go install github.com/golang/mock/mockgen@v1.5.0
which mockgen
这应该会把GoMock安装到你的$GOPATH (确保它也在你的路径中,使下载的mockgen 文件可以执行)。
根据你的$GOPATH ,你会得到一个类似的输出。
/Users/bernhard/go/bin/mockgen
接下来,我们通过运行以下命令来创建模拟对象。
mockgen -source=interfaces/IAppointmentRepository.go -destination=interfaces/mocks/IAppointmentRepository.go -package=mocks
-source 告诉mockgen我们要模拟哪个接口; ,指定新生成文件的路径, ,指定新生成文件的包名。-destination -package
在单元测试中使用生成的mock
我们现在准备在单元测试中使用生成的mock对象。创建一个名为AppointmentService_test.go 的文件,内容如下:
package services
import (
"github.com/golang/mock/gomock"
"testing"
"time"
"mockExample/interfaces/mocks"
"mockExample/models"
)
func TestAppointmentService_GetCountAppointments(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockAppointmentRepo := mocks.NewMockIAppointmentRepository(mockCtrl)
mockAppointmentRepo.EXPECT().GetAllAppointments().Return([]models.Appointment{{
Id: 1,
Start: time.Time{},
FirstName: "Donald",
LastName: "Duck",
}, {
Id: 2,
Start: time.Time{},
FirstName: "Daisy",
LastName: "Duck",
}}, nil)
appointmentService := AppointmentService{IAppointmentRepository: mockAppointmentRepo}
appointmentCount, _ := appointmentService.GetCountAppointments()
if appointmentCount != 2 {
t.Errorf("AppointmentCount not matching")
}
}
该单元测试创建了一个模拟对象(mockAppointmentRepo),并指定当GetAllAppointments() 方法被调用时它应该返回什么(在这种情况下,它将返回两个硬编码的约会实例)。
然后,模拟对象被传递给约会服务,服务的GetCountAppointments() 方法被调用。在内部,这使用了IAppointmentRepository ,并只是返回从存储库中收到的约会的总计数。
运行单元测试
现在你可以使用go test运行生成的测试--它应该通过,因为预期的元素计数(2)是正确的。
=== RUN TestAppointmentService_GetCountAppointments
--- PASS: TestAppointmentService_GetCountAppointments (0.00s)
PASS
Process finished with the exit code 0