دورة حياة الاختبار والإعداد
دورة حياة الاختبار والإعداد
تتّبع الاختبارات المُهيكَلة بشكل جيّد إيقاعًا متوقعًا: تُجهّز الموارد قبل تشغيل الاختبار، ثم تُشغّل الوحدة المختبَرة، ثم تُنظّف بعد ذلك. يُضفي JUnit 5 الطابع الرسمي على هذا الإيقاع عبر أربعة تعليقات توضيحية لدورة الحياة: @BeforeAll، و@BeforeEach، و@AfterEach، و@AfterAll. فهم توقيت تنفيذ كلٍّ منها — ولماذا — هو الفاصل بين مجموعة اختبارات سريعة ومعزولة وقابلة للصيانة، وبين مجموعة هشّة مليئة بالحالات المخفيّة.
ترتيب التنفيذ بنظرة سريعة
يُنفّذ JUnit 5 هذه الاستدعاءات بترتيب صارم لكل فئة اختبار:
@BeforeAll— يعمل مرّة واحدة قبل أول دالة اختبار في الفئة.@BeforeEach— يعمل قبل كل دالة اختبار.- تُنفَّذ دالة الاختبار.
@AfterEach— يعمل بعد كل دالة اختبار.@AfterAll— يعمل مرّة واحدة بعد آخر دالة اختبار في الفئة.
@BeforeAll و@AfterAll static (تنتمي للفئة لا للنسخة)، إلا إذا انتقلت إلى @TestInstance(Lifecycle.PER_CLASS).
@BeforeEach — الإعداد لكل اختبار
@BeforeEach هي المحرّك الرئيسي لإعداد الاختبار. استخدمها لبناء حالة نظيفة ومعروفة لكل اختبار حتى لا يتأثر اختبار بما سبقه. الاستخدامات الشائعة تشمل: إنشاء الوحدة المختبَرة، وتعبئة هيكل بيانات صغير في الذاكرة، أو فتح مورد يحتاج إلى إغلاق بعد الاختبار.
نظرًا لتشغيل setUp() قبل كل اختبار، يبدأ كلا الاختبارين بعربة تحتوي بالفعل على Widget واحد. لا يمكن لأي اختبار أن يُفسد الآخر بترك حالة عربة قديمة.
@AfterEach — التنظيف بعد كل اختبار
@AfterEach تُقابل @BeforeEach. استخدمها لتحرير الموارد التي استُحصلت في @BeforeEach — إغلاق مقبض ملف، أو إعادة تعيين محاكاة ثابتة، أو التراجع عن معاملة قاعدة بيانات. يضمن JUnit تشغيل @AfterEach حتى عندما يُلقي الاختبار نفسه استثناءً، مما يجعلها المكان المناسب للتنظيف الذي يجب أن يحدث دائمًا.
AutoCloseable، فكّر في استخدام امتداد @TempDir الخاص بـ JUnit 5 (للمجلّدات) أو واجهة برمجة Store الخاصة بـ CloseableResource بدلًا من التنظيف اليدوي عبر @AfterEach. هذه الأساليب أقل عرضة للخطأ لأن الإطار يتولّى الإغلاق تلقائيًا.
@BeforeAll — الإعداد لمرّة واحدة على مستوى الفئة
بعض الأدوات مكلفة الإنشاء وآمنة للمشاركة بين الاختبارات: مجمّع اتصالات قاعدة بيانات، أو خادم يعمل، أو تعبير نمطي مُجمَّع. تعمل @BeforeAll مرّة واحدة لكل فئة، مما يجعلها مثالية لهذا النوع من الحالة المشتركة باهظة الثمن والمخصّصة للقراءة فقط.
لاحظ النمط: تُشغّل @BeforeAll قاعدة البيانات مرّة واحدة؛ وتُعيد @BeforeEach تعيين البيانات قبل كل اختبار. العملية المكلفة تحدث مرّة واحدة؛ والعزل يُحافَظ عليه بتكلفة زهيدة.
@BeforeAll دون إعادة تعيينه، فقد تفشل الاختبارات اللاحقة بسبب السابقة — وهو فشل كلاسيكي يعتمد على الترتيب. احتفظ بـ @BeforeAll للموارد التي تكون إما للقراءة فقط من منظور الاختبار (تعبير نمطي مُجمَّع، أو مجمّع اتصالات) أو يتمّ إعادة تعيينها في @BeforeEach.
@BeforeAll الثابتة مقابل @TestInstance(PER_CLASS)
مع دورة حياة PER_METHOD الافتراضية، يجب أن تكون دوال @BeforeAll و@AfterAll static لأنه لا توجد نسخة عند استدعائها. إضافة @TestInstance(TestInstance.Lifecycle.PER_CLASS) إلى الفئة يُخبر JUnit بإنشاء نسخة واحدة مشتركة للفئة كاملة، مما يُزيل اشتراط static — مفيد عندما لا يمكن الوصول للمورد بشكل ثابت (مثل ApplicationContext المُحقَن من Spring).
أفضل ممارسات التسمية والترتيب
- سمِّ دوال
@BeforeEachباسمsetUp()أو اسم وصفي مثلgivenFreshCart(). الاسم الوصفي يُسهّل تشخيص الإعداد الفاشل. - أبقِ
@BeforeEachقصيرة. إذا تجاوز الإعداد 10-15 سطرًا فهذا مؤشر على أن فئة الاختبار تفعل الكثير — قسّمها. - استخدم
@DisplayNameعلى فئة الاختبار ودواله لكتابة أسماء مقروءة تظهر في التقارير دون معرّفات الدوال الغامضة. - تجنّب وضع التأكيدات داخل
@BeforeEach؛ فشل الإعداد يظهر بالفعل كأخطاء، والتأكيدات تُضيف ضجيجًا. - إذا وُجدت دوال متعددة من
@BeforeEach(ممكن عبر الوراثة)، يستدعي JUnit دالة الفئة الأم أولًا. استفد من هذا لتطبيق طبقات إعداد الفئة الأساسية المشتركة مع التفاصيل الخاصة بالفئات الفرعية.
الوراثة والأدوات المشتركة
النمط الشائع في قواعد الكود الكبيرة هو فئة اختبار أساسية تتولّى الأجزاء المتكرّرة — تشغيل عميل ويب، وتهيئة التسجيل — وفئات فرعية ملموسة تُضيف إعدادًا خاصًا بالمجال:
الخلاصة
@BeforeEach و@AfterEach هما أدواتك الأساسية لعزل الاختبارات — تضمنان بداية نظيفة قبل كل اختبار وتنظيفًا موثوقًا بعده. أما @BeforeAll و@AfterAll فتُحسّنان الأدوات المشتركة المكلفة التي ستُبطّئ المجموعة لو كُرّرت لكل اختبار. القاعدة الذهبية هي مشاركة الحالة فقط عندما تكون للقراءة فقط حقًا أو يُعاد تعيينها في @BeforeEach. هذا الانضباط يجعل مجموعة اختباراتك سريعة وحتمية وجديرة بالثقة — الأسس التي ستعتمد عليها في كل درس قادم عن Mockito والتطوير المبني على الاختبار وأفضل الممارسات.