深入探讨 Awaitility:异步测试的神器

文摘   2024-11-01 08:58   辽宁  

深入探讨 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(11);
        assertNotEquals(12);
    });
}

以上就是 Awaitility 的一些基本用法和配置选项。通过这些配置,我们可以更加灵活地处理各种异步场景,使我们的测试代码更加健壮和可靠。在实际应用中,可以根据具体的需求选择合适的配置组合,以达到最佳的效果。

文档:https://github.com/awaitility/awaitility/wiki/Usage

GitHub:https://github.com/awaitility/awaitility


欢迎关注我的公众号“编程与架构”,原创技术文章第一时间推送。



编程与架构
专注于Java、大数据、AI以及开发运维技术的深入探索与分享。作为一名开源爱好者,致力于分享实战经验和前沿技术动态,帮助更多技术人提升技能。
 最新文章