验证函数在Mockito中被调用的次数有什么意义?

时间:2018-09-07 12:09:06

标签: java unit-testing testing mockito

在我的理解中,代码测试就是测试结果是否正确,就像计算器一样,我需要编写一个测试用例来验证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()之后被调用吗?否则会发生一些错误,我会在日志中注意到错误消息,其中显示了错误行号,可以帮助我快速找到错误的代码。

那么像上面那样进行验证有什么意义呢?

4 个答案:

答案 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,则仅测试结果就存在几个问题:

  • 网络I / O速度很慢。如果您有很多这样的检查,它将减慢您的测试用例
  • 任何这样的往返都必须依靠发出请求的代码,API和解释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(); 

通常,您需要对模拟游戏中的单个呼叫进行验证
在某些不常见的情况下,您可能需要验证某个方法被调用特定次数(一次以上)。
但是您要避免使用如此具体的验证。
实际上,您甚至希望使用尽可能少的验证。

如果您需要使用验证,并且除了模拟的调用次数外,它通常还意味着两件事:模拟的依赖关系与以下类的耦合太多 测试和/或被测方法执行太多的单一任务,仅产生副作用。
因此,该测试不是必需的直接可读性和可维护性。就像您在验证调用中编写了模拟流程一样。
因此,由于它检查调用详细信息而不是整体逻辑和状态,因此也使测试更加脆弱。
在大多数情况下,重构是一种补救方法,并且取消了指定多个调用的要求。

我并没有说它不是必需的,而只是因为它恰好是被测类的一个不错的选择而使用它。