Awaitility 是一个用于 Java 的小型领域特定语言(DSL),主要用于简化和管理异步操作的同步问题。它的主要作用包括:
等待异步操作完成:在测试异步代码时,Awaitility 可以帮助你等待某个条件变为真,而不需要使用复杂的线程管理或轮询机制。
提高测试的可读性:通过使用流畅的 API,Awaitility 使得测试代码更易于阅读和理解。
减少测试中的线程问题:避免在测试中显式地使用 Thread.sleep()
,从而减少不必要的等待时间和线程问题。
灵活的超时和轮询间隔:允许你设置自定义的超时时间和轮询间隔,以便更好地控制等待条件的检查频率。
总之,Awaitility 使得在测试异步操作时更加简单和直观,特别是在需要等待某个条件满足的情况下。
一个使用 Awaitility 的简单示例,演示如何等待异步操作完成。假设我们有一个异步任务,该任务在后台线程中更新一个标志,我们希望在测试中等待这个标志变为 true
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Java-demo</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Awaitility</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- Awaitility dependency -->
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.0</version>
</dependency>
<!-- JUnit dependency for testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
异步任务:startAsyncTask
方法启动一个异步任务,该任务在 5秒后将 flag
设置为 true
。
Awaitility 使用:在 main
方法中,我们使用 Awaitility 的 await()
方法来等待 flag
变为 true
。我们设置了一个最大等待时间为 5 秒。
条件检查:until(example::isFlag)
表示我们等待 example.isFlag()
返回 true
。
package com.et;
import org.awaitility.Awaitility;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AwaitilityExample {
private volatile boolean flag = false;
public void startAsyncTask() {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
try {
// mock async
Thread.sleep(5000);
flag = true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
executor.shutdown();
}
public boolean isFlag() {
return flag;
}
public static void main(String[] args) {
AwaitilityExample example = new AwaitilityExample();
example.startAsyncTask();
// use Awaitility to wait flag for true
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.until(example::isFlag);
System.out.println("Flag is now true!");
}
}
@Test
public void testAsynchronousNormal() {
AwaitilityExample example = new AwaitilityExample();
example.startAsyncTask();
try {
// Default timeout is 10 seconds. If the condition is not met within this period, a ConditionTimeoutException is thrown
Awaitility.await().until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return example.isFlag();
}
});
} catch (Exception e) {
Assertions.fail("Run exception: " + e.getMessage() + ", error: " + e.getStackTrace()[0].toString());
}
}
@Test
public void testAsynchronousAtMost() {
AwaitilityExample example = new AwaitilityExample();
example.startAsyncTask();
try {
// Specify a timeout of 3 seconds. If the condition is not met within this period, a ConditionTimeoutException is thrown
Awaitility.await().atMost(3, SECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return example.isFlag();
}
});
} catch (Exception e) {
Assertions.fail("Run exception: " + e.getMessage() + ", error: " + e.getStackTrace()[0].toString());
}
}
@Test
public void testAsynchronousAtLeast() {
AwaitilityExample example = new AwaitilityExample();
example.startAsyncTask();
try {
// Specify at least 1 second and at most 3 seconds. If the condition is not met within this period, a ConditionTimeoutException is thrown
Awaitility.await().atLeast(1, SECONDS).and().atMost(3, SECONDS).until(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return example.isFlag();
}
});
} catch (Exception e) {
Assertions.fail("Run exception: " + e.getMessage() + ", error: " + e.getStackTrace()[0].toString());
}
}
@Test
public void testAsynchronousPoll() {
AwaitilityExample example = new AwaitilityExample();
example.startAsyncTask();
try {
// Polling query, pollInterval specifies how often to poll, pollDelay specifies the delay between each poll
Awaitility.with().pollInterval(ONE_HUNDRED_MILLISECONDS).and().with().pollDelay(50, MILLISECONDS).await("count is greater 3").until(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return example.isFlag();
}
});
} catch (Exception e) {
Assertions.fail("Run exception: " + e.getMessage() + ", error: " + e.getStackTrace()[0].toString());
}
}
@Test
public void testAsynchronousFibonacciPoll() {
AwaitilityExample example = new AwaitilityExample();
example.startAsyncTask();
try {
// Use Fibonacci numbers as the interval: 1, 1, 2, 3, 5, 8,..., default unit is milliseconds
Awaitility.with().pollInterval(fibonacci(SECONDS)).await("count is greater 3").until(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return example.isFlag();
}
});
} catch (Exception e) {
Assertions.fail("Run exception: " + e.getMessage() + ", error: " + e.getStackTrace()[0].toString());
}
}