الاختبار في Spring Boot
الاختبار في Spring Boot
كتابة كود يعمل مرة واحدة أمر سهل. أما كتابة كود يظل يعمل عبر عمليات إعادة الهيكلة وترقيات التبعيات وتغيرات الفريق فهو التحدي الهندسي الحقيقي. الاختبارات الآلية هي الممارسة التي تجعل ذلك ممكنًا، وتشحن Spring Boot بمنظومة اختبار متكاملة من الصندوق مباشرةً. يُؤطّر هذا الدرس المشكلة ويُقدّم المفاهيم الأساسية ويستعرض كل مكوّن تجلبه تبعية spring-boot-starter-test إلى مسار الفئات، حتى تعرف ما تعمل معه قبل أن تكتب أول @Test.
لماذا يهم الاختبار أكثر في تطبيق Spring
خدمة Spring Boot هي تأليف من حبوب (beans) متعاونة كثيرة — متحكمات وخدمات ومستودعات ومستمعات أحداث ومهام مجدولة — مترابطة بحاوية IoC. يمكن لأي حبّة منها أن تنكسر بصمت إذا تغيّر عقد التبعية. تكشف الاختبارات الآلية تلك الكسور في ثوانٍ لا خلال حادثة إنتاجية في الثانية صباحًا.
ثمة فائدة أخرى أدق: الكود القابل للاختبار هو دائمًا تقريبًا كودٌ أفضل تصميمًا. الخدمة التي يصعب اختبارها بمعزل عن غيرها إشارةٌ إلى أنها تحمل مسؤوليات كثيرة أو مقترنة اقترانًا شديدًا. يدفع فعل كتابة الاختبارات تصميمك نحو مكوّنات أصغر وأنظف وأكثر قابلية للتأليف.
هرم الاختبار
يُعدّ هرم الاختبار نموذجًا ذهنيًا قدّمه مايك كوهن يصف التوزيع المثالي للاختبارات عبر ثلاث طبقات:
- اختبارات الوحدة (القاعدة — العريضة): تختبر فئة أو دالة واحدة في عزلة تامة. تُستبدَل جميع المتعاونون بأضعاف اختبارية (mocks أو stubs). هذه الاختبارات سريعة (أقل من ميلي ثانية كل منها) وكثيرة وتشكّل العمود الفقري لمجموعتك.
- اختبارات التكامل (الوسط — أضيق): تختبر مكوّنَين حقيقيَّين أو أكثر يعملان معًا — مثلًا خدمة تتفاعل مع قاعدة بيانات حقيقية أو في الذاكرة، أو متحكم مرتبط بطبقة خدمة حقيقية. هي أبطأ وأقل عددًا.
- اختبارات من طرف إلى طرف (القمة — الضيقة): تختبر التطبيق بأكمله من الخارج — طلب HTTP يضرب الخادم ويمر بكل طبقة وصولًا إلى قاعدة البيانات. هي الأبطأ والأكثر هشاشة ويجب أن تكون الأقل.
صُمّمت أدوات اختبار Spring Boot بحيث يمكنك كتابة اختبارات على كل مستوى من مستويات الهرم دون كود تجهيزي. يتولى الإطار تشغيل السياق وربطه نيابةً عنك.
ما توفره spring-boot-starter-test
أضف مُشغِّل الاختبار إلى ملف pom.xml بنطاق test:
هذه التبعية الوحيدة تسحب بشكل انتقالي مجموعة منتقاة بعناية من المكتبات. فهم كل منها يخبرك بأي أداة تمد يدك في سيناريو اختبار معيّن.
JUnit 5 (Jupiter)
JUnit 5 هو مشغّل الاختبار — المحرك الذي يكتشف ويُنفّذ دوال الاختبار. حلّ محل JUnit 4 باعتباره الإعداد الافتراضي لـ Spring Boot منذ الإصدار 2.2. التعليقات التوضيحية الرئيسية التي ستستخدمها باستمرار:
@Test— تُعلّم الدالة باعتبارها حالة اختبار.@BeforeEach/@AfterEach— إعداد وتنظيف حول كل دالة اختبار.@BeforeAll/@AfterAll— دوال ثابتة تعمل مرة واحدة لكل فئة اختبار (للإعداد المكلف كتشغيل حاوية).@DisplayName— اسم مقروء يظهر في IDE وتقارير CI.@Nested— يُجمّع الاختبارات ذات الصلة داخل فئة داخلية لتحسين القراءة.@ParameterizedTestمع@ValueSource/@CsvSource/@MethodSource— تشغيل نفس الاختبار بمدخلات متعددة دون نسخ ولصق.
AssertJ — التأكيدات السلسلة
يشحن JUnit 5 بدوال Assertions أساسية، لكن AssertJ مضمّن في المُشغِّل ومُفضَّل بشكل شبه عالمي لأن واجهته البرمجية السلسلة تُنتج رسائل فشل أوضح بكثير.
عند فشل اختبار، تطبع AssertJ بالضبط ما وجدته مقابل ما توقّعته، بما في ذلك محتويات القائمة الكاملة — لا مجرد "توقّعت true لكن كان false".
Mockito — محاكاة المتعاونين
تتيح لك Mockito استبدال التبعيات الحقيقية بأضعاف خاضعة للتحكم على مستوى اختبار الوحدة. ستستخدم ثلاثة أنماط باستمرار:
mock(SomeClass.class)— ينشئ mock يعيد القيم الافتراضية (null، 0، مجموعات فارغة) ما لم تُعدّه.when(...).thenReturn(...)— يُعدّ دالة لإعادة قيمة محددة.verify(...)— يؤكد أن دالة استُدعيَت بالوسيطات المتوقعة.
new OrderService(mockRepo). هذا أسرع اختبار ممكن وهو الذي يجب أن تلجأ إليه أولًا.
Hamcrest — مكتبة المطابقات
مطابقات Hamcrest موجودة أيضًا في مسار الفئات (سابقة لـ AssertJ). قد تصادفها في الكود القديم أو في تأكيدات نتائج اختبار Spring MVC (MockMvcResultMatchers يستخدم Hamcrest افتراضيًا). كلاهما يتعايشان دون تعارض.
JSONassert وJsonPath
عندما تحتاج اختباراتك إلى التحقق من بنية استجابات JSON من نقاط نهاية REST، يتضمّن المُشغِّل مساعدَين:
- JSONassert — يقارن سلاسل JSON هيكليًا متجاهلًا التنسيق وترتيب الحقول.
- JsonPath — يتيح استخراج قيم من مستند JSON باستخدام تعبيرات المسار (مثلًا
$.orders[0].id). يتكاملMockMvcفي Spring مباشرةً مع JsonPath.
spring-test — طبقة تكامل Spring
توفر وحدة spring-test (جزء من Spring Framework الأساسي) البنية التحتية التي تدمج JUnit 5 مع حاوية IoC:
@ExtendWith(SpringExtension.class)— امتداد JUnit 5 يُشغّل سياق Spring لفئات الاختبار المُعلَّمة (مُضمَّن تلقائيًا في@SpringBootTestوتعليقات الشريحة).@ContextConfiguration— تعليق منخفض المستوى لتحديد فئات التهيئة التي يجب تحميلها.MockMvc— طبقة HTTP مخصصة للاختبار ترسل الطلبات إلى متحكماتك دون تشغيل حاوية servlet فعلية.TestRestTemplate— مغلّف حولRestTemplateلاختبارات HTTP كاملة الرصة التي تشغّل خادمًا فعلًا.@TestPropertySource/@DynamicPropertySource— تجاوز خصائص التهيئة لفئة اختبار محددة.
تعليقات الشريحة — ميزة Spring Boot الاختبارية
تضيف Spring Boot طبقة فوق spring-test: تعليقات الشريحة. تُشغّل كل شريحة فقط المجموعة الفرعية من سياق التطبيق ذات الصلة بطبقة واحدة مما يجعل الاختبارات أسرع وأكثر تركيزًا:
@WebMvcTest— يحمّل طبقة الويب فقط (متحكمات وفلاتر ومحوّلات). لا حبوب خدمة أو مستودع.@DataJpaTest— يحمّل مستودعات JPA فقط وقاعدة بيانات مدمجة. لا متحكمات.@JsonTest— يحمّل مكوّنات التسلسل/إلغاء التسلسل JSON فقط.@SpringBootTest— يحمّل سياق التطبيق الكامل. استخدمه لاختبارات التكامل الحقيقية.
@SpringBootTest بشكل منعكس. يستغرق تشغيل السياق الكامل عدة ثوانٍ ويحمّل كل حبّة ومصدر بيانات وتكامل خارجي. لفئة بها 30 دالة اختبار تحتاج فقط إلى متحكم، يتضاعف هذا العبء بشكل مؤلم. استخدم أضيق شريحة تغطي ما تحتاج اختباره.
الخلاصة
يمنحك هرم الاختبار استراتيجية: اختبارات وحدة كثيرة وسريعة، واختبارات تكامل أقل، وعدد صغير من الاختبارات الشاملة. تجلب تبعية spring-boot-starter-test JUnit 5 كمشغّل، وAssertJ للتأكيدات السلسلة، وMockito للأضعاف، وتعليقات شريحة Spring الخاصة حتى تحمّل القدر المناسب من السياق لكل اختبار. في الدرس القادم ستضع JUnit 5 وMockito في الاستخدام لكتابة اختبارات وحدة لفئة خدمة — دون أي سياق Spring مطلوب.