Introduction to Awaitility – 等待的介绍

最后修改: 2017年 7月 15日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

A common problem with asynchronous systems is that it’s hard to write readable tests for them that are focused on business logic and are not polluted with synchronizations, timeouts, and concurrency control.

异步系统的一个常见问题是,很难为其编写可读的测试,这些测试集中在业务逻辑上,不被同步、超时和并发控制所污染。

In this article, we are going to take a look at Awaitility — a library which provides a simple domain-specific language (DSL) for asynchronous systems testing.

在这篇文章中,我们将看看Awaitility – 一个为异步系统测试提供简单特定领域语言(DSL)的库

With Awaitility, we can express our expectations from the system in an easy-to-read DSL.

有了Awaitility,我们可以用易于阅读的DSL来表达我们对系统的期望。

2. Dependencies

2.依赖性

We need to add Awaitility dependencies to our pom.xml.

我们需要在我们的pom.xml中添加Awaitility的依赖关系。

The awaitility library will be sufficient for most use cases. In case we want to use proxy-based conditions, we also need to provide the awaitility-proxy library:

awaitility库将足以满足大多数使用情况。如果我们想使用基于代理的条件我们还需要提供awaitility-proxy库。

<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <version>3.0.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility-proxy</artifactId>
    <version>3.0.0</version>
    <scope>test</scope>
</dependency>

You can find the latest version of the awaitility and awaitility-proxy libraries on Maven Central.

您可以在Maven中心找到最新版本的awaitilityawaitility-proxy库。

3. Creating an Asynchronous Service

3.创建一个异步服务

Let’s write a simple asynchronous service and test it:

让我们写一个简单的异步服务并测试它。

public class AsyncService {
    private final int DELAY = 1000;
    private final int INIT_DELAY = 2000;

    private AtomicLong value = new AtomicLong(0);
    private Executor executor = Executors.newFixedThreadPool(4);
    private volatile boolean initialized = false;

    void initialize() {
        executor.execute(() -> {
            sleep(INIT_DELAY);
            initialized = true;
        });
    }

    boolean isInitialized() {
        return initialized;
    }

    void addValue(long val) {
        throwIfNotInitialized();
        executor.execute(() -> {
            sleep(DELAY);
            value.addAndGet(val);
        });
    }

    public long getValue() {
        throwIfNotInitialized();
        return value.longValue();
    }

    private void sleep(int delay) {
        try {
            Thread.sleep(delay);
        } catch (InterruptedException e) {
        }
    }

    private void throwIfNotInitialized() {
        if (!initialized) {
            throw new IllegalStateException("Service is not initialized");
        }
    }
}

4. Testing With Awaitility

4.等待测试

Now, let’s create the test class:

现在,让我们来创建测试类。

public class AsyncServiceLongRunningManualTest {
    private AsyncService asyncService;

    @Before
    public void setUp() {
        asyncService = new AsyncService();
    }
    
    //...
}

Our test checks whether initialization of our service occurs within a specified timeout period (default 10s) after calling the initialize method.

我们的测试检查我们的服务是否在调用initialize方法后的指定超时时间(默认为10s)内发生初始化。

This test case merely waits for the service initialization state to change or throws a ConditionTimeoutException if the state change does not occur.

这个测试用例只是等待服务初始化状态的改变,如果状态没有发生改变,则抛出一个ConditionTimeoutException

The status is obtained by a Callable that polls our service at defined intervals (100ms default) after a specified initial delay (default 100ms). Here we are using the default settings for the timeout, interval, and delay:

状态是由一个Callable获得的,它在指定的初始延迟(默认为100ms)后,以定义的时间间隔(默认为100ms)轮询我们的服务。这里我们使用超时、间隔和延迟的默认设置。

asyncService.initialize();
await()
  .until(asyncService::isInitialized);

Here, we use await — one of the static methods of the Awaitility class. It returns an instance of a ConditionFactory class. We can also use other methods like given for the sake of increasing readability.

这里,我们使用awaitAwaitility类的一个静态方法。它返回一个ConditionFactory类的实例。为了增加可读性,我们也可以使用其他方法,如given

The default timing parameters can be changed using static methods from the Awaitility class:

可以使用Awaitility类的静态方法来改变默认的定时参数。

Awaitility.setDefaultPollInterval(10, TimeUnit.MILLISECONDS);
Awaitility.setDefaultPollDelay(Duration.ZERO);
Awaitility.setDefaultTimeout(Duration.ONE_MINUTE);

Here we can see the use of the Duration class, which provides useful constants for the most frequently used time periods.

这里我们可以看到Duration类的使用,它为最经常使用的时间段提供了有用的常量。

We can also provide custom timing values for each await call. Here we expect that initialization will occur at most after five seconds and at least after 100ms with polling intervals of 100ms:

我们还可以为每个await调用提供自定义的时间值。在这里,我们期望初始化最多在5秒后发生,至少在100ms后发生,轮询间隔为100ms。

asyncService.initialize();
await()
    .atLeast(Duration.ONE_HUNDRED_MILLISECONDS)
    .atMost(Duration.FIVE_SECONDS)
  .with()
    .pollInterval(Duration.ONE_HUNDRED_MILLISECONDS)
    .until(asyncService::isInitialized);

It’s worth mentioning that the ConditionFactory contains additional methods like with, then, and, given. These methods don’t do anything and just return this, but they could be useful to enhance the readability of test conditions.

值得一提的是,ConditionFactory包含额外的方法,如withthenandgiven。这些方法不做任何事情,只是返回this,但它们对提高测试条件的可读性很有用。

5. Using Matchers

5.使用匹配器

Awaitility also allows the use of hamcrest matchers to check the result of an expression. For example, we can check that our long value is changed as expected after calling the addValue method:

Awaitility还允许使用hamcrest匹配器来检查表达式的结果。例如,我们可以检查我们的long值在调用addValue方法后是否如预期的那样被改变。

asyncService.initialize();
await()
  .until(asyncService::isInitialized);
long value = 5;
asyncService.addValue(value);
await()
  .until(asyncService::getValue, equalTo(value));

Note that in this example, we used the first await call to wait until the service is initialized. Otherwise, the getValue method would throw an IllegalStateException.

请注意,在这个例子中,我们使用第一个await调用来等待服务被初始化。否则,getValue方法将抛出一个IllegalStateException

6. Ignoring Exceptions

6.忽略异常情况

Sometimes, we have a situation where a method throws an exception before an asynchronous job is done. In our service, it can be a call to the getValue method before the service is initialized.

有时,我们会遇到这样的情况:在异步工作完成之前,一个方法抛出一个异常。在我们的服务中,它可能是在服务被初始化之前对getValue方法的调用。

Awaitility provides the possibility of ignoring this exception without failing a test.

Awaitingility提供了忽略这种异常而不使测试失败的可能性。

For example, let’s check that the getValue result is equal to zero right after initialization, ignoring IllegalStateException:

例如,让我们检查一下getValue的结果在初始化后是否等于零,忽略IllegalStateException

asyncService.initialize();
given().ignoreException(IllegalStateException.class)
  .await().atMost(Duration.FIVE_SECONDS)
  .atLeast(Duration.FIVE_HUNDRED_MILLISECONDS)
  .until(asyncService::getValue, equalTo(0L));

7. Using Proxy

7.使用代理权

As described in section 2, we need to include awaitility-proxy to use proxy-based conditions. The idea of proxying is to provide real method calls for conditions without implementation of a Callable or lambda expression.

正如第2节所述,我们需要包含awaitility-proxy来使用基于代理的条件。代理的想法是为条件提供真正的方法调用,而无需实现Callable或lambda表达式。

Let’s use the AwaitilityClassProxy.to static method to check that AsyncService is initialized:

让我们使用AwaitilityClassProxy.to静态方法来检查AsyncService是否被初始化。

asyncService.initialize();
await()
  .untilCall(to(asyncService).isInitialized(), equalTo(true));

8. Accessing Fields

8.访问字段

Awaitility can even access private fields to perform assertions on them. In the following example, we can see another way to get the initialization status of our service:

Awaitility甚至可以访问私有字段,对其进行断言。在下面的例子中,我们可以看到获取服务初始化状态的另一种方式。

asyncService.initialize();
await()
  .until(fieldIn(asyncService)
  .ofType(boolean.class)
  .andWithName("initialized"), equalTo(true));

9. Conclusion

9.结论

In this quick tutorial, we introduced the Awaitility library, got acquainted with its basic DSL for the testing of asynchronous systems, and saw some advanced features which make the library flexible and easy to use in real projects.

在这个快速教程中,我们介绍了Awaitility库,熟悉了它用于测试异步系统的基本DSL,并看到了一些高级功能,这些功能使该库在实际项目中灵活而容易使用。

As always, all code examples are available on Github.

一如既往,所有的代码实例都可以在Github上找到