Awaitility用户指导
Scala文档请点击这里here。Groovy文档请点击这里here。
1 静态导入
为了高效地使用Awaitility,。推荐从Awaitility框架静态导入下面的方法:
- org.awaitility.Awaitility.*
导入下面这些方法也是有用的:
- org.awaitility.Duration.*
- java.util.concurrent.TimeUnit.*
- org.hamcrest.Matchers.*
- org.junit.Assert.*
2 用法示例
2.1 示例1 – 简单
假设我们向异步系统发送了一个"add user"消息:
publish(new AddUserMessage("Awaitility Rocks"));
在我们的测试用例中,Awaitility可以帮忙你很容易的验证数据库是否已经被更新。它最简单的形式可能是这样子的:
await().until(newUserIsAdded());
newUserIsAdded是你在测试用例中实现的一个方法。它指定了一个Awaitility停止等待的条件:
默认情况下,Awaitility会等待10秒,如果在这段时间内,用户仓库的大小不等于1,那么它就会抛出ConditionTimeoutException异常,测试就失败了。如果想要重新定义超时时间,你可以这样写:
await().atMost(5, SECONDS).until(newUserWasAdded());
2.2 示例 2 – 更好的复用
Awaitility支持将条件拆分为供给和匹配两部分,以实现更好的复用。上面的示例也可以写成这样:
await().until( userRepositorySize(), equalTo(1) );
现在userRepositorySize方法是类型Integer的Callable:
equalTo是标准的Hamcrest匹配器,它为Awaitility指定了条件的匹配部分。
现在我们可以在不同的测试中复用userRepositorySize方法。例如,同时测试增加三个用户:
publish(new AddUserMessage("User 1"), new AddUserMessage("User 2"), new AddUserMessage("User 3"));
我们现在复用userRepositorySize供应者,简单更新一下Hamcrest匹配器:
await().until( userRepositorySize(), equalTo(3) );
2.3 示例3 – 基于代理的条件
用代理构建供应者也可以获取同样的结果:
await().untilCall( to(userRepository).size(), equalTo(3) );
to是org.awaitility.proxy.AwaitilityClassProxy的方法,AwaitilityClassProxy可以定义内联到await语句中的供应者。这样你自己就不必创建Callable了。选择用哪个取决于测试场景和可读性。
重要:在3.0.0版本中,代理条件不是Awaitility核心的一部分,为了使用这个功能,你必须额外依赖如下的库:
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility-proxy</artifactId>
<version>3.0.0</version>
<scope>test</scope>
</dependency>
2.4 示例4 – 字段
你也可以引用某个字段构建供应者,例如:
await().until( fieldIn(object).ofType(int.class), equalTo(2) );
或
await().until( fieldIn(object).ofType(int.class).andWithName("fieldName"), equalTo(2) );
或
await().until( fieldIn(object).ofType(int.class).andAnnotatedWith(MyAnnotation.class), equalTo(2) );
2.5 示例5 – 原子
如果你正在是使用原子结构(AtomicInteger、AtomicLong等), Awaitility提供了简单的方式等待匹配特定的值:
AtomicInteger atomic = new AtomicInteger(0);
// Do some async stuff that eventually updates the atomic integer
await().untilAtomic(atomic, equalTo(1));
等待AtomicBoolean更简单:
AtomicBoolean atomic = new AtomicBoolean(false);
// Do some async stuff that eventually updates the atomic boolean
await().untilTrue(atomic);
2.6 示例6 – 高级
使用100ms轮询间隔, 初始延迟20ms,直到客户的状态等于"REGISTERED"。这个例子用一个命名的await, 它指定了一个别名"customer registration"。如果一个测试中有多个await, 那么当await语句失败的时候,很容易找出那个失败了。
with().pollInterval(ONE_HUNDERED_MILLISECONDS)
.and().with().pollDelay(20, MILLISECONDS).await("customer registration")
.until(customerStatus(), equalTo(REGISTERED));
那使用不固定的轮询间隔,请参考poll interval文档。
2.7 示例7 - Java 8
如果你正在使用Java 8,那么你可以用lambda表达式指定条件:
await().atMost(5, SECONDS).until(() -> userRepository.size() == 1);
或用方法引用指定条件:
await().atMost(5, SECONDS).until(userRepository::isNotEmpty);
或方法引用和Hamcrest匹配器的组合:
await().atMost(5, SECONDS).until(userRepository::size, is(1));
示例请参考Jayway team blog。
2.8 示例8 – 使用AssertJ或Fest Assert
从1.6.0版本开始,你可以使用AssertJ或Fest Assert作为Hamcrest的选择 (实际上可以用任何错误时能抛出异常的第三方库)。
2.8.1 Version 3.x以上版本
await().atMost(5, SECONDS).untilAsserted(() -> assertThat(fakeRepository.getValue()).isEqualTo(1));
2.8.2 Version 2.x以下版本
await().atMost(5, SECONDS).until(() -> assertThat(fakeRepository.getValue()).isEqualTo(1));
2.9 示例9 – 忽略异常
从Awaitility 1.6.5起,你可以选择在评估条件时忽略异常。这是很有用的,如果你想要等待最终状态时会抛出异常作为中间态。作为例子,可以看看Sprint的SocketUtils类,它允许你在给定的范围内查找TCP端口。如果在给定的范围内没有端口,那么它就会抛出异常。所以,我们知道在给定的范围内一些端口是不可用的,但是我们想要等待它们可用,这时我们可以选择忽略SocketUtils抛出的异常。例如:
given().ignoreExceptions().await().until(() -> SocketUtils.findAvailableTcpPort(x,y));
这将告诉Awaitility,在评估条件时忽略所有捕获的异常。异常将会被评估为false。当异常匹配提供的异常类型时,测试不会失败(除非它超时了)。你也可以忽略指定的类型:
given().ignoreException(IllegalStateException.class).await().until(() -> SocketUtils.findAvailableTcpPort(x,y));
或者使用Hamcrest匹配器:
given().ignoreExceptionsMatching(instanceOf(RuntimeException.class)).await().until(() -> SocketUtils.findAvailableTcpPort(x,y));
或者使用断言predicate (Java 8):
given().ignoreExceptionsMatching(e -> e.getMessage().startsWith("Could not find an available")).await().until(something());
从版本3.0.0起,你也可以忽略Throwable实例。
2.10 示例10 – Runnable lambda表达式中的受检异常
在Java中,Runnable接口不允许抛出受检异常。所以你定义了类似于下面的方法:
public void waitUntilCompleted() throws Exception { ... }
这个方法可能会抛出受检查异常,当使用lambda表达式时,你不得不捕获这个异常:
await().untilAsserted(() -> {
try {
waitUntilCompleted();
} catch(Exception e) {
// Handle exception
}
});
这是很啰嗦的,所以Awaitility改善了这种状况。
2.10.1 Version 3.x以上版本
在Awaitility 3.x版本中,until方法被重命名为untilAsserted,它的入参是ThrowingRunnable实例,而不是java.lang.Runnable实例。
2.10.2 Version 2.x以下版本
Awaitility 1.7.0版本引入了一个帮助方法,叫matches,可以静态导入从org.awaitility.Awaitility类。在这个函数里,包装一个lambda表达式:
await().until(matches(() -> waitUntilCompleted()));
这极大地减少了公式化的代码,提高了可读性。
2.11 示例11 – 至少
从2.0.0版本起,你可以指导Awaitility至少等待一定的数量的时间。例如:
await().atLeast(1, SECONDS).and().atMost(2, SECONDS).until(value(), equalTo(1));
如果在atLeast指定的时间内条件满足了,那么它就会抛出异常表明条件不应该在指定的时间内完成。
3 线程处理
Awaitility 3.0.0版本引入了更加细粒度的方式配置Awaitility如何使用线程。这是通过提供一个线程或者ExecutorService实现的,这些将用于Awaility轮询条件。注意,这是一个高级特性,应该保守地使用。例如:
given().pollThread(Thread::new).await().atMost(1000, MILLISECONDS).until(..);
另一种方法是指定一个ExecutorService:
ExecutorSerivce es = ...
given().pollExecutorService(es).await().atMost(1000, MILLISECONDS).until(..);
这是很有用的,例如你需要等待条件,轮询ThreadLocal变量。
在一些场景中,让Awaitility使用与测试用例相同的线程是很重要的。因此,Awaitility 3.0.0引入了pollInSameThread方法:
with().pollInSameThread().await().atMost(1000, MILLISECONDS).until(...);
这是一个高级特性,你应该小心的使用,当"pollInSameThread"和条件组合使用时,会永远等待(或很长时间)。因为Awaitility不能中断这个线程。
4 异常处理
默认情况下,Awaitility捕获所有线程中未捕获的异常,并将这些异常传递给awaiting线程。这意味着你的测试用例会失败,即使不是测试线程抛出的异常。
你可以选择忽略特定的异常或。
5 死锁检测
Awaitility 1.6.2自动检测死锁,并将ConditionTimeoutException的原因和DeadlockException关联起来。DeadlockException异常包含那个线程引起死锁。
6 默认值
如果你不指定任何超时时间,那么Awaitility会等待10s,然后如果条件还不满足,则抛出ConditionTimeoutException异常。默认的轮询时间和轮询延迟时间都是100ms。你可以指定默认的值:
Awaitility.setDefaultTimeout(..)
Awaitility.setDefaultPollInterval(..)
Awaitility.setDefaultPollDelay(..)
你也可以使用Awaitility.reset()重置回默认值。
Awaitility 1.7.0引入了非固定轮询间隔,以补充之前版本中的固定轮询间隔。
注意,由于Awaitility用轮询来验证条件是否匹配,所以不推荐在就精确性能测试中使用它。在这些场景中,最好使用AOP框架,例如AspectJ的编译时编织。
也要注意到非固定轮询间隔的起始值是Duration.ZERO。对于固定轮询间隔来说,轮询延迟时间等于FixedPollInterval的持续时间,以实现后向兼容。
请看一看这篇博客了解额外的细节。
7.1 固定轮询间隔
这是Awaitility默认的轮询间隔机制。当用正常方式使用DSL时,例如
with().pollDelay(100, MILLISECONDS)
.and().pollInterval(200, MILLISECONDS).await().until(<condition>);
Awaitility将使用FixedPollInterval。这意味着Awaitility将会在轮询延迟之后检查条件是否满足(在轮询开始前的初始延迟时间,本示例中为100ms)。除非明确指定,Awaitility将使用相同的轮询延迟和轮询间隔(注意,这只针对于固定轮询间隔)。这意味着,在给定的轮询延迟时间之后,它会以轮询时间间隔周期性的检查条件是否满足,也就是说在pollDelay和pollDelay+pollInterval时间点检查条件是否满足。如果你修改了轮询间隔,轮询延迟也会修改以匹配指定的轮询间隔,除非你明确指定了轮询间隔。
7.2 斐波那契轮询间隔
FibonacciPollInterval生成非线性的轮询间隔,基于斐波那契数列。用法示例:
with().pollInterval(fibonacci()).await().until(..);
这里的fibonacci方法是从org.awaitility.pollinterval.FibonacciPollInterval静态导入的。这个方法会产生轮询间隔为1, 1, 2, 3, 5, 8, 13, .. 毫秒。你可以配置时间单位,例如秒。
with().pollInterval(fibonacci(TimeUnit.SECONDS)).await().until(..);
或者更像英语一些:
with().pollInterval(fibonacci().with().timeUnit(SECONDS).and().offset(5)).await().until(..);
偏移(Offset)意味着斐波那契数列从offset开始(默认偏移是0)。偏移可以是负数(-1)(fib(0) = 0).
7.3 迭代轮询间隔
迭代轮询间隔是由函数和一个起始duration生成的。这个函数可以做任何事情。例如:
await().with().pollInterval(iterative(duration -> duration.multiply(2)), Duration.FIVE_HUNDRED_MILLISECONDS).until(..);
或者更像英语一些:
await().with().pollInterval(iterative(duration -> duration.multiply(2)).with().startDuration(FIVE_HUNDRED_MILLISECONDS)).until(..);
这将生成这样的轮询间隔序列(ms):500, 1000, 2000, 4000, 8000, 16000, ...
注意如果你指定了轮询延迟时间,那么在第一次轮询间隔时间之前,它会延迟等待。请参考javadoc了解更多信息。
7.4 自定义轮询间隔
你也可以实现PollInterval接口,自定义轮询间隔。这是一个函数接口,所以在Java 8中,你可以这样写:
await().with().pollInterval((__, previous) -> previous.multiply(2).plus(1)).until(..);
在这个例子中,我们创建了一个实现了bi-function的PollInterval,它用上一次的轮询间隔乘以3,并加1。__只是表明我们不关心轮询次数。轮询次数只是在不关心上一次的轮询间隔,而只关心上一次的轮询次数的时候才需要。例如,FibonacciPollInterval只使用轮询次数:
await().with().pollInterval((pollCount, __) -> new Duration(fib(pollCount), MILLISECONDS)).until(..);
大多数场景下,你不必实现一个轮询间隔。应该为IterativePollInterval提供一个函数。
Awaitility 1.7.0引入了非固定轮询间隔,以补充之前版本中的固定轮询间隔。
注意,由于Awaitility用轮询来验证条件是否匹配,所以不推荐在就精确性能测试中使用它。在这些场景中,最好使用AOP框架,例如AspectJ的编译时编织。
也要注意到非固定轮询间隔的起始值是Duration.ZERO。对于固定轮询间隔来说,轮询延迟时间等于FixedPollInterval的持续时间,以实现后向兼容。
请看一看这篇博客了解额外的细节。
7.1 固定轮询间隔
这是Awaitility默认的轮询间隔机制。当用正常方式使用DSL时,例如
with().pollDelay(100, MILLISECONDS)
.and().pollInterval(200, MILLISECONDS).await().until(<condition>);
Awaitility将使用FixedPollInterval。这意味着Awaitility将会在轮询延迟之后检查条件是否满足(在轮询开始前的初始延迟时间,本示例中为100ms)。除非明确指定,Awaitility将使用相同的轮询延迟和轮询间隔(注意,这只针对于固定轮询间隔)。这意味着,在给定的轮询延迟时间之后,它会以轮询时间间隔周期性的检查条件是否满足,也就是说在pollDelay和pollDelay+pollInterval时间点检查条件是否满足。如果你修改了轮询间隔,轮询延迟也会修改以匹配指定的轮询间隔,除非你明确指定了轮询间隔。
7.2 斐波那契轮询间隔
FibonacciPollInterval生成非线性的轮询间隔,基于斐波那契数列。用法示例:
with().pollInterval(fibonacci()).await().until(..);
这里的fibonacci方法是从org.awaitility.pollinterval.FibonacciPollInterval静态导入的。这个方法会产生轮询间隔为1, 1, 2, 3, 5, 8, 13, .. 毫秒。你可以配置时间单位,例如秒。
with().pollInterval(fibonacci(TimeUnit.SECONDS)).await().until(..);
或者更像英语一些:
with().pollInterval(fibonacci().with().timeUnit(SECONDS).and().offset(5)).await().until(..);
偏移(Offset)意味着斐波那契数列从offset开始(默认偏移是0)。偏移可以是负数(-1)(fib(0) = 0).
7.3 迭代轮询间隔
迭代轮询间隔是由函数和一个起始duration生成的。这个函数可以做任何事情。例如:
await().with().pollInterval(iterative(duration -> duration.multiply(2)), Duration.FIVE_HUNDRED_MILLISECONDS).until(..);
或者更像英语一些:
await().with().pollInterval(iterative(duration -> duration.multiply(2)).with().startDuration(FIVE_HUNDRED_MILLISECONDS)).until(..);
这将生成这样的轮询间隔序列(ms):500, 1000, 2000, 4000, 8000, 16000, ...
注意如果你指定了轮询延迟时间,那么在第一次轮询间隔时间之前,它会延迟等待。请参考javadoc了解更多信息。
7.4 自定义轮询间隔
你也可以实现PollInterval接口,自定义轮询间隔。这是一个函数接口,所以在Java 8中,你可以这样写:
await().with().pollInterval((__, previous) -> previous.multiply(2).plus(1)).until(..);
在这个例子中,我们创建了一个实现了bi-function的PollInterval,它用上一次的轮询间隔乘以3,并加1。__只是表明我们不关心轮询次数。轮询次数只是在不关心上一次的轮询间隔,而只关心上一次的轮询次数的时候才需要。例如,FibonacciPollInterval只使用轮询次数:
await().with().pollInterval((pollCount, __) -> new Duration(fib(pollCount), MILLISECONDS)).until(..);
大多数场景下,你不必实现一个轮询间隔。应该为IterativePollInterval提供一个函数。
8 条件评估监听器
Awaitility 1.6.1引入了条件评估监听器的概念。这可以用于获取每一次评估条件时的信息。例如你可以用这个监听器获取条件的中间值。它可以用于记录日志。例如:
with().conditionEvaluationListener(
condition -> System.out.printf("%s (elapsed time %dms, remaining time %dms)\n",
condition.getDescription(),
condition.getElapsedTimeInMS(),
condition.getRemainingTimeInMS()))
.await().atMost(Duration.TEN_SECONDS).until(new CountDown(5), is(equalTo(0)));
将会在控制台打印如下的信息:
org.awaitility.AwaitilityJava8Test$CountDown expected (<0> or a value less than <0>) but was <5> (elapsed time 101ms, remaining time 1899ms)
org.awaitility.AwaitilityJava8Test$CountDown expected (<0> or a value less than <0>) but was <4> (elapsed time 204ms, remaining time 1796ms)
org.awaitility.AwaitilityJava8Test$CountDown expected (<0> or a value less than <0>) but was <3> (elapsed time 306ms, remaining time 1694ms)
org.awaitility.AwaitilityJava8Test$CountDown expected (<0> or a value less than <0>) but was <2> (elapsed time 407ms, remaining time 1593ms)
org.awaitility.AwaitilityJava8Test$CountDown expected (<0> or a value less than <0>) but was <1> (elapsed time 508ms, remaining time 1492ms)
org.awaitility.AwaitilityJava8Test$CountDown reached its end value of (<0> or a value less than <0>) (elapsed time 610ms, remaining time 1390ms)
有一个内置的用于日志的ConditionEvaluationListener,称为ConditionEvaluationLogger, 你可以这样使用:
with().conditionEvaluationListener(new ConditionEvaluationLogger()).await(). ...
注意Awaitility 1.6.1只支持基于Hamcrest的条件,但是1.6.2以上版本中所有的条件类型都有效。
9 Duration类
Awaitility提供了Duration类包含一些预定义的Duration值,例如ONE_HUNDRED_MILLISECONDS、FIVE_SECONDS和ONE_MINUTE。你也可以对Duration执行一些基本的数学操作。例如:
new Duration(5, SECONDS).plus(17, MILLISECONDS);
这将返回一个新的Duration,它的持续时间为5017毫秒。注意Duration是不可变的。所以调用plus方法会返回一个新的实例。这主要是在于非固定轮询间隔时有用的。
10 重要
Awaitility不保证线程安全性和线程同步!这是你的责任!确保你的代码是正确同步的或者你使用的是线程安全的数据结构,例如volatile字段或者原子类AtomicInteger和ConcurrentHashMap。
11 链接与代码示例
- Awaitility test case
- Awaitility test case Java 8
- Field supplier test case
- Atomic test case
- Presentation from Jayway's KHelg 2011