1. نظرة عامة
باستخدام Spring Data JPA ، يمكنك بسهولة إنشاء استعلامات قاعدة البيانات واختبارها باستخدام قاعدة بيانات H2 المضمنة.
لكن في بعض الأحيان يكون الاختبار على قاعدة بيانات حقيقية أكثر فائدة ، خاصة إذا استخدمنا الاستعلامات المرتبطة بتنفيذ قاعدة بيانات محددة.
في هذا الدليل ، سنعرض كيفية استخدام حاويات الاختبار لاختبار التكامل مع قاعدة بيانات Spring Data JPA و PostgreSQL.
في المقالة السابقة ، أنشأنا العديد من استعلامات قاعدة البيانات ، وذلك باستخدام التعليقات التوضيحية Query بشكل أساسي ، والتي نختبرها الآن.
2. التكوين
لاستخدام قاعدة بيانات PostgreSQL في اختباراتنا ، يجب أن نضيف تبعية Testcontainers للاختبارات فقط وبرنامج تشغيل PostgreSQL إلى ملف pom.xml الخاص بنا:
<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.10.6</version> <scope>test</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.5</version> </dependency>
سنقوم أيضًا بإنشاء ملف application.properties في دليل موارد الاختبار ، حيث سنقوم بتعيين استخدام فئة برنامج التشغيل المطلوبة لـ Spring ، بالإضافة إلى إنشاء مخطط قاعدة البيانات وحذفه في كل مرة يتم فيها تشغيل الاختبار:
spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=create-drop
3. وحدة الاختبار
لبدء استخدام مثيل PostgreSQL في فئة اختبار واحدة ، تحتاج إلى إنشاء تعريف حاوية ، ثم استخدام المعلمات الخاصة به لتأسيس الاتصال:
@RunWith(SpringRunner.class) @SpringBootTest @ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.class}) public class UserRepositoryTCIntegrationTest extends UserRepositoryCommonIntegrationTests { @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11.1") .withDatabaseName("integration-tests-db") .withUsername("sa") .withPassword("sa"); static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.username=" + postgreSQLContainer.getUsername(), "spring.datasource.password=" + postgreSQLContainer.getPassword() ).applyTo(configurableApplicationContext.getEnvironment()); } } }
في المثال أعلاه ، استخدمنا @ClassRule
من JUnit لتكوين حاوية قاعدة البيانات قبل تنفيذ طرق الاختبار . أنشأنا أيضًا فئة داخلية ثابتة تطبق ApplicationContextInitializer . أخيرًا ، قمنا بتطبيق التعليق التوضيحي @ContextConfiguration
على فئة الاختبار الخاصة بنا باستخدام فئة التهيئة كمعلمة.
بعد الانتهاء من هذه الخطوات الثلاث ، يمكننا ضبط معلمات الاتصال قبل نشر سياق Spring.
نحن نستخدم الآن استعلامات UPDATE من المقالة السابقة:
@Modifying @Query("update User u set u.status = :status where u.name = :name") int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name); @Modifying @Query(value = "UPDATE Users u SET u.status = ? WHERE u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name);
والاختبار في بيئة وقت التشغيل ضبطها:
@Test @Transactional public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationJPQL_ThenModifyMatchingUsers(){ insertUsers(); int updatedUsersSize = userRepository.updateUserSetStatusForName(0, "SAMPLE"); assertThat(updatedUsersSize).isEqualTo(2); } @Test @Transactional public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationNative_ThenModifyMatchingUsers(){ insertUsers(); int updatedUsersSize = userRepository.updateUserSetStatusForNameNative(0, "SAMPLE"); assertThat(updatedUsersSize).isEqualTo(2); } private void insertUsers() { userRepository.save(new User("SAMPLE", "email@example.com", 1)); userRepository.save(new User("SAMPLE1", "email2@example.com", 1)); userRepository.save(new User("SAMPLE", "email3@example.com", 1)); userRepository.save(new User("SAMPLE3", "email4@example.com", 1)); userRepository.flush(); }
في السيناريو أعلاه ، ينجح الاختبار الأول ، ويلقي الثاني InvalidDataAccessResourceUsageException بالرسالة:
Caused by: org.postgresql.util.PSQLException: ERROR: column "u" of relation "users" does not exist
إذا أجرينا الاختبارات نفسها باستخدام قاعدة بيانات H2 المضمنة ، فستكون كلتاهما ناجحة ، لكن PostgreSQL لا يقبل الأسماء المستعارة في عبارة SET. يمكننا إصلاح الطلب بسرعة عن طريق إزالة الاسم المستعار الإشكالي:
@Modifying @Query(value = "UPDATE Users u SET status = ? WHERE u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name);
هذه المرة اجتاز الاختباران بنجاح. في هذا المثال ، استخدمنا Testcontainers لتحديد مشكلة في استعلام أصلي لا يمكن اكتشافه إلا بعد الانتقال إلى قاعدة بيانات الإنتاج . تجدر الإشارة أيضًا إلى أن استخدام استعلامات JPQL أكثر أمانًا بشكل عام ، لأن Spring يقوم بترجمتها بشكل صحيح وفقًا لموفر قاعدة البيانات المستخدم.
4. مثيل قاعدة البيانات المشتركة
في القسم السابق ، وصفنا كيفية استخدام حاويات الاختبار في اختبار واحد. في الحالات الحقيقية ، أود استخدام حاوية قاعدة البيانات نفسها في العديد من الاختبارات بسبب وقت الإطلاق الطويل نسبيًا.
لنقم بإنشاء فئة شائعة لإنشاء حاوية قاعدة بيانات عن طريق توريث PostgreSQLContainer وتجاوز أساليب start () و stop () :
public class BaeldungPostgresqlContainer extends PostgreSQLContainer<BaeldungPostgresqlContainer> { private static final String IMAGE_VERSION = "postgres:11.1"; private static BaeldungPostgresqlContainer container; private BaeldungPostgresqlContainer() { super(IMAGE_VERSION); } public static BaeldungPostgresqlContainer getInstance() { if (container == null) { container = new BaeldungPostgresqlContainer(); } return container; } @Override public void start() { super.start(); System.setProperty("DB_URL", container.getJdbcUrl()); System.setProperty("DB_USERNAME", container.getUsername()); System.setProperty("DB_PASSWORD", container.getPassword()); } @Override public void stop() {
مع ترك طريقة الإيقاف () فارغة ، يمكننا تمكين JVM من معالجة إتمام الحاوية من تلقاء نفسها. ننفذ أيضًا مفردًا بسيطًا ، حيث يبدأ الاختبار الأول فقط في الحاوية ، ويستخدم كل اختبار لاحق مثيلًا حاليًا. في طريقة start () ، نستخدم System # setProperty لحفظ معلمات الاتصال لمتغيرات البيئة.
الآن يمكننا كتابتها في ملف application.properties :
spring.datasource.url=${DB_URL} spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD}
الآن نستخدم فئة المنفعة في تعريف الاختبار:
@RunWith(SpringRunner.class) @SpringBootTest public class UserRepositoryTCAutoIntegrationTest { @ClassRule public static PostgreSQLContainer postgreSQLContainer = BaeldungPostgresqlContainer.getInstance();
كما في الأمثلة السابقة ، قمنا بتطبيق التعليق التوضيحي ClassRule على الحقل باستخدام تعريف الحاوية. وبالتالي ، يتم تعبئة معلمات اتصال DataSource بالقيم الصحيحة قبل إنشاء سياق Spring.
الآن يمكننا تنفيذ العديد من الاختبارات باستخدام نفس مثيل قاعدة البيانات من خلال تعيين الحقل ببساطة مع تعليق توضيحي ClassRule تم إنشاؤه باستخدام فئة الأدوات المساعدة BaeldungPostgresqlContainer الخاصة بنا .
5. الخاتمة
في هذه المقالة ، أظهرنا طرق اختبار في قاعدة بيانات الإنتاج باستخدام Testcontainers.
لقد درسنا أيضًا أمثلة لاستخدام اختبار واحد باستخدام آلية ApplicationContextInitializer من Spring ، بالإضافة إلى تطبيق فصل لإعادة استخدام مثيل قاعدة البيانات.
لقد أظهرنا أيضًا كيف يمكن لـ Testcontainers المساعدة في تحديد مشكلات التوافق بين موفري قواعد البيانات المتعددة ، وخاصةً للاستعلامات الأصلية.
كما هو الحال دائمًا ، يتوفر الرمز الكامل المستخدم في هذه المقالة على GitHub .