【问题标题】:How to use Mockito to check number of calls to a function without making Mockito make an extra call如何在不让 Mockito 进行额外调用的情况下使用 Mockito 检查对函数的调用次数
【发布时间】:2017-06-16 12:50:20
【问题描述】:

我们制作了自己的框架,可以轻松设置分析管道。 每次分析结束时,都会调用 finish()。 finish() 上传分析期间生成的文件。 为确保正确使用框架,我们检查了 finish() 是否没有被调用两次。

现在,我想测试是否为管道中的特定步骤调用了 finish()。 我通过在测试中调用以下代码来做到这一点:

verify(consumer).finish();

但显然,verify() 也调用了 finish(),所以会抛出异常并且测试失败。

现在,我的问题是:

  • 如何避免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();
    }
}

管道测试

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();
    }
}

【问题讨论】:

    标签: java unit-testing testing mockito


    【解决方案1】:

    测试框架都有其怪癖,但当您遇到此类问题时,第一步是评估您的类和测试设计。

    首先,我注意到 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
        }
    }
    

    这将验证在多次调用 finish() 时 Analysis 抛出 IllegalStateException 的隐含合同。您的问题有多种解决方案,其中大多数取决于验证这一点。

    接下来,带有 finalfinish() 方法的抽象 Finishable 类并不像看起来那么简单。由于 finishHelper 方法具有受保护的访问权限,因此包中的任何类仍然可以直接访问它。所以在你的例子中,如果 Pipeline 和 Analysis 在同一个包中,那么 Pipeline 可以直接调用 finishHelper。我猜这是实际完成代码被调用两次的最大风险。不小心让你的 IDE 自动完成完成助手有多容易?即使您的单元测试按您的意愿工作,它也无法捕捉到这一点。

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

    那么我们该怎么办呢?没有完美的答案,这真的取决于具体情况。

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

    您还可以将 Finishable 切换为接口并将当前类 AbstractFinishable 重命名为基本实现。然后将 Analysis 切换到扩展 Finishable 的接口,并创建一个扩展 AbstractFinishable 并实现 Analysis 的 ExampleAnalysis 类。然后 Pipeline 引用 Analysis 接口。我们必须这样做,否则它可以访问 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。正确地测试分析,你是对的。但是之前发布的草图是错误的。写。您对框架的建议,这是一个绝妙的主意,我会尝试一下。谢谢。
    【解决方案2】:

    我这样验证:verify(object, times(1)).doStuff();

    【讨论】:

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

    这个问题可以通过捕获框架的异常来解决,如下:

    @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() 的次数太少,verify 会按预期处理问题。

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

    否则,测试成功。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-06
      相关资源
      最近更新 更多