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

cqoc49vn  于 2023-10-20  发布在  其他
关注(0)|答案(7)|浏览(243)

让我们假设我有下面的方法需要测试:

@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()

efzxgjgh

efzxgjgh1#

如果你正在测试methodToBeTested方法的[Unit Test],你应该简单地模拟routingservice
您不应该测试methodToBeTested调用的任何方法。
然而,听起来你想测试RoutingService(你说“问题是RoutingService改变了objectToRoute的状态,这正是我想检查的”)。
要测试RoutingService方法,应该为这些方法编写单独的单元测试。

hzbexzde

hzbexzde2#

我建议使用awaitility来同步异步测试。例如,假设您有一个在某些线程操作后得到设置的结果对象,并且您想要测试它。你写这样的声明:

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

它等待最多100秒,,直到满足Assert。例如,如果getResult()在0-100秒之间返回非空值,则执行将继续,而不像Thread.sleep那样,无论结果是否存在,都会在给定时间内保持执行。

qojgxg4l

qojgxg4l3#

您可以模拟objectToRoute来设置CompletableFuture的值,然后在Assert中调用get。这将等待直到值被设置后才继续。然后设置一个超时@Test(timeout=5000),以防从未设置该值。
这样做的好处是,测试不会等待超过必要的时间,并且由于时间太短而失败的可能性更小,因为您可以使超时时间比正常情况大得多。

66bbxpm5

66bbxpm54#

这要看情况。正如本绿色在他的评论中所说,睡眠在测试中是危险的,因为它可以隐藏一个竞争条件。知道整体设计是否包含这样的竞态条件,即服务可能在路由准备好之前被使用。如果是这样,你应该在代码中修复它,例如通过测试一个 ready 条件,并在你的测试类中测试它。
如果你知道它不会发生,你应该在你的主代码和测试类中记录它。这将是一个完美的理由睡觉。
(我假设这是一个集成测试-对于单元测试 * 模拟 * 应该足够了,因为你在其他答案中说过)

x759pob2

x759pob25#

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

ohtdti5x6#

从java 9开始,你可以使用CompleteableFuturedelayedExecutor方法来避免Thread.sleep和它的SonarLint警告。
这是基本模式,根据您的需求进行调整:

import static java.util.concurrent.CompletableFuture.delayedExecutor;
import static java.util.concurrent.CompletableFuture.runAsync;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

@Test
void yourTest() {

// [...]

  runAsync(() -> {}, delayedExecutor(100, MILLISECONDS)).join();
}
s5a0g9ez

s5a0g9ez7#

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

相关问题