اختبار طبقة الاستمرارية باستخدام @DataJpaTest
اختبار طبقة الاستمرارية باستخدام @DataJpaTest
تمثّل مستودعاتك (repositories) الحدَّ الفاصل بين منطق المجال والقاعدة البيانات. اختبارها بمعزل — دون تشغيل سياق التطبيق الكامل — يمنحك تغذية راجعة سريعة وحتمية حول تعيينات JPA الخاصة بك والاستعلامات المخصصة وقيود البيانات. مُرشَّح الشريحة @DataJpaTest من Spring Boot مصمَّم تحديدًا لهذا الغرض.
ما الذي يفعله @DataJpaTest فعليًا
حين تُعلِّق فئة اختبار بـ @DataJpaTest، يبني Spring Boot شريحة من سياق التطبيق تحتوي فقط على المكوّنات اللازمة لطبقة الاستمرارية:
- جميع مستودعات Spring Data JPA
- فئات
@Entityالخاصة بك وتعييناتها في Hibernate EntityManagerمُهيَّأ (وTestEntityManager)- دعم المعاملات في Spring
ما يستثنيه عمدًا: حبوب @Service وحبوب @Controller وسياق @SpringBootTest الكامل. هذا يعني أن اختبارك يبدأ في أجزاء من الثانية بدلًا من ثوانٍ، ويختبر فقط الكود الذي يعنيك.
@DataJpaTest مصدر البيانات المُهيَّأ لديك بقاعدة بيانات H2 مضمَّنة (في الذاكرة) ويُشغّل schema.sql / data.sql إن وُجدا، أو يعتمد على ddl-auto=create-drop في Hibernate. يعمل كل أسلوب اختبار داخل معاملة يتمّ التراجع عنها في النهاية، فتُعاد القاعدة إلى حالتها تلقائيًا بين الاختبارات.
الإعداد: التبعيات
أضف مُشغّل اختبار Spring Boot وH2 إلى ملف pom.xml (كلاهما بنطاق test):
تُوفّر إدارة تبعيات Spring Boot إصدارات متوافقة، لذا لا حاجة لتحديد علامات الإصدار.
الكيان والمستودع قيد الاختبار
تأمّل كيانًا بسيطًا Order ومستودع Spring Data يضيف محدّدًا مخصصًا واحدًا:
كتابة فئة اختبار @DataJpaTest
فيما يلي فئة اختبار كاملة وواقعية تستخدم TestEntityManager لتهيئة البيانات والمستودع للتنفيذ والتحقق:
persistAndFlush() الكيانَ في قاعدة البيانات في الذاكرة فورًا. إن اكتفيت بـ persist() فقد يُؤجّل Hibernate عملية INSERT دُفعةً واحدة وقد يعمل استعلام SELECT التالي قبل وجود الصف، مما يُسبّب فشلًا كاذبًا.
TestEntityManager مقابل المستودع
سؤال شائع: إن كان لديك المستودع، فلماذا تستخدم TestEntityManager أيضًا؟ القاعدة بسيطة:
- استخدم
TestEntityManagerللترتيب (البذر) والتحقق من الحالة الخام. فهو يتجاوز منطق المستودع، لذا فهو مناسب لإعداد المتطلبات المسبقة والتأكيد على ما يُخزَّن فعليًا على مستوى الكيان. - استخدم المستودع باعتباره النظام قيد الاختبار. الأساليب التي تختبرها تنتمي إلى المستودع؛ استدعها في خطوة التنفيذ.
خلط الدورين في خطوة واحدة — كالاستمرار عبر المستودع ثم القراءة بـ em.find() — صحيح أيضًا حين تحتاج إلى التحقق من حالة قاعدة البيانات الخام بعد عملية الحفظ.
التراجع عن المعاملات وعزل الاختبارات
يُغلَّف كل أسلوب اختبار داخل معاملة يتمّ التراجع عنها عند انتهاء الأسلوب. يعني هذا:
- لا تسرّب للبيانات بين الاختبارات — يبدأ كل اختبار بقاعدة بيانات نظيفة.
- لا تحتاج أبدًا لكود تنظيف في
@BeforeEachلحذف الصفوف. - قد يتقدّم عدّاد الزيادة التلقائية (لا يُعيد H2 ضبط التسلسلات عند التراجع)، لذا لا تُقرّ أبدًا بقيم معرّفات محددة.
LazyInitializationException. اختبر سلوك التحميل الكسول والسريع عن قصد، أو هيّئ @Transactional(propagation = REQUIRES_NEW) في أسلوب مساعد لمحاكاة حدود المعاملة الحقيقية.
الاختبار على قاعدة بيانات حقيقية باستخدام @AutoConfigureTestDatabase
قاعدة بيانات H2 في الذاكرة مريحة لكنها ليست دائمًا أمينة. تختلف اللهجات: نوع jsonb في PostgreSQL وفهارس النص الكامل في MySQL والامتدادات JPQL الخاصة بقاعدة بيانات معينة غير موجودة في H2. حين تعتمد استعلاماتك على سلوك خاص بقاعدة البيانات، تجاوز الاستبدال:
ادمج Replace.NONE مع Testcontainers (الدرس التاسع) لتشغيل نسخة حقيقية من PostgreSQL أو MySQL في Docker خلال تشغيل الاختبار — أفضل ما في العالمين: العزل مع أمانة اللهجة الكاملة.
اعتبارات الأداء
نظرًا لأن @DataJpaTest يبني سياقًا أدنى حجمًا، فهو سريع — عادةً أقل من 3 ثوانٍ للاختبار الأول وشبه فوري للاختبارات اللاحقة في نفس التشغيل (يُخزّن Spring سياق التطبيق بين الاختبارات ذات الإعداد نفسه). أبق الشريحة خفيفة:
- لا تضف
@Importلحبوب الخدمات أو وحدات التحكم في@DataJpaTest— هذا يُهزم الغرض. اختبر تلك الطبقات في شرائحها الخاصة. - جمّع الاختبارات التي تشترك في إعداد السياق نفسه في فئة واحدة كي يُعاد استخدام السياق.
- استخدم
@Sqlلمجموعات البيانات الكبيرة بدلًا من استدعاءاتem.persist()المتعددة — فهو أسرع ويجعل مرحلة الترتيب أكثر وضوحًا.
الخلاصة
يمنحك @DataJpaTest شريحة مُركَّزة وسريعة لاختبار المستودعات: قاعدة بيانات H2 في الذاكرة وTestEntityManager للبيانات التجريبية والتراجع التلقائي عن المعاملات بين الاختبارات وتحميل مكوّنات JPA فقط. استخدمه للتحقق من محدّداتك المخصصة واستعلامات JPQL وقيود قاعدة البيانات دون عبء سياق التطبيق الكامل. حين لا تكون H2 أمينة بما يكفي، انتقل إلى Replace.NONE وأشر الشريحة إلى قاعدة بيانات حقيقية أو Testcontainer. في الدرس القادم ستتعلم كيف يتيح لك @MockBean استبدال أي حبة في السياق بمحاكٍ Mockito.