Awaitility用户指导

来源:互联网 发布:金字塔原理 知乎 编辑:程序博客网 时间:2024/06/03 05:53

Awaitility用户指导

Scala文档请点击这里hereGroovy文档请点击这里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停止等待的条件:

文本框: private Callable<Boolean> newUserIsAdded() {    return new Callable<Boolean>() {        public Boolean call() throws Exception {            // 这个条件必须满足            return userRepository.size() == 1;        }    };}

默认情况下,Awaitility会等待10秒,如果在这段时间内,用户仓库的大小不等于1,那么它就会抛出ConditionTimeoutException异常,测试就失败了。如果想要重新定义超时时间,你可以这样写:

await().atMost(5, SECONDS).until(newUserWasAdded());

2.2 示例 2 – 更好的复用

Awaitility支持将条件拆分为供给和匹配两部分,以实现更好的复用。上面的示例也可以写成这样:

await().until( userRepositorySize(), equalTo(1) );

现在userRepositorySize方法是类型IntegerCallable

文本框: private Callable<Integer> userRepositorySize() {      return new Callable<Integer>() {            public Integer call() throws Exception {                  return userRepository.size(); // The condition supplier part            }      };}

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) );

toorg.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 – 原子

如果你正在是使用原子结构(AtomicIntegerAtomicLong等), 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 – 使用AssertJFest Assert

1.6.0版本开始,你可以使用AssertJFest 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起,你可以选择在评估条件时忽略异常。这是很有用的,如果你想要等待最终状态时会抛出异常作为中间态。作为例子,可以看看SprintSocketUtils类,它允许你在给定的范围内查找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()重置回默认值。

7 轮询

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将使用相同的轮询延迟和轮询间隔(注意,这只针对于固定轮询间隔)。这意味着,在给定的轮询延迟时间之后,它会以轮询时间间隔周期性的检查条件是否满足,也就是说在pollDelaypollDelay+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-functionPollInterval,它用上一次的轮询间隔乘以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_MILLISECONDSFIVE_SECONDSONE_MINUTE。你也可以对Duration执行一些基本的数学操作。例如:

new Duration(5, SECONDS).plus(17, MILLISECONDS);

这将返回一个新的Duration,它的持续时间为5017毫秒。注意Duration是不可变的。所以调用plus方法会返回一个新的实例。这主要是在于非固定轮询间隔时有用的。

10 重要

Awaitility不保证线程安全性和线程同步!这是你的责任!确保你的代码是正确同步的或者你使用的是线程安全的数据结构,例如volatile字段或者原子类AtomicIntegerConcurrentHashMap

11 链接与代码示例

  1. Awaitility test case
  2. Awaitility test case Java 8
  3. Field supplier test case
  4. Atomic test case
  5. Presentation from Jayway's KHelg 2011