Awaitility في Java: دليل شامل مع أمثلة
1. ما هي Awaitility ولماذا تحتاجها
Awaitility هي مكتبة Java قوية توفر لغة خاصة بالمجال (DSL) لكتابة اختبارات غير متزامنة بسهولة. في التطوير الحديث، نتعامل باستمرار مع عمليات غير متزامنة مثل: طلبات HTTP، التعامل مع قواعد البيانات، قوائم الرسائل، CompletableFuture، التدفقات التفاعلية وغيرها. الطرق التقليدية للاختبار مثل Thread.sleep() تؤدي إلى اختبارات هشة وبطيئة وغير موثوقة. Awaitility تحل هذه المشكلة من خلال السماح لك بوصف التوقعات بشكل تصريحي: "انتظر، ولكن ليس أكثر من 10 ثوانٍ، حتى يصبح الشرط صحيحًا، مع التحقق كل 100 مللي ثانية".
القيمة الأساسية لهذه المكتبة هي جعل الاختبارات غير المتزامنة مستقرة وقابلة للقراءة وفعالة. بدلاً من التخمين حول المدة التي ستستغرقها العملية، تصف الحالة النهائية للنظام. Awaitility تدير بنفسها وقت الانتظار، وتكرار الفحوصات، ومعالجة الاستثناءات. هذا مهم بشكل خاص عند اختبار الخدمات المصغرة، وسيناريوهات التكامل، والأنظمة ذات البنية الموزعة حيث يمكن أن يختلف وقت تنفيذ العمليات بشكل كبير.
تتكامل المكتبة مع جميع أطر الاختبار الشائعة (JUnit 4/5، TestNG، Spock) ويمكن استخدامها في كل من الاختبارات الوحدوية والتكاملية. Awaitility لا تفرض أي نهج محدد للتعامل مع عدم التزامن - فهي تعمل مع أي آلية: الاستدعاءات الراجعة، Future، التدفقات التفاعلية، ناقل الأحداث، وغيرها. الشرط الوحيد هو وجود طريقة للتحقق من حالة النظام في وقت معين.
2. التثبيت
يتم توزيع Awaitility عبر Maven Central. لتضمينها في مشروعك، استخدم مدير التبعيات الخاص بمشروعك.
Maven (pom.xml)
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.2.1</version>
<scope>test</scope>
</dependency>
Gradle (build.gradle)
testImplementation 'org.awaitility:awaitility:4.2.1'
Gradle Kotlin DSL (build.gradle.kts)
testImplementation("org.awaitility:awaitility:4.2.1")
التحقق من الإصدار
يمكنك التحقق من أحدث إصدار على MVNRepository. وقت كتابة هذا المقال، الإصدار المستقر الحالي هو 4.2.1 (متوافق مع Java 8+).
3. البداية السريعة - مثال عملي بسيط
لننظر إلى مثال بسيط: لدينا خدمة تقوم بتحديث قيمة بشكل غير متزامن. نريد الانتظار حتى تصبح القيمة مساوية لـ 5.
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class QuickStartTest {
@Test
public void shouldWaitForAsyncUpdate() {
// محاكاة خدمة غير متزامنة
AtomicInteger counter = new AtomicInteger(0);
// تشغيل خيط خلفي يزيد العداد
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200); // محاكاة عمل
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
counter.incrementAndGet();
}
}).start();
// Awaitility تنتظر حتى يصبح العداد مساوياً لـ 5
Awaitility.await()
.atMost(5, TimeUnit.SECONDS)
.until(() -> counter.get() == 5);
// التحقق من النتيجة
assert counter.get() == 5 : "يجب أن يكون العداد مساوياً لـ 5";
}
}
هذا الاختبار سينتهي بشكل مضمون في أقل من 5 ثوانٍ (أو سيفشل إذا لم يصل العداد إلى 5). لاحظ كيف أن الكود واضح ويعبر عن النية: "انتظر حتى يصبح الشرط صحيحًا، ولكن ليس أكثر من 5 ثوانٍ".