【问题标题】:What is the most idiomatic way in Go to test code which has dependency on structure with big amount of methods?在 Go 中测试依赖于具有大量方法的结构的代码的最惯用的方法是什么?
【发布时间】:2016-12-04 06:05:25
【问题描述】:

假设我有一个 UserRepository 结构,它封装了与数据库交互的逻辑。该结构有一组方法,例如:

  • findAll()
  • findById()
  • findByName()
  • 保存()
  • 等等....

还有另一个依赖于 UserRepository 结构的结构(我们称之为 UserService)。

要测试 UserService,我需要模拟 UserRepository 的功能。我知道这样做的唯一方法是为 UserRepository 提供接口并使 UserService 依赖于它而不是 UserRepository 结构。它将允许创建接口的模拟实现并将其设置为测试中 UserService 的依赖项。

最惯用的方法是什么?

1) 如果 UserService 仅依赖于 1 个 UserRepository 的方法(比如说 findAll)——我应该仍然定义一个包含所有存储库方法的接口,还是最好只为这个方法定义一个单独的接口并将其用作用户服务?如果是这样,它的最佳名称是什么(接口)?如果另一个结构将依赖于 findAll() 和 findById() 方法,我应该再次创建另一个接口吗?

2) 为这些接口存储模拟的最佳位置在哪里?是否可以重复使用它们?或者对于不同结构的测试,我需要重新定义模拟?

附:对我来说,单元测试是项目中非常重要的一部分。我想让它们尽可能地可读,避免样板代码并专注于它们的逻辑。因此,在不同的测试文件中为相同的接口创建多个模拟实现对我来说是一个糟糕的选择,因为它会降低测试代码的可读性。

【问题讨论】:

  • 我不知道这是否被认为是惯用的,但我的直觉是为 UserRepository 创建一个具有您需要的最小表面积的接口(目前只有一种方法)。如果它正在与 MongoDb 交谈,我会将接口 UserRepository 和结构称为更具体的实现,例如 MongoUserRepository
  • 您可以创建一个名为mocks_test.go 的文件并将您的模拟文件保存在其中。只要该文件与您的其他测试在同一个包中,他们就可以使用模拟。同样,我不知道这是否会被认为是惯用的。
  • 问题是用这种方法我最终会得到很多接口。 FindAll() 方法的一个接口。 FindByName() 方法的另一个接口。 FindAll() 和 FindById() 方法等组合的第三个接口。我不能将它们都称为 UserRepository。
  • 我不是在暗示。创建一个接口并根据接口调用者的需要放入尽可能多的方法。你不应该在没有被另一个结构调用的接口中添加任何额外的东西。
  • 在我看来你正在尝试用 Go 编写 Java。

标签: unit-testing testing go


【解决方案1】:

1) 我会同意 elevine 所说的,即只需要该结构所需的方法。例如:您有需要FindByNameFindAllUserService,以及需要FindByIdFindAllSaveUserAdminService。在这种情况下,您应该有两个接口:

  1. UserProviderFindByNameFindAll
  2. UserAdminProviderFindByIdFindAllSave

这还可以让您检查您的UserProvider,例如你知道它不能调用Save,所以它不能修改用户。

您可能只需要一个满足两个接口的实际实现。

2) 查看testify/mockmockery。 Mockery 将在mocks 子包中为您的接口生成模拟,每个接口一个。 这确实意味着您不能对两个测试使用相同的模拟结构,但这没关系,因为代码是生成的。您不会在模拟结构中模拟接口的行为,而是通过设置期望在测试中进行模拟,例如:

func TestThatYouCantLoginWithNonexistentUser(t *testing.T) {
    userRepository := new(mocks.UserRepository)
    userService := user.NewService(userRepository)
    // if the userService calls UserRepository.FindByName("joe"), it will return nil, since there's no such user.
    userRepository.On("FindByName", "joe").Return(nil)
    _, err := userService.Login("joe", "password")
    // err should not be nil and the message should be "user does not exist"
    assert.EqualError(t, err, "user does not exist")
    // assert that the expectations were met, i.e. FindByName was called with "joe"
    userRepository.AssertExpectations(t)
}

这实际上使测试易于理解,因为当您调用 FindByName("joe") 时,您不必检查其他文件模拟的作用。

【讨论】:

    猜你喜欢
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 2019-03-03
    • 2015-10-23
    • 2012-03-25
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    相关资源
    最近更新 更多