كيفية تجميع صورة Oracle DB لـ Testcontainers

يجب اختبار الرمز على نظام إدارة قواعد البيانات (DBMS) الذي سيعمل به. Testcontainers هي مكتبة تتيح لك استخدام أي قاعدة بيانات تقريبًا في اختبارات الوحدة بنفس سهولة قواعد البيانات المدمجة مثل HSQLDB أو H2. لن يكون هناك سوى صورة عامل ميناء



هذه المقالة مخصصة لتجميع صورة عامل ميناء ملائمة للاستخدام مع Testcontainers. عندما حاولت القيام بذلك ، واجهت مشاكل ، وهنا أشارك حل بلدي.
سأجمع الصورة الخاصة بـ Oracle 11 ، لأنها صغيرة ولديّ نسخة كافية 11. مع الإصدارات الأخرى ، فإن النهج هو نفسه تقريبا.


لتوضيح كيفية استخدام الصورة ، سيكون هناك أيضًا كود Java يوضح استخدام الصورة لاختبار تطبيقات Spring Boot. طريقة الاتصال بحاويات الاختبار التي قدمتها ربما ليست هي الأفضل. ولكن أولاً ، يوضح كيفية استخدام الإعدادات المحددة عند إنشاء الصورة. ثانيا ، الأمر بسيط. وثالثا ، ليس مرتبطًا بـ Spring تقريبًا ، بل يمكن تعليقه في تعليمات Java البرمجية ، التي لا يوجد فيها سوى فراغ ثابت عام رئيسي.


من المفترض أن يكون القارئ على دراية سطحية بـ Docker و Testcontaners ، ويعرف أيضًا جافا جيدًا. للبناء ، تحتاج إلى استخدام linux ، إذا كنت تقوم بالبناء تحت Windows ، فستحتاج إلى استخدام msys2 أو شيء من هذا القبيل.


يتم تحميل رمز العرض التوضيحي إلى github هنا https://github.com/poxu/testcontainers-spring-demo يمكن الاطلاع على البرامج النصية الصحيحة لتجميع الصورة في مفترقتي لتعليمات Oraklov https://github.com/poxu/docker-images/tree/ ماجستير / OracleDatabase / SingleInstance


بناء صورة عامل الميناء


لا يوفر Oracle صورًا لرسو السفن ، ولكنه نشر إرشادات مفصلة حول كيفية تجميعها على جيثب.


لسوء الحظ ، لا يمكن استخدام هذه الصور في حاويات الاختبار لأن الحاوية التي يتم إطلاقها من هذه الصورة تبدأ من دقيقتين إلى 20 دقيقة.


للاستخدام في اختبارات الوحدات ، هذا أمر غير مقبول ، لذلك تحتاج إلى إجراء التغييرات الخاصة بك على البرامج النصية ، ولكن من الأفضل أولاً محاولة تجميع الحاوية وفقًا للتعليمات التي توفرها Oracle. سأجعل رواية موجزة هنا ، يوجد تعليمات أكثر اكتمالا على هذا الرابط https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance


تجميع الصور وفقًا لتعليمات Oracle


أولاً ، عليك استنساخ المستودع بتعليمات حول كيفية تجميع الصورة.


git clone https://github.com/oracle/docker-images.git 

ثم احصل على حزمة rpm للنسخة السريعة المأذون بها من Oracle 11.2.0.2 ، إنها ليست صعبة للغاية ، فأنت تحتاج فقط إلى التسجيل على موقع Oracle على الويب ، وانتقل إلى صفحة تنزيل Oracle DBMS ، وحدد الإصدار 11.2.0.2 XE هناك ، وقم بتنزيل ملف rpm المحزوم oracle-xe-11.2.0 -1.0.x86 ~ 64 ~ .rpm.zip.


ضع الملف في مستودع git الذي تم تنزيله في الدليل docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 /


بعد ذلك ، انتقل إلى دليل docker-images / OracleDatabase / SingleInstance / dockerfiles وقم بتشغيل الأمر


 ./buildDockerImage.sh -v 11.2.0.2 -x 

سيقوم عامل التجميع بتجميع صورة تسمى oracle / database: 11.2.0.2-xe بناءً على ما تحتاج إليه لإنشاء الحاوية باستخدام هذا الأمر


 docker run --rm --name vanilla_oracle_test_habr \ -p 1234:1521 \ -p 5678:5500 \ -e ORACLE_PWD=123 \ --shm-size="1g" \ oracle/database:11.2.0.2-xe 

تبدأ الحاوية بضع دقائق ، لأنه بعد البدء في إنشاء قاعدة بيانات جديدة ، وهذه العملية ليست سريعة.


بعد بضع دقائق ، ستظهر لافتة في وحدة التحكم


#########################
قاعدة البيانات جاهزة للاستخدام!
#########################

بعد ذلك ، يمكنك الاتصال بقاعدة البيانات باستخدام تسجيل الدخول SYSTEM ، وكلمة المرور هي 123 ، وعنوان الاتصال هو المضيف المحلي و SID هو XE.


إذا نجح كل شيء ، فيمكنك المتابعة إلى عملية إنشاء صورة تحت حاويات الاختبار. إذا لم يكن الأمر كذلك ، فمن الأفضل أن تذهب أولاً من خلال الدليل من Oracle ومعرفة الخطأ.


كما اكتشفنا بالفعل ، تبدأ الحاوية لفترة طويلة بسبب حقيقة أنه بعد البدء يتم إنشاء قاعدة بيانات. في بعض الحالات ، قد يكون هذا مناسبًا ، لكن الآن يتسبب ذلك في ضرر تام. من الضروري جعل الحاوية جاهزة للاستخدام فورًا بعد الإطلاق.


تحسين الصورة اليدوية


تتمثل إحدى طرق الحصول على الصورة من قاعدة البيانات النهائية في الانتظار حتى يتم بدء تشغيل الحاوية وإكمال إنشاء قاعدة البيانات ، ثم حفظ الحاوية في صورة جديدة.


من المهم فقط عدم بدء تشغيل الحاوية بالوسيطة - rm ، وإلا فسيقوم عامل النقل بضربها فور إيقافها.


 docker commit --message "container for unit tests" <container id> my/oracle-11.2.0.2-for-unit-tests 

سيؤدي ذلك إلى إنشاء صورة جديدة من الحاوية ، والتي سيتم إطلاقها ليس فقط لبضع دقائق ، ولكن من 20 إلى 30 ثانية.


تعديل عملية تجميع الصور من أجل الحصول على صورة منتهية فور التجميع


بالطبع ، من الجيد أن يكون لديك تعليمات لتجميع الصورة في شكل رمز بحيث يمكنك بدء التجميع بأمر واحد ولا حاجة إلى الانتظار حتى تبدأ الحاوية في إنشاء صورة تستند إليها بيديك.


يشير السطر الأخير من dockerfile إلى الأمر الذي يتم تنفيذه بعد البداية


 CMD exec $ORACLE_BASE/$RUN_FILE 

\ $ ORACLE ~ BASE ~ / \ $ RUN ~ FILE ~ يشير إلى ملف docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 / runOracle.sh


إذا قمت بإيقاف هذه الحاوية ثم تشغيلها مرة أخرى ، كنتيجة ، لأول مرة ، سينشئ البرنامج النصي قاعدة البيانات ، وفي المرة الثانية سيبدأ تشغيله ببساطة. يمكن افتراض أنه إذا قمت بتنفيذ البرنامج النصي في مرحلة تجميع الصورة ، فسيتم تجميع الصورة من قاعدة البيانات التي تم إنشاؤها بالفعل.


ولكن في الطريق إلى تنفيذ هذه الخطة الجريئة ، تنشأ تعقيد واحد.


يتم تشغيل البرنامج النصي إلى الأبد ، أي إلى أن تصل الإشارة إلى الحاوية التي تحتاج إلى إكمال العمل. تم حل هذا بكل بساطة. يحتوي السطر الأخير من ملف runOracle.sh على أمر الانتظار. نحن نعلم أنه في مرحلة التجميع لا تحتاج إلى الانتظار لأي شيء ، تحتاج إلى إنهاء العمل وبالتالي سنضع بيانًا مشروطًا في البرنامج النصي.


سيتم التحقق مما إذا لم يتم تمرير الوسيطة --running-بينما - بناء إلى الملف وإذا تم تمرير هذه الوسيطة ، ثم لا تنتظر أي إشارات ، ولكن ببساطة مقاطعة العمل. وهذا هو ، تفعل مثل هذا:


 if [ "$1" != "--running-while-building" ] then wait $childPID fi 

حسنًا ، في dockerfile نضيف مكالمة نصية أخرى ، فقط في مرحلة التجميع. سوف تتحول مثل هذا.


 RUN $ORACLE_BASE/$RUN_FILE --running-while-building CMD exec $ORACLE_BASE/$RUN_FILE 

التغييرات المطلوبة للاستخدام في الاختبار


القضاء على استخدام حجم


بحاجة الى ايجاد الخط


المجلد ["\ $ ORACLE ~ BASE ~ / oradata"]

والتعليق عليها. ليست هناك حاجة لاستخدام وحدات التخزين ، لأن كل التغييرات سيتم طرحها بعد كل تشغيل تجريبي ، ولكن يمكن أن تنشأ مشاكل عند استخدام وحدات التخزين بسهولة عند نسخ الصور.


حذف الملفات غير الضرورية


تحتاج إلى إضافة خطوط


 rm -rf $ORACLE_HOME/demo && \ rm -rf $ORACLE_HOME/jdbc && \ rm -rf $ORACLE_HOME/jlib && \ rm -rf $ORACLE_HOME/md && \ rm -rf $ORACLE_HOME/nls/demo && \ rm -rf $ORACLE_HOME/odbc && \ rm -rf $ORACLE_HOME/rdbms/jlib && \ rm -rf $ORACLE_HOME/rdbms/public && \ rm -rf $ORACLE_HOME/rdbms/demo && \ rm -rf $ORACLE_HOME/bin/rman && \ 

فقط قبل الخط


 chmod ug+x $ORACLE_BASE/*.sh 

سيؤدي ذلك إلى إزالة جميع الملفات غير اللازمة لأغراض الاختبار من الصورة. أصغر الصورة ، كان ذلك أفضل.


إزالة تقسيم الصورة


لتقليل الصورة ، تحتاج إلى إنشائها باستخدام وسيطة الاسكواش . سيؤدي ذلك إلى إزالة الفصل إلى طبقات من الصورة ، مما يقلل من حجمه. تعتبر حجة الاسكواش تجريبية ، لذلك عليك تمكينها بشكل منفصل. في أنظمة التشغيل المختلفة ، يتم ذلك بطرق مختلفة.


لإعادة توجيه الوسائط إلى عامل الإرساء ، يوفر عامل الإلحاق بالصور / OracleDatabase / SingleInstance / dockerfiles / buildDockerImage.sh الوسيطة -o. هذا هو ، لإعادة توجيه وسيطة - الحشو إلى عامل الميناء ، تحتاج إلى استدعاء buildDockerImage.sh مثل هذا


 ./buildDockerImage.sh -o '--squash' 

تغيير اسم الصورة


حتى الآن ، تختلف الصورة اختلافًا كبيرًا عما تقترحه Oracle ، لذلك يجب إعادة تسميته. للقيام بذلك ، تحتاج بالفعل إلى تحرير ملف buildDockerImage.sh نفسه ، يأخذ البرنامج النصي اسم الصورة من متغير IMAGE ~ NAME ~ ، حيث يتم تعيين القيمة مباشرة في الملف.


هنا


 # Oracle Database Image Name IMAGE_NAME="oracle/database:$VERSION-$EDITION" 

لقد غيرت ل


 # Oracle Database Image Name IMAGE_NAME="my/oracle-for-habr:$VERSION-$EDITION" 

تعيين كلمة مرور DB


تم تعيين كلمة المرور هذه في متغير البيئة ORACLE ~ PWD ~ أثناء البداية الأولى للحاوية. لكن لدينا إعداد قاعدة البيانات أثناء إنشاء الصور ، لذلك يجب تحديد المتغير في هذه المرحلة. إذا لم تكن هناك حاجة إلى تعيين كلمة مرور لكل مجموعة من خلال سطر الأوامر ، يمكنك ببساطة إدخالها في dockerfile:


 ENV ORACLE_PWD=123 

إذا احتجت إلى شيء ما لتتمكن من تحديد كلمة المرور مرة أخرى مع كل بنية ، ثم لإعادة توجيه الوسيطة إلى عامل الميناء ، يمكنك مرة أخرى استخدام -o


 ./buildDockerImage.sh -v 11.2.0.2 -x -o '--squash --build-arg ORACLE_PWD=123' 

سيؤدي هذا إلى نقل متغير البيئة ORACLE ~ PWD ~ إلى dockerfile ، ولكن لن يقوم dockerfile بتمريره إلى البرامج النصية التي تعمل أثناء الإنشاء. لكي يقوم بذلك ، أضف تعليمة ARG إلى dockerfile.


 ARG ORACLE_PWD=default 

ستكون كلمة المرور ، كما قد تصبح واضحة بالفعل ، 123 ، وإذا لم تقم بتمرير ORACLE ~ PWD ~ إلى buildDockerImage.sh ثم الافتراضي.


في بعض الأحيان ، تعتقد Oracle أن كلمة المرور سيئة ولا ترغب في العمل ، لذلك قد يكون من الضروري استبدال 123 بشيء آخر


اختبار رفع الصورة الناتجة


الآن يمكنك محاولة تشغيل الحاوية على أساس الصورة


 docker run --rm --name dockertest_habr \ -p 1234:1521 \ -p 5678:5500 \ --shm-size="1g" \ my/oracle-for-habr:11.2.0.2-xe 

الوسيطة --shm-size = "1g" مهمة هنا ، بدونها تبدأ الحاوية ، لكن Oracle 11.2.0.2 نفسه لا يمكن أن يعمل. هذا ، فقط في حالة ، لا يعني أن الحاوية ستحتاج إلى غيغابايت من ذاكرة الوصول العشوائي ، فهي تستهلك حوالي 100 ميجابايت.


إذا ارتفعت الحاوية بشكل طبيعي ، فيمكنك محاولة الاتصال بقاعدة البيانات الموجودة هناك.


العنوان الأساسي - على الأرجح المضيف المحلي
ميناء - 1234
نظام المستخدم
كلمة المرور - 123


إذا بدأت بشكل طبيعي ، فيمكنك المتابعة إلى الخطوة التالية.


النصي التهيئة DB


حتى يتمكن البرنامج من العمل مع قاعدة البيانات من الصورة ، من الضروري وجود دائرة هناك بعد الإطلاق. يمكنك إنشاء هذه الدائرة في مرحلة الإنشاء ، لكنني أفضل القيام بذلك عند بدء تشغيل الحاوية.


بعد البدء ، ستقوم الحاوية بالبحث في الدليل u01 / app / oracle / scripts / startup وتنفيذ جميع البرامج النصية sql التي تعثر عليها هناك ، والتي يمكنك استخدامها بوضع الملف هناك الذي سيؤدي إلى إنشاء الدائرة. شيء من هذا القبيل.


 CREATE USER TEST_USER IDENTIFIED BY passwordnoquotes; ALTER USER TEST_USER QUOTA unlimited ON SYSTEM; GRANT CREATE SESSION, CONNECT, RESOURCE, DBA TO TEST_USER; GRANT ALL PRIVILEGES TO TEST_USER; 

كل هذا يحتاج إلى إضافته إلى ملف init ~ db ~ .sql ، ويتم طرح الملف في الحاوية باستخدام -v


 docker run --rm --name dockertest_habr \ -p 1234:1521 \ -p 5678:5500 \ -e ORACLE_PWD=123 \ -v ${PWD}/init_db.sql:/u01/app/oracle/scripts/startup/init_db.sql \ --shm-size="1g" \ my/oracle-for-habr:11.2.0.2-xe 

\ $ {PWD} هنا يتم استخدامه لأن المسار المطلق للملف مطلوب ، عند استخدام Windows ، تحتاج إلى تحديده بطريقة مختلفة بطريقة ما. إذا تم إنشاء مخطط TEST ~ USER ~ بنجاح بعد البدء ، فيمكنك المتابعة لربط الحاوية التي تم إنشاؤها حديثًا بالاختبارات.


باستخدام صورة في كود جافا


عند اختبار استخدام قاعدة البيانات المدمجة ، كقاعدة عامة ، تنشأ نفس المشكلة. إذا تعذر أخذ التكوين من ذاكرة التخزين المؤقت ، فسيجمعه Spring مرة أخرى. على وجه الخصوص ، تعيد إنشاء قاعدة البيانات المدمجة ، والتي بالطبع تبطئ بشكل خطير الاختبارات. لقد قمت بحل مشكلة القوة الغاشمة من خلال جعل جزء رفع الحاوية منفردًا. حقيقي واحد ، الشقة.


بالنسبة إلى Oracle XE ، تحتوي Testcontainers على فصل دراسي مُعد خصيصًا. بادئ ذي بدء ، يعرف هذا الفصل أننا نتحدث عن حاوية مع DBMS وأنه من أجل تحديد رفعها ، يجب أن نحاول الاتصال بقاعدة البيانات باستخدام jdbc.


سينتظر كائن من هذه الفئة رفع الحاوية من تلقاء نفسه ، وتحتاج فقط إلى إخبارها بسجلات باستخدام كلمة مرور للاستخدام.


 import org.testcontainers.containers.BindMode; import org.testcontainers.containers.OracleContainer; public class StaticOracleContainer { public static OracleContainer getContainer() { return LazyOracleContainer.ORACLE_CONTAINER; } private static class LazyOracleContainer { private static final OracleContainer ORACLE_CONTAINER = makeContainer(); private static OracleContainer makeContainer() { //       testcontainers.properties //  oracle.container.image final var dockerImageName = "my/oracle-for-habr"; final var container = new OracleContainer(dockerImageName) //    ,  testcontainers //  ,  ,  //   .withUsername("SYSTEM").withPassword("123") // ,  testcontainers  //     .withExposedPorts(1521, 5500) //      shared memory //    .withSharedMemorySize(2147483648L) //   ,       // -v /path/to/init_db.sql:/u01/app/oracle/scripts/startup/init_db.sql //    init_db.sql   .withClasspathResourceMapping("init_db.sql" , "/u01/app/oracle/scripts/startup/init_db.sql" , BindMode.READ_ONLY); container.start(); return container; } } } 

أيضًا ، تقوم حاويات الاختبار ، عند إطلاقها ، بتعيين المنافذ الداخلية للحاوية إلى منافذ خارجية غير مأهولة محددة عشوائيًا. لذلك ، لا يمكنك الخوف من أن الحاوية لن ترتفع ، لأن المنفذ يستخدم بالفعل من قبل شخص ما. يمكن الحصول على منفذ خارجي من الحاوية باستخدام طريقة getOraclePort ().


يمكنك أيضًا الحصول على عنوان الحاوية باستخدام طريقة getContainerIpAddress () ، ولكن تحتوي أي حاوية على هذه الطريقة.


بعد أول مكالمة إلى getContainer ، لن يتم إعادة إنشاء الحاوية ، ولكن سيتم إرجاع الحاوية الحالية. يمكن الآن استخدام هذه الطريقة في Java العارية أو في تهيئة Spring للحصول على كائن به حاوية يمكنك من خلالها سحب المنافذ وعنوان الاتصال.


على سبيل المثال ، يمكنك إنشاء مُهيئ ، والذي عند رفع السياق ، سيتخطى سمات الربيع المسؤولة عن الاتصال بقاعدة البيانات.


فئة لربط حاويات Test to Spring


بعد رفع الحاوية ، يستبدل المُهيِّج الخصائص المسؤولة عن عنوان URL للاتصال بقاعدة البيانات وتسجيل الدخول وكلمة المرور وكل ذلك.


ولكن ، إذا لم يكن هناك إعداد evilcorp.testcontainers.enabled في application.properties ، فلن يتم رفع الحاوية وسيعمل كل شيء كما لو أنه لا يوجد أحد متصل بـ testcontainers.


 package com.evilcorp.demo; import org.junit.platform.commons.logging.Logger; import org.junit.platform.commons.logging.LoggerFactory; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.testcontainers.containers.OracleContainer; public class TestcontainersInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { static Logger log = LoggerFactory.getLogger(TestcontainersInitializer.class); @Override public void initialize(ConfigurableApplicationContext applicationContext) { // ,   Testcontainers final String testcontainersEnabled = applicationContext.getEnvironment().getProperty("evilcorp.testcontainers.enabled"); if (!"true".equals(testcontainersEnabled)) { return; } OracleContainer oracleContainer = StaticOracleContainer.getContainer(); oracleContainer.followOutput(s -> log.debug(() -> s.getUtf8String())); // IP       //  ,    // (linux, MacOs, Windows  ) , //    oracleContainer.getContainerIpAddress() //    // // Testcontainers     //   ,    oracleContainer.getOraclePort() //  ,         final String jdbcUrl = "jdbc:oracle:thin:@//" + oracleContainer.getContainerIpAddress() + ":" + oracleContainer.getOraclePort() + "/XE"; //      init_db.sql //      final String user = "TEST_USER"; final String password = "passwordnoquotes"; TestPropertyValues.of( "spring.jpa.properties.hibernate.default_schema=" + user, "spring.datasource.driver-class-name=oracle.jdbc.OracleDriver", "spring.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect", "spring.datasource.username=" + user, "spring.datasource.password=" + password, "spring.datasource.url=" + jdbcUrl, "spring.liquibase.url=" + jdbcUrl, "spring.liquibase.user=" + user, "spring.liquibase.password=" + password ).applyTo(applicationContext.getEnvironment(), TestPropertyValues.Type.MAP, "test"); } } 

يمكن استخدام هذا التكوين في اختبار التمهيد الربيع للكتابة فوق إعدادات قاعدة البيانات أثناء الطيران.


اختبار باستخدام Testcontainers


الاختبار ببساطة يكتب كائن إلى قاعدة البيانات ، ثم يقرأ ، لا شيء خاص.


 package com.evilcorp.demo; import com.evilcorp.demo.entity.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.ContextConfiguration; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; @SpringBootTest @ContextConfiguration(initializers = {TestcontainersInitializer.class}) class TestcontainersSpringDemoApplicationTests { @Autowired UserRepository userRepository; private User createdUser; @BeforeEach void setUp() { createdUser = new User(); createdUser.setName("Fry"); userRepository.save(createdUser); } @Test void userRepositoryLoaded() { assertNotNull(userRepository); } @Test void userAdded() { final Optional<User> loadedUser = userRepository.findById(createdUser.getUserId()); assertTrue(loadedUser.isPresent()); assertEquals("Fry", loadedUser.get().getName()); assertNotSame(createdUser, loadedUser.get()); } } 

ونعم ، أضف تبعيات إلى pom.xml


  <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.12.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>oracle-xe</artifactId> <version>1.12.3</version> <scope>test</scope> </dependency> 

هذا حول كيفية إنشاء صورة لرسو السفن Oracle DBMS واستخدامها في تعليمات Java البرمجية. يبقى فقط وضع الصورة في مستودع الشركة من القطع الأثرية وترتيب إطلاق الاختبارات داخل حاوية أخرى لرسو السفن. لكن هذه قصة مختلفة تماما.

Source: https://habr.com/ru/post/ar480106/


All Articles