在我的理解中,代码测试就是测试结果是否正确,就像计算器一样,我需要编写一个测试用例来验证1 + 1的结果是否为2。
但是我已经阅读了许多有关验证方法调用次数的测试案例。我对此很困惑。最好的例子就是我在 Spring in Action
中看到的内容:public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
quest.embark();
}
}
public class BraveKnightTest {
@Test
public void knightShouldEmbarkOnQuest() {
Quest mockQuest = mock(Quest.class);
BraveKnight knight = new BraveKnight(mockQuest);
knight.embarkOnQuest();
verify(mockQuest, times(1)).embark();
}
}
我真的不知道为什么他们需要一次验证embark()
函数。您不认为embark()
肯定会在调用embarkOnQuest()
之后被调用吗?否则会发生一些错误,我会在日志中注意到错误消息,其中显示了错误行号,可以帮助我快速找到错误的代码。
那么像上面那样进行验证有什么意义呢?
答案 0 :(得分:9)
需求很简单:验证是否进行了正确的调用。在某些情况下,不应进行方法调用,而在其他情况下,应比默认方法多或少发生方法调用。
请考虑对embarkOnQuest
进行以下修改:
public void embarkOnQuest() {
quest.embark();
quest.embarkAgain();
}
假设您正在测试quest.embark()
的错误情况:
@Test
public void knightShouldEmbarkOnQuest() {
Quest mockQuest = mock(Quest.class);
Mockito.doThrow(RuntimeException.class).when(mockQuest).embark();
...
}
在这种情况下,您要确保未调用quest.embarkAgain
(或被调用0次):
verify(mockQuest, times(0)).embarkAgain(); //or verifyZeroInteractions
当然,这是另一个简单的例子。可以添加许多其他示例:
答案 1 :(得分:5)
好问题。您提出了一个很好的观点,即当您仅检查结果时,嘲笑可能会过于circuit回。但是,在某些情况下确实会导致更强大的测试。
例如,如果某个方法需要调用外部API,则仅测试结果就存在几个问题:
要解决您的子问题:
您不认为在调用embarkOnQuest()之后肯定会调用embark()吗?
测试也很有价值,它可以让您重构而不必担心发生问题。这是显而易见的,是的。在6个月内会很明显吗?
答案 2 :(得分:5)
考虑以下代码:
public void saveFooIfFlagTrue(Foo foo, boolean flag) {
if (flag) {
fooRepository.save(foo);
}
}
如果您不检查fooRepository.save()
的调用次数,那么如何知道此方法是否正在执行您想要的操作?
这适用于其他void方法。如果没有返回方法,因此也没有响应来进行验证,则检查是否调用其他方法是验证该方法行为正确的一种好方法。
答案 3 :(得分:2)
我真的不知道为什么他们需要验证embark() 函数被调用一次
在特定次数上验证对模拟的调用是Mockito调用Mockito.verify()
时工作的标准方式。
实际上是这样:
verify(mockQuest, times(1)).embark();
只是一种冗长的书写方式:
verify(mockQuest).embark();
通常,您需要对模拟游戏中的单个呼叫进行验证。
在某些不常见的情况下,您可能需要验证某个方法被调用特定次数(一次以上)。
但是您要避免使用如此具体的验证。
实际上,您甚至希望使用尽可能少的验证。
如果您需要使用验证,并且除了模拟的调用次数外,它通常还意味着两件事:模拟的依赖关系与以下类的耦合太多
测试和/或被测方法执行太多的单一任务,仅产生副作用。
因此,该测试不是必需的直接可读性和可维护性。就像您在验证调用中编写了模拟流程一样。
因此,由于它检查调用详细信息而不是整体逻辑和状态,因此也使测试更加脆弱。
在大多数情况下,重构是一种补救方法,并且取消了指定多个调用的要求。
我并没有说它不是必需的,而只是因为它恰好是被测类的一个不错的选择而使用它。