如何在单元测试中避免Thread.sleep?

时间:2016-03-29 11:44:05

标签: java multithreading unit-testing junit

让我们想象一下我应该测试以下方法:

@Autowired
private RoutingService routingservice;

public void methodToBeTested() {
    Object objectToRoute = initializeObjectToRoute();
    if (someConditions) {
         routingService.routeInOneWay(objectToRoute);
    } else {
         routingService.routeInAnotherWay(objectToRoute);
    }
}

在这种情况下RoutingService在单独的线程中运行,因此在它的构造函数中我们有以下内容:

Thread thread = new Thread(this);
thread.setDaemon(true);
thread.start();

问题是RoutingService改变了objectToRoute的状态,这正是我想要检查的,但这不会立即发生,因此测试失败。但是,如果我添加Thread.sleep(),那么它可以正常工作,但据我所知,这是不好的做法。

在这种情况下如何避免Thread.sleep()

6 个答案:

答案 0 :(得分:5)

如果您正在测试methodToBeTested,则只需模拟routingservice即可。您不应该测试methodToBeTested调用的任何方法。但是,听起来你想要测试RoutingService(你说“问题是RoutingService改变了objectToRoute的状态,这正是我要检查的内容”)。要测试RoutingService方法,您应该为这些方法编写单独的单元测试。

答案 1 :(得分:2)

您可以模拟objectToRoute来设置CompletableFuture的值,然后在断言中调用get。这将等到设置值之后再继续。然后设置超时@Test(timeout=5000),以防从未设置该值。

这样做的好处是测试不会等待超过必要的时间,并且由于时间太短而更难以失败,因为你可以使超时比正常时间大得多。

答案 2 :(得分:2)

我建议awaitility用于同步异步测试。例如,假设您有一个结果对象,该对象在一些线程操作后被设置,并且您想对其进行测试。您可以这样编写语句:

 await()
.atMost(100, TimeUnit.SECONDS)
.untilAsserted(() -> assertNotNull(resultObject.getResult()));

它最多等待100秒(),直到满足声明为止。例如,如果getResult()在0到100秒之间返回不为null的值,则执行将继续执行,而Thread.sleep会在给定的时间内保持执行,无论结果是否存在。

答案 3 :(得分:0)

这取决于。正如本格林在评论中所说,睡眠在测试中是危险的,因为它可以隐藏竞争条件。 知道整体设计是否包含这样的竞争条件,在路线准备好之前可以使用该服务。它确实如此,您应该在代码中修复它,例如通过测试 ready 条件,并在测试类中对其进行相同的测试。

如果您知道不可能发生这种情况,则应在测试类中的主代码中进行记录。那将是睡眠的完美理由。

(我认为这是针对集成测试的 - 对于单元测试模拟应该足够,就像你在其他答案中所说的那样)

答案 4 :(得分:0)

您可以在Junit测试用例中将值传递为零,而不是避免使用Thread.sleep()。

答案 5 :(得分:0)

使用Object.wait()

我们已经使用了一些异步过程,这些异步过程从目录或.ZIP存档中获取文件。我们在其他地方使用文件的内容,而异步文件则继续读取。

我们测试它们的方法是在通信对象上wait()(在我们的例子中是一个队列),因此只要有新文件,异步过程就会queue.notifyAll()准备使用并保持工作状态。另一方面,消费者将一次处理一个项目,直到队列为空,然后queue.wait()等待更多。我们确实使用了通过队列传递的特殊对象来表示没有更多要处理的项目。

在您的情况下,我想您要检查的对象objectToRoute,并不是真正在您要测试的方法内部创建的。您可以在测试内部wait()上,在您要测试的方法内部notifyAll()上,methodToBeTested.我知道这会在生产代码库中引入额外的代码行,但是没有人等待它,它应该不会造成伤害。最终结果如下:

public void methodToBeTested(Object objectToRoute) {
    if (someConditions) {
         routingService.routeInOneWay(objectToRoute);
    } else {
         routingService.routeInAnotherWay(objectToRoute);
    }
    synchronized(objectToRoute) {
        objectToRoute.notifyAll();
    }
}

在您的测试课程中,将出现类似以下内容:

@Test
public void testMethodToBeTested() throws InterruptedException {
    Object objectToRoute = initializeObjectToRoute();
    methodToBeTested(objectToRoute);
    synchronized (objectToRoute) {
        objectToRoute.wait();
    }
    verifyConditionsAfterRouting(objectToRoute);
}

我知道在这个简单的示例中没有太大的意义,因为示例系统不是多线程的。我假设在routeInOneWayrouteInAnotherWay方法中添加了多线程扭曲;因此,这些就是调用notifyAll()方法的那些。

下面是一些代码片段,它们是指出解决方案方向的示例代码。

在异步工作者端或生产者端:

while(files.hasNext(){
   queue.add(files.next());
   synchronized (outputQueue) {
       queue.notifyAll()
   }
}

在消费者方面:

while(!finished){
    while(!queue.isEmpty()){
        nextFile = queue.poll();
        if (nextFile.equals(NO_MORE_FILES_SIGNAL)) {
            finished = true;
            break;
        }
        doYourThingWith(nextFile);
    }
    if (!finished) {
        synchronized (outputQueue) {
            outputQueue.wait();
        }
    }
}