如何使用Mockito检查呼叫功能的数量,而不使Mockito额外呼叫

问题描述:

我们制作了自己的框架,可以轻松设置分析管道。每次分析结束时,调用finish()。 finish()上传分析过程中生成的文件。 为了确保框架得到正确使用,我们检查了finish()没有被调用两次。如何使用Mockito检查呼叫功能的数量,而不使Mockito额外呼叫

现在,我想测试finish()被调用的管道中的特定步骤。 我做到这一点通过调用在我的测试如下:

verify(consumer).finish(); 

但很显然,验证()还调用完成(),所以抛出一个异常并导致测试失败。

现在,我的问题是:

  • 如何避免说完()被调用两次?

编辑

问题的快速设置:

分析

package mockitoTwice; 

public class Analysis extends Finishable { 
    @Override 
    public void finishHelper() { 
     System.out.println("Calling finishHelper()"); 
    } 
} 

最终处理

package mockitoTwice; 

public abstract class Finishable { 
    protected boolean finished = false; 

    public final void finish() { 
     System.out.println("Calling finish()"); 
     if (finished) { 
      throw new IllegalStateException(); 
     } 
     finished = true; 
     finishHelper(); 
    } 

    public abstract void finishHelper(); 
} 

管道

package mockitoTwice; 

public class Pipeline { 
    private Analysis analysis; 

    public Pipeline(Analysis analysis) { 
     this.analysis = analysis; 
    } 

    public void runAnalyses() { 
     analysis.finish(); 
    } 
} 

PipelineTest

package mockitoTwice; 

import static org.mockito.Mockito.mock; 
import static org.mockito.Mockito.times; 
import static org.mockito.Mockito.verify; 

import org.junit.Test; 

public class PipelineTest { 
    @Test 
    public void test() { 
     Analysis analysis = mock(Analysis.class); 
     Pipeline pipeline = new Pipeline(analysis); 
     pipeline.runAnalyses(); 
     verify(analysis).finish(); 
    } 
} 

这个问题可以通过捕捉框架之外如下解决:

@Rule 
public ExpectedException exception; 

@Test 
public void test() { 
    Analysis analysis = mock(Analysis.class); 
    Pipeline pipeline = new Pipeline(analysis); 
    pipeline.runAnalyses(); 
    exception.expect(IllegalStateException.class); 
    verify(analysis).finish(); 
} 

如果finish()被调用的次数太少,那么验证将按照预期处理问题。

如果finish()调用次数过多,则会在pipeline.runAnalyses()上调用该异常。

否则,测试成功。

我确认它是这样的:verify(object, times(1)).doStuff();

+0

其他人发布了完全相同的建议。此解决方案不起作用。仍然会抛出异常。在上面的代码中尝试一下。 –

测试框架都有他们的怪癖,但是当你遇到这样的问题时,第一步就是评估你的类和测试设计。

首先,我注意到AnalysisTest并没有真正测试Analysis类。它正在嘲笑分析并实际测试Pipeline类。用于分析的正确测试将是这个样子:

@Test 
public void testFinishTwice() { 

    Analysis analysis = new Analysis(); 

    try { 
     analysis.finish(); 
     analysis.finish(); 
     Assert.fail(); 
    } catch (IllegalStateException ex) { 
     // Optionally assert something about ex 
    } 
} 

这将验证分析抛出IllegalStateException异常时结束()被调用一次以上的隐含合同。有多种解决方案可以解决您的问题,其中大部分都取决于验证此问题。

接下来,具有final finish()方法的抽象Finishable类并不像看起来那么简单。由于finishHelper方法保护了访问,因此它仍然可以直接访问包中的任何类。因此,在您的示例中,如果Pipeline和Analysis位于同一个包中,则Pipeline可以直接调用finishHelper。我猜想这是实际完成代码被调用两次的最大风险。意外地让你的IDE自动完成以完成帮助是多么容易?即使你的单元测试按你想要的方式工作,它也无法解决这个问题。

现在我已经解决了这个问题,我们可以找到问题的根源。完成方法被标记为final,所以Mockito不能覆盖它。通常,Mockito会为它创建一个存根方法,但这里必须使用Finishable中的原始代码。完成调用时,真正的模拟甚至不会打印“调用完成()”。由于它被原始实现所困住,真正的完成方法被流水线调用,然后再被验证(分析).finish();调用。

那么我们该怎么做呢?没有完美的答案,这取决于情况的细节。

最简单的方法是将final关键字放在finish方法上。那么你只需要确保Analysis和Pipeline不在同一个包中。您所写的测试可以确保并且管道调用只需完成一次。我建议的测试可以保证在Analysis上第二次完成时会有异常。即使它会覆盖完成,也会发生这种情况。你仍然可以滥用它,但是你必须有意地走出去做。

您也可以将Finishable切换到接口并将当前类AbstractFinishable重命名为基本实现。然后将Analysis转换为扩展Finishable的接口,并创建一个扩展AbstractFinishable并实现Analysis的ExampleAnalysis类。然后管道引用分析界面。我们必须这样做,因为否则它可以访问finishHelper,我们又回到了我们开始的地方。这里是代码草图:

public interface Finishable { 
    public void finish(); 
} 

public abstract class AbstractFinishable implements Finishable { 
    // your current Finishable class with final finish method goes here                             
} 

public interface Analysis extends Finishable { 
    // Other analysis methods that Pipeline needs go here                           
} 

public ExampleAnalysis extends AbstractFinishable implements Analysis { 
    // Implementations of Analysis methods go here                            
} 

所以这是一种方法来做到这一点。它本质上是将要编码的类切换到它们的依赖关系的接口,而不是具体的类实现。这通常更容易模拟和测试。您也可以使用委托模式,并在ExampleAnalysis上放置一个Finishable,而不是扩展AbstractFinishable。还有其他方式,这些只是想法。您应该充分了解项目的具体情况,以确定最佳路线。

+0

这些是一些尖锐的意见。 不幸的是,wrt。正确地测试分析,你是对的。但之前发布的草图是错误的。 Wrt。你对框架的建议,我会尝试一下,这是一个绝妙的主意。谢谢。 –