基于 Spock 交互的测试:对方法的调用太少
Spock interaction based test: too few invocation on a method
下面有一个方法非常简单。它调用另一种方法软删除 API 键,然后调用另一种方法创建一个新键并 returns 它。
测试也在下面,它只是检查这两个方法是否被正确调用。但是仍然在这两种方法上都出现 0 调用错误。是什么导致了这个问题?
AuthApiKeyPair updateApiKeyPair(AuthApiKeyPair apiKeyPair, Boolean createNewKey) {
AuthApiKeyPair newKeyPair
if (createNewKey) {
deleteApiKeyPair(apiKeyPair)
//The key will be created with the same info as the previous key.
newKeyPair = createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
}
newKeyPair
}
测试:
def "should soft delete key pair and create new one"() {
setup:
AuthApiKeyPair apiKeyPair = AuthApiKeyPair.build(acquirerId: 123, source: PaymentSource.BOARDING_API, label: 'Boarding API key')
when:
service.updateApiKeyPair(apiKeyPair, true)
then:
1 * service.deleteApiKeyPair(apiKeyPair)
1 * service.createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
}
如果您考虑您的测试,您会意识到在最佳情况下,它测试的是 Spock 的模拟机制,而不是您的业务代码。您没有向我们展示完整的 class 和您的测试规范,但是根据您的场景,我们可以假设您测试中的 service
只是一个模拟。如果这是真的,那么你不能指望这两个调用:
then:
1 * service.deleteApiKeyPair(apiKeyPair)
1 * service.createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
发生。仅仅是因为模拟 class 不执行真正的方法。
我强烈建议您测试一个真实的 class 而不是特定方法导致的调用类型,而是 调用特定方法的预期(和确定性)结果是什么 代替。您可以在 when
子句中对真实对象执行 service.updateApiKeyPair(apiKeyPair, true)
然后您可以检查是否创建了新的 API 密钥对(并保存在您使用的存储中)以及旧对是否创建了不存在了。与仅检查调用相比,这样的测试至少有一些好处:
- 您可以随时更改
service.updateApiKeyPair()
的实现,只要它产生相同的结果,您的测试仍然有用(因为测试不会像调用测试那样限制您的实现),
- 你测试实际行为而不是模拟库 - 有这个
轶事说 Mockito 是数百万项目中经过最严格测试的库。
当然,它可能需要一些设计更改。我猜测您的服务 class 使用了一些注入的 DAO 或存储库,这些 DAO 或存储库保留 API 密钥对。考虑为您的测试提供此类 DAO 的内存中实现 - class 不是在真实数据库中持久化对象,而是将所有对象存储在内部 ConcurrentMap<K,V>
中。多亏了这个,你仍然可以 运行 你的测试作为一个单元测试,你可以测试更新 API 密钥对 createNewKey
参数设置为 true
是否完全符合你的期望。或者,您可以编写一个带有 H2 数据库替换的集成测试,但这只会使您的测试 bootstrap 更长。选择权在你。
有一条规则值得记住 - 如果您的 class/component/functionality 等难以进行单元测试,则意味着做出了错误的设计选择。
备选方案:Spock 的 Spy
个对象
最后我特意提到了一件事。 Spock 支持所谓的 "spy" 对象,它们的行为类似于真实对象,但它们允许您对某些部分进行存根并将该对象视为模拟对象,例如调用计数。但即使是 Spock 作者也警告开发人员不要使用此功能:
(Think twice before using this feature. It might be better to change the design of the code under specification.)
Source: http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#spies
我不知道 Grails 是否有用于创建 Spy 而不是 Mock 的测试注释,但您始终可以 follow official documentation 并创建普通的 Spock 单元测试,将您的服务实例化为 Spy,然后您可以尝试计算调用次数。不过,我不建议这样做,只是提一下以作记录。
下面有一个方法非常简单。它调用另一种方法软删除 API 键,然后调用另一种方法创建一个新键并 returns 它。
测试也在下面,它只是检查这两个方法是否被正确调用。但是仍然在这两种方法上都出现 0 调用错误。是什么导致了这个问题?
AuthApiKeyPair updateApiKeyPair(AuthApiKeyPair apiKeyPair, Boolean createNewKey) {
AuthApiKeyPair newKeyPair
if (createNewKey) {
deleteApiKeyPair(apiKeyPair)
//The key will be created with the same info as the previous key.
newKeyPair = createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
}
newKeyPair
}
测试:
def "should soft delete key pair and create new one"() {
setup:
AuthApiKeyPair apiKeyPair = AuthApiKeyPair.build(acquirerId: 123, source: PaymentSource.BOARDING_API, label: 'Boarding API key')
when:
service.updateApiKeyPair(apiKeyPair, true)
then:
1 * service.deleteApiKeyPair(apiKeyPair)
1 * service.createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
}
如果您考虑您的测试,您会意识到在最佳情况下,它测试的是 Spock 的模拟机制,而不是您的业务代码。您没有向我们展示完整的 class 和您的测试规范,但是根据您的场景,我们可以假设您测试中的 service
只是一个模拟。如果这是真的,那么你不能指望这两个调用:
then:
1 * service.deleteApiKeyPair(apiKeyPair)
1 * service.createApiKeyPair(apiKeyPair.label, apiKeyPair.accountMode, apiKeyPair.source)
发生。仅仅是因为模拟 class 不执行真正的方法。
我强烈建议您测试一个真实的 class 而不是特定方法导致的调用类型,而是 调用特定方法的预期(和确定性)结果是什么 代替。您可以在 when
子句中对真实对象执行 service.updateApiKeyPair(apiKeyPair, true)
然后您可以检查是否创建了新的 API 密钥对(并保存在您使用的存储中)以及旧对是否创建了不存在了。与仅检查调用相比,这样的测试至少有一些好处:
- 您可以随时更改
service.updateApiKeyPair()
的实现,只要它产生相同的结果,您的测试仍然有用(因为测试不会像调用测试那样限制您的实现), - 你测试实际行为而不是模拟库 - 有这个 轶事说 Mockito 是数百万项目中经过最严格测试的库。
当然,它可能需要一些设计更改。我猜测您的服务 class 使用了一些注入的 DAO 或存储库,这些 DAO 或存储库保留 API 密钥对。考虑为您的测试提供此类 DAO 的内存中实现 - class 不是在真实数据库中持久化对象,而是将所有对象存储在内部 ConcurrentMap<K,V>
中。多亏了这个,你仍然可以 运行 你的测试作为一个单元测试,你可以测试更新 API 密钥对 createNewKey
参数设置为 true
是否完全符合你的期望。或者,您可以编写一个带有 H2 数据库替换的集成测试,但这只会使您的测试 bootstrap 更长。选择权在你。
有一条规则值得记住 - 如果您的 class/component/functionality 等难以进行单元测试,则意味着做出了错误的设计选择。
备选方案:Spock 的 Spy
个对象
最后我特意提到了一件事。 Spock 支持所谓的 "spy" 对象,它们的行为类似于真实对象,但它们允许您对某些部分进行存根并将该对象视为模拟对象,例如调用计数。但即使是 Spock 作者也警告开发人员不要使用此功能:
(Think twice before using this feature. It might be better to change the design of the code under specification.)
Source: http://spockframework.org/spock/docs/1.0/interaction_based_testing.html#spies
我不知道 Grails 是否有用于创建 Spy 而不是 Mock 的测试注释,但您始终可以 follow official documentation 并创建普通的 Spock 单元测试,将您的服务实例化为 Spy,然后您可以尝试计算调用次数。不过,我不建议这样做,只是提一下以作记录。