测试一个非阻塞方法(异步测试)

5
我有一个非常简单的Check类,其中有一个阻塞的waitForCondition()方法。该方法是阻塞的。我想为这个方法创建一些单元测试。首先,当条件满足时,该方法应该返回。其次,当被中断时,该方法应该返回。
在内部,Check类具有一个ArrayBlockingQueue并调用其take()方法,因此我的测试实际上是关于正确编写条件逻辑(应该如此)的问题。 在应用程序中,Check类的数据通过另一个线程通过InputData方法提供。 InputData方法在传入数据上执行逻辑,并在满足条件时在ArrayBlockingQueue中放置一个虚拟对象。这应该导致waitForCondition()返回。
所以我的第一个想法是通过模拟测试InputData并检查当满足条件时是否将虚拟对象添加到队列中。这将需要更改类的设计,因为队列是一个私有数据成员(除非可能模拟私有数据)。当条件满足时,InputData不再直接添加到队列中,而是必须调用某个可模拟的东西。

但是,如果InputData正常工作,检查waitForCondition()方法本身就会出现问题。它真的很简单:

try {
        myArrayBlockingQueue.take();
        return true;
    } catch (InterruptedException ex) {
        return false;
    }

所以我在想,这是否值得想象中的麻烦:一个测试用例创建一个带有Check 的另一个线程,调用其waitForCondition(),然后在完成时返回一些内容。也许可以使用Executor服务。模糊的部分是如何同步assertTrue(...)。我发现了这篇异步测试文章 article on asynchronous testing,看起来它可能会起作用。
问题总结:
  1. 我应该更改设计以测试InputData()中的逻辑,如果是这样,应该怎么做?
  2. 只要测试了InputData(),就可以省略对waitForCondition()的测试吗?
  3. 还是直接执行需要执行的操作(一种有点复杂的单元测试),并直接测试waitForCondition()更好?
3个回答

3
如果你在Check类的构造函数中注入ArrayBlockingQueue的实例,那么你的测试可以在测试过程中注入适当的值。
然后,您可以在100毫秒内运行带有超时的单元测试,如果测试没有返回,则测试失败。请注意,HTML标记将被保留。

有趣的想法。注入的队列可以在某些测试条件下被模拟为抛出 InterruptedException,等待很长时间等。 - user949300
好主意。我为了测试目的向Check类添加了一个构造函数,按照你建议的方式注入队列。然后,通过直接设置和读取实际队列(而不是模拟),我能够测试所有公共方法。只需要使用一个Mock来测试InterruptedException。使用Mockito:when(queueMock.take()).thenThrow(new InterruptedException()); 所以,在这种情况下,我们可以完全避免任何异步/定时问题进行测试。 - Pete
如果阻塞对象的数量较少且您的代码结构可以注入它们,那么这是一个很好的解决方案。 - user949300

1
谢谢提供这个好链接!我遇到了一些类似的问题,也许那个链接比我做的更好。(我也很想看看其他回答这个问题的方法 - 这是一个好问题)
如果你不介意改变(至少是暂时)你实际的代码(是的,这不是通常的单元测试实践!),你可以做一些我称之为"错误注入"的事情。
在我的实现中,你创建一个从属性(或Map)读取信息的类,在特定的唯一点上“做一些有趣的事情”。例如,你的属性可能会说:
myClass.myMethod.blockingQueueTake = interrupt:
myClass.myLongCalculation = throw: java.lang.ArithmeticException(Failed to converge)

在您的代码中,您可以添加测试行,例如在queue.take()之前添加。
TestSimulator.doCommand("myClass.myMethod.blockingQueueTake");

优点是一切都发生在实际的代码中,而不是一些可能非常复杂的模拟代码。(在我的情况下,软件比较老,没有为单元测试编写/设计,因此制作模拟非常困难。) 缺点是你可能想要在之后删除或注释掉代码。所以它并不是一个持续集成类型的单元测试,它更像是一次非常严肃的现实调试。 所以,我承认,它远远不理想,但是它确实为我找到了一堆错误!

0
你也可以使用一个“测试运行器”类来循环运行断言。循环将在try/catch中运行断言。异常处理程序将简单地尝试再次运行断言,直到超时已过期。我最近写了一篇关于这种技术的博客文章。示例是用Groovy编写的,但这个概念应该很容易适应Java。

http://www.greenmoonsoftware.com/2013/08/asynchronous-functional-testing/


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