深入探讨 Awaitility:异步测试的神器
什么是 Awaitility?
Awaitility 是一个开源的 Java 测试库,专门用于处理异步操作的等待。它允许开发者以一种清晰、易读的方式等待某个条件的成立,而不是使用繁琐的线程睡眠或轮询。通过 Awaitility,我们可以更容易地编写健壮且易于维护的测试代码。
用法
Maven 依赖
要在项目中使用 Awaitility,首先需要在 Maven 项目中添加相关依赖:
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.2</version>
<scope>test</scope>
</dependency>
配置 Awaitility
在使用 Awaitility 之前,可以通过配置类来调整默认的行为。例如,你可以改变默认的超时时间、轮询间隔以及其他配置项。
// 配置Awaitility的全局设置
Awaitility.setDefaultPollDelay(Duration.ofMillis(200));
Awaitility.setDefaultPollInterval(Duration.ofMillis(200));
常用方法
atMost
atMost
方法用于定义在条件被满足前,Awaitility 最多等待的时间。如果在这个时间内条件没有得到满足,Awaitility 将抛出 ConditionTimeoutException
。
import org.awaitility.Awaitility;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;
@Test
public void testAtMost() {
AtomicBoolean conditionMet = new AtomicBoolean(false);
// 模拟异步操作
new Thread(() -> {
try {
Thread.sleep(1000);
conditionMet.set(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 等待5秒,直到conditionMet为true , 如果5秒后conditionMet还是false,则测试失败
Awaitility.await().atMost(Duration.ofSeconds(5)).until(conditionMet::get);
}
atLeast
atLeast
方法定义了 Awaitility 在开始检查条件前至少需要等待的时间。这在需要确保某个操作至少运行了一定时间的情况下很有用。
import org.awaitility.Awaitility;
@Test
public void testAtLeast() {
AtomicBoolean conditionMet = new AtomicBoolean(true);
// 模拟异步操作
new Thread(() -> {
try {
Thread.sleep(1000);
conditionMet.set(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 等待2秒后再开始检查,1秒后conditionMet为false,则下面这个测试成功。
Awaitility.await().atLeast(Duration.ofSeconds(2)).until(() -> !conditionMet.get());
}
forever
虽然 forever
方法允许无限期地等待某个条件的满足,但实际上很少使用这种方式,因为这样会导致测试无休止地等待。通常我们会结合 atMost
来设置一个合理的超时时间。
import org.awaitility.Awaitility;
@Test
public void testForever() {
AtomicBoolean conditionMet = new AtomicBoolean(false);
// 启动一个新的线程模拟异步操作
new Thread(() -> {
try {
Thread.sleep(10000);
conditionMet.set(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 调用await().forever()方法,持续等待直到条件满足
Awaitility.await().forever().until(conditionMet::get);
}
pollInterval
pollInterval
方法允许设置固定的轮询间隔,这在某些情况下可能会比默认的递增轮询策略更有优势。
import org.awaitility.Awaitility;
import java.time.Duration;
@Test
public void testPollInterval() {
AtomicBoolean flag = new AtomicBoolean(false);
// 开启一个新的线程,在几秒钟后设置标志位为true
new Thread(() -> {
try {
Thread.sleep(3000); // 等待3秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
flag.set(true);
}).start();
// 使用固定轮询间隔等待标志位变为true
Awaitility.await()
.pollInterval(Duration.ofMillis(500)) // 设置轮询间隔为500毫秒
.atMost(Duration.ofSeconds(10)) // 设置最大等待时间为10秒
.until(flag::get); // 等待条件flag.get()为true
}
默认行为
如果不设置pollInterval
,默认情况下,Awaitility
使用一个递增的轮询策略。这意味着在等待条件满足时,最初会有一个较短的轮询间隔,并且随着时间的推移逐渐增加轮询间隔的长度,直到达到最大超时时间。这样的设计是为了快速检测到条件的变化,同时避免在长时间等待时过于频繁地检查条件。
默认的轮询间隔通常是非常短的,可能只有几十毫秒,而随着等待时间的延长,轮询间隔会逐渐增加。这种策略有助于减少CPU负载,特别是在等待较长的时间时。
ignoreExceptions
ignoreExceptions
方法允许在等待某个条件变为真(true
)的过程中忽略某些类型的异常。这对于处理那些可能因为暂时性的故障而抛出的异常特别有用,比如网络抖动、临时的服务不可达等,这些情况通常不会导致整个测试失败。
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
@Test
public void testIgnoreExceptions() {
// 使用ignoreExceptionOfType忽略NullPointerException,当发生了NullPointerException时;不会抛出异常,会继续判断条件
Awaitility.await()
.ignoreExceptionOfType(NullPointerException.class)
.until(() -> methodThatMayThrowNPE());
System.out.println("Method no longer throws NullPointerException.");
}
private boolean methodThatMayThrowNPE() {
// 模拟可能抛出NullPointerException的情况
try {
if (Math.random() < 0.5) { // 50%的概率抛出NullPointerException
String nullString = null;
return nullString.length() > 0;
} else {
return true; // 不抛出异常的情况
}
} catch (NullPointerException e) {
return false;
}
}
untilAsserted
.untilAsserted()
是一个非常有用的方法,它用于在给定的时间内执行断言,直到断言成功或者超时为止。这在测试异步操作时特别有用,因为它可以确保在异步操作完成之前不会继续执行测试,从而避免了由于异步操作尚未完成而导致的假阴性测试失败。
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@Test
public void testUntilAsserted() {
// 使用untilAsserted方法,直到断言成功
Awaitility.await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> {
// 断言条件
assertEquals(1, 1);
assertNotEquals(1, 2);
});
}
以上就是 Awaitility 的一些基本用法和配置选项。通过这些配置,我们可以更加灵活地处理各种异步场景,使我们的测试代码更加健壮和可靠。在实际应用中,可以根据具体的需求选择合适的配置组合,以达到最佳的效果。
文档:https://github.com/awaitility/awaitility/wiki/Usage
GitHub:https://github.com/awaitility/awaitility
欢迎关注我的公众号“编程与架构”,原创技术文章第一时间推送。