如何使用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();
测试框架都有他们的怪癖,但是当你遇到这样的问题时,第一步就是评估你的类和测试设计。
首先,我注意到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。还有其他方式,这些只是想法。您应该充分了解项目的具体情况,以确定最佳路线。
这些是一些尖锐的意见。 不幸的是,wrt。正确地测试分析,你是对的。但之前发布的草图是错误的。 Wrt。你对框架的建议,我会尝试一下,这是一个绝妙的主意。谢谢。 –
其他人发布了完全相同的建议。此解决方案不起作用。仍然会抛出异常。在上面的代码中尝试一下。 –