Java 如何避免单元测试中的Thread.sleep?
让我们假设我有以下应该测试的方法:Java 如何避免单元测试中的Thread.sleep?,java,multithreading,unit-testing,junit,Java,Multithreading,Unit Testing,Junit,让我们假设我有以下应该测试的方法: @Autowired private RoutingService routingservice; public void methodToBeTested() { Object objectToRoute = initializeObjectToRoute(); if (someConditions) { routingService.routeInOneWay(objectToRoute); } else {
@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()
?如果要测试方法methodToBeTested
,则只需模拟routingservice
您不应该测试任何
methodToBeTested
调用的方法
但是,听起来您想测试RoutingService
(您说“问题是RoutingService
更改了objectToRoute
的状态,这正是我想要检查的”)
要测试
RoutingService
方法,您应该为这些方法编写单独的单元测试。您可以模拟objectToRoute
来设置CompletableFuture
的值,然后在断言中调用get
。这将等待设置该值后再继续。然后设置超时@Test(timeout=5000)
,以防从未设置该值
这样做的好处是,测试的等待时间不会超过需要的时间,而且由于时间太短,测试很难失败,因为您可以使超时时间比正常时间长得多。这取决于具体情况。正如本·格林在评论中所说,睡眠在测试中是危险的,因为它可以隐藏比赛状态您知道总体设计是否包含这样的竞速条件,即在路线准备就绪之前可以使用该服务。如果确实如此,您应该在代码中修复它,例如通过测试就绪条件,并在您的测试类中进行相同的测试 如果您知道它不可能发生,那么应该在主代码和测试类中记录它。那将是睡眠的完美理由
(我假设这是一个集成测试-对于单元测试,mock应该足够了,正如您在其他答案中所说的)在Junit测试用例中,您可以将值传递为零,而不是避免使用Thread.sleep()。我建议同步异步测试。例如,假设您有一个在一些线程操作之后设置的结果对象,并且您想要测试它。你写这样的声明:
await()
.atMost(100, TimeUnit.SECONDS)
.untilAsserted(() -> assertNotNull(resultObject.getResult()));
它最多等待100秒,或,直到满足断言。例如,如果getResult()在0-100秒内返回的值不为null,则执行将继续,而不像Thread.sleep那样,它会在给定的时间内保持执行,无论结果是否存在。使用Object.wait()
我们使用过一些异步进程,这些进程从目录或.ZIP归档文件中获取文件。我们在其他地方使用文件的内容,而异步文件继续读取
我们测试它们的方法是在通信对象上执行wait()
,在我们的例子中,它是一个队列-,因此异步进程将Queue.notifyAll()
,只要有新文件准备好使用并继续工作。在另一端,消费者将一次处理一个项目,直到队列为空,然后queue.wait()
等待更多。我们确实使用一个通过队列传递的特殊对象来表示没有更多的项目要处理
在您的例子中,我假设您要检查的对象,objectToRoute,
并不是在您要测试的方法中真正创建的。您可以在测试内部对其执行wait()
,在要测试的方法内部对其执行notifyAll()
,methodtobetest。
我知道这会在您的产品代码库中引入额外的代码行,但如果没有人在等待,结果应该是无害的。结果会是:
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);
}
在你的测试课上会有这样的内容:
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);
}
我知道在这个简单的示例中没有太多意义,因为示例系统不是多线程的。我假设在routeInOneWay
和routeInonetherway
方法中添加了多线程扭曲;因此,这些是要调用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();
}
}
}
在单元测试中使用
Thread.sleep
并不是不好的做法,因为您所做的只是模拟时间的流逝,这对于某些单元测试是必要的。人们之所以说使用Thread.sleep
是一种不好的做法,是因为它有时被用作修复竞争条件的尝试。您是在测试中还是在源代码中只使用线程.sleep
?当您测试线程代码时,如何确保您的单元测试没有问题?它们总是以确定性的方式执行。我认为模仿服务类是一个更好的观点,我只在测试中使用睡眠,但发现有很多地方人们提到在单元测试中睡眠通常不是那么好。例如,Sonarint插件也抱怨说这是违规行为,并给出以下描述:“在测试中使用Thread.sleep通常是个坏主意。它会创建脆弱的测试,根据环境(“在我的机器上传递!”)或负载,这些测试可能会不可预测地失败。”同意Ben的说法。我们在Maven构建期间运行JUnit测试,当前线程不会等待多线程进程,比如JMS交互和外部性能监视。我们使用Thread.sleep来确保这些进程在线程被终止之前完成