如何对回调逻辑进行单元测试?

15

更完整的问题是,假设一个依赖项期望接受回调函数作为参数,那么我该如何编写一个单元测试来覆盖回调逻辑 并且 仍然能够模拟该依赖项?

public class DoStuff {
    public void runThis(Runnable callback) {
        // call callback
    }
}

public class ClassUnderTest {
    private DoStuff stuffToDo;

    public void methodUnderTest() {
        this.stuffToDo.runThis(/*a runnable with some logic*/)
    }
}

在上面的例子中,我会模拟stuffToDo,因为我应该验证方法调用的调用和模拟输出。然而,模拟runThis将导致回调逻辑未受到测试。此外,回调逻辑似乎应该是私有的,因此我不希望直接测试它;也许这是我的误解。

由于回调被广泛使用,我期望有一种常见的方法来测试它们,但我还没有找到。


回调函数只需要在这种情况下进行测试吗?如果是这样,为什么不为您计划传递的各种回调编写测试,以表达您对其结果的期望?我有什么遗漏吗? - Jonah
你尝试过像这个例子中使用CountDownLatch吗:https://dev59.com/N3E95IYBdhLWcg3watM3#3802487 - Baker
5个回答

12

在单个测试中,你无法做到这一点。如果你对某些内容进行了模拟,它就被称为mocked,这意味着它只能验证参数并模拟返回值(你在测试中配置的)。

事实上,模拟的整个目的是为了在隔离环境中测试使用模拟的部分。如果你想对DoStuff进行单元测试,你不想担心使用一些可能工作或可能不工作的回调实现。你使用模拟回调,以便你不必担心它。

你仍然可以通过对回调代码进行隔离测试和对回调用户进行隔离测试(使用模拟回调),以及通过使用完全配置的组件进行集成测试来获得良好的测试结果。


1

在这里,基本上您想要测试类 DoStuff。这意味着您需要单元测试 DoStuff 中的所有方法,对吧?那么在这种情况下,您需要做的是,而不是模拟 stuffToDo 本身,而是将模拟的 Runnable 注入到 stuffToDo 中。然后检查您的可运行对象是否成功执行。

但是,如果您在该类中还有其他功能,则可能需要进行单独的测试并将其模拟出来。


1
我想测试一个依赖于DoStuff的ClassUnderTest。 - Andrew White

0
如果您正在使用EasyMock,您可以使用andStubAnswer来调用可运行对象。
doSomethingMock.runThis(runnable);
expectLastCall().andStubAnswer(new IAnserable<Void>() {
   Runnable runnable = (Runnable)getCurrentArguments()[0];
   runnable.run();
   return null;
});

我想其他的模拟框架也包含类似的东西。


0
在你的特定情况下,听起来像是你已经测试过DoStuff(或者不再关心它是否被模拟),现在正在专门对你设计的特定Runnable进行单元测试。在这种情况下,callback听起来正是你想要测试的内容,就像有人可能想要直接单元测试数据库策略或内存策略一样。
如果这就是你所尝试的,那么你可以在黑盒中测试它,尽可能地通过ClassUnderTest进行测试。或者你可以在你特定的Runnable上创建一个测试工具。如果你要发布这个代码,并且不想让你的测试工具可达,你可以将测试工具方法设置为私有,并将其详细信息与你的单元测试程序集共享。
点击这里了解如何制作友元程序集。我通常会签署我的单元测试代码,这样我就不必操纵命令行编译器了。我想这取决于你的构建环境。

不,ClassUnderTest是要被测试的类。我正在模拟DoStuff。 - Andrew White
我修改了类名以反映这一点。这不会影响答案。 - Michael Hays

0

那么,假如我们创建一个虚假的 DoStuff 方法,它无条件地调用 Runnable 接口呢?

这样,你就可以检测效果 - 如果回调函数被执行,应该观察到哪些变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接