So stellen Sie ein Oracle DB-Image für Testcontainer zusammen

Der Code sollte auf dem DBMS getestet werden, mit dem er funktioniert. Testcontainers ist eine Bibliothek, mit der Sie nahezu jedes DBMS in Komponententests genauso einfach verwenden können wie eingebettete Datenbanken wie HSQLDB oder H2. Es würde nur ein Docker-Image geben



Dieser Artikel befasst sich mit der Erstellung eines Docker-Images, das für die Verwendung mit Testcontainern geeignet ist. Als ich versuchte, es zu schaffen, hatte ich Probleme und hier teile ich meine Lösung.
Ich werde das Image für Oracle 11 sammeln, da es klein ist und ich genug Version 11 habe. Bei anderen Versionen ist der Ansatz ungefähr gleich.


Um zu verdeutlichen, wie das Image verwendet wird, wird auch Java-Code bereitgestellt, der die Verwendung des Images zum Testen von Spring Boot-Anwendungen demonstriert. Die Methode zum Verbinden mit Testcontainern, die ich angegeben habe, ist wahrscheinlich nicht die beste. Zunächst zeigt er jedoch, wie die beim Erstellen des Abbilds festgelegten Einstellungen verwendet werden. Zweitens ist es einfach. Und drittens ist es fast nicht an Spring gebunden, es kann sogar in Java-Code stecken bleiben, in dem es nichts als öffentliche statische Leerstellen gibt.


Es wird davon ausgegangen, dass der Leser mit Docker und Testcontanern oberflächlich vertraut ist und auch Java gut kennt. Zum Erstellen müssen Sie Linux verwenden. Wenn Sie unter Windows erstellen, müssen Sie msys2 oder ähnliches verwenden.


Der Demo-Code wird hier auf den Github hochgeladen. Https://github.com/poxu/testcontainers-spring-demo Korrigierte Skripte zum Zusammenstellen des Bildes finden Sie in meinem Fork der Oraklov-Anleitung. Https://github.com/poxu/docker-images/tree/ master / OracleDatabase / SingleInstance


Erstellen Sie ein Docker-Image


Oracle stellt keine Bilder für Docker zur Verfügung, hat jedoch detaillierte Anweisungen zur Montage auf dem Github veröffentlicht.


Leider ist es nicht möglich, diese Images in Testcontainern als zu verwenden, da der Container, der von diesem Image gestartet wird, zwischen zwei und 20 Minuten startet.


Dies ist für die Verwendung in Komponententests nicht akzeptabel. Daher müssen Sie Ihre eigenen Änderungen an den Skripten vornehmen. Zunächst ist es jedoch besser, den Container gemäß den Anweisungen von Oracle zusammenzustellen. Ich werde hier kurz nacherzählen. Eine ausführlichere Anleitung finden Sie unter folgendem Link: https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance


Image-Zusammenstellung nach Anleitung von Oracle


Zunächst müssen Sie das Repository mit Anweisungen zum Zusammenstellen des Images klonen.


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

Dann besorgen Sie sich das RPM-Paket für die autorisierte Express-Version von Oracle 11.2.0.2. Es ist nicht sehr schwierig, Sie müssen sich lediglich auf der Oracle-Website registrieren, die Oracle DBMS-Downloadseite aufrufen, dort die Version 11.2.0.2 XE auswählen und die gepackte RPM-Datei oracle-xe-11.2.0 herunterladen -1.0.x86 ~ 64 ~ .rpm.zip.


Legen Sie die Datei im heruntergeladenen Git-Repository im Verzeichnis docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 / ab.


Wechseln Sie als Nächstes in das Verzeichnis docker-images / OracleDatabase / SingleInstance / dockerfiles und führen Sie den Befehl aus


 ./buildDockerImage.sh -v 11.2.0.2 -x 

Der Docker stellt ein Bild mit dem Namen oracle / database: 11.2.0.2-xe zusammen, auf dessen Grundlage Sie den Container mit diesem Befehl erstellen müssen


 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 

Der Container startet einige Minuten, da nach dem Start eine neue Datenbank erstellt wird und dieser Vorgang nicht schnell ist.


Nach einigen Minuten erscheint ein Banner in der Konsole


############################
DATENBANK IST BEREIT!
############################

Danach können Sie mit dem SYSTEM-Login eine Verbindung zur Datenbank herstellen, das Passwort lautet 123, die Verbindungsadresse lautet localhost und die SID lautet XE.


Wenn alles geklappt hat, können Sie mit dem Erstellen eines Images unter Testcontainern fortfahren. Wenn nicht, ist es besser, zuerst das Handbuch von Oracle zu lesen und herauszufinden, was nicht stimmt.


Wie wir bereits festgestellt haben, startet der Container lange, da nach dem Start eine Datenbank erstellt wird. In einigen Fällen mag dies wahrscheinlich praktisch sein, aber jetzt verursacht dies völligen Schaden. Der Container muss sofort nach dem Start einsatzbereit sein.


Manuelle Bildveredelung


Eine Möglichkeit, das Bild aus der fertigen Datenbank abzurufen, besteht darin, zu warten, bis der Container startet und die Erstellung der Datenbank abgeschlossen ist, und dann den Container in einem neuen Bild zu speichern.


Es ist nur wichtig, den Container nicht mit dem Argument --rm zu starten, da der Docker ihn sonst sofort nach dem Stoppen schlägt.


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

Dadurch wird ein neues Image aus dem Container erstellt, das nicht nur einige Minuten, sondern 20 - 30 Sekunden lang gestartet wird.


Änderung des Image-Assemblierungsprozesses, um ein fertiges Image unmittelbar nach dem Zusammenbau zu erhalten


Natürlich ist es gut, Anweisungen zum Zusammenstellen des Abbilds in Form von Code zu haben, damit Sie die Zusammenstellung mit einem Befehl starten können und nicht warten müssen, bis der Container gestartet ist und ein Abbild basierend auf diesem mit Ihren Händen erstellen kann.


Die letzte Zeile der Andockdatei gibt den Befehl an, der nach dem Start ausgeführt wird


 CMD exec $ORACLE_BASE/$RUN_FILE 

\ $ ORACLE ~ BASE ~ / \ $ RUN ~ FILE ~ verweist auf die Datei docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 / runOracle.sh


Wenn Sie diesen Container stoppen und dann erneut ausführen, erstellt das Skript die Datenbank zum ersten Mal und startet sie beim zweiten Mal einfach. Es kann davon ausgegangen werden, dass das Image aus der bereits erstellten Datenbank zusammengesetzt wird, wenn Sie das Skript in der Phase der Image-Montage ausführen.


Auf dem Weg zur Umsetzung dieses mutigen Plans entsteht jedoch eine Komplikation.


Das Skript wird für immer ausgeführt, dh bis ein Signal im Container eintrifft, dass die Arbeit abgeschlossen werden muss. Das ist ganz einfach gelöst. Die letzte Zeile der Datei runOracle.sh enthält den Befehl wait. Wir wissen, dass Sie in der Montagephase nicht auf irgendetwas warten müssen, sondern die Arbeit beenden müssen, und deshalb werden wir eine bedingte Anweisung in das Skript einfügen.


Es wird überprüft, ob das Argument --running-while-building nicht an die Datei übergeben wird. Wenn dieses Argument übergeben wird, warten Sie nicht auf Signale, sondern unterbrechen Sie einfach die Arbeit. Das heißt, mach das so:


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

Nun, in der Docker-Datei fügen wir einen weiteren Skriptaufruf nur in der Assembler-Phase hinzu. Es wird sich so herausstellen.


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

Änderungen, die zur Verwendung beim Testen erforderlich sind


Vermeiden Sie die Verwendung von Volumen


Muss die Linie finden


VOLUME ["\ $ ORACLE ~ BASE ~ / oradata"]

Und kommentiere es. Die Verwendung von Volumes ist nicht erforderlich, da alle Änderungen nach jedem Testlauf übernommen werden. Beim Kopieren von Bildern können jedoch Probleme mit der Verwendung von Volumes auftreten.


Löschen Sie nicht benötigte Dateien


Müssen Zeilen hinzufügen


 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 && \ 

Kurz vor der Linie


 chmod ug+x $ORACLE_BASE/*.sh 

Dadurch werden alle Dateien aus dem Image entfernt, die nicht zu Testzwecken benötigt werden. Je kleiner das Bild, desto besser.


Entfernen Sie die Bildteilung


Um das Bild zu verkleinern, müssen Sie es mit dem Squash- Argument erstellen. Dadurch wird die Trennung in Ebenen aus dem Bild entfernt, wodurch das Volumen weiter verringert wird. Das Squash-Argument ist experimentell, Sie müssen es also separat aktivieren. In verschiedenen Betriebssystemen wird dies auf unterschiedliche Weise durchgeführt.


Um Argumente an den Docker weiterzuleiten, stellt docker-images / OracleDatabase / SingleInstance / dockerfiles / buildDockerImage.sh das Argument -o bereit. Das heißt, um das Argument --squash an den Docker weiterzuleiten, müssen Sie buildDockerImage.sh folgendermaßen aufrufen


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

Bildnamen ändern


Bisher unterscheidet sich das Image erheblich von dem, was Oracle vorschlägt. Daher muss es umbenannt werden. Dazu müssen Sie bereits die Datei buildDockerImage.sh selbst bearbeiten. Das Skript übernimmt den Namen des Bildes aus der Variablen IMAGE ~ NAME ~, deren Wert direkt in der Datei festgelegt wird


Genau hier


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

Ich wechselte zu


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

Legen Sie das DB-Passwort fest


Dieses Passwort wird beim ersten Start des Containers in der Umgebungsvariablen ORACLE ~ PWD ~ festgelegt. Da die Datenbank jedoch während der Image-Erstellung eingerichtet wird, muss die Variable zu diesem Zeitpunkt definiert werden. Wenn die Möglichkeit zum Festlegen eines Kennworts für jede Assembly über die Befehlszeile nicht erforderlich ist, können Sie es einfach in die Docker-Datei eingeben:


 ENV ORACLE_PWD=123 

Wenn Sie in der Lage sein müssen, das Kennwort bei jedem Build erneut zu ermitteln, können Sie -o erneut verwenden, um das Argument an den Docker weiterzuleiten


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

Dadurch wird die Umgebungsvariable ORACLE ~ PWD ~ in die Docker-Datei übertragen, aber die Docker-Datei übergibt sie nicht an Skripten, die während der Erstellung ausgeführt werden. Dazu müssen Sie die ARG-Anweisung zur Docking-Datei hinzufügen.


 ARG ORACLE_PWD=default 

Das Passwort, wie es wahrscheinlich bereits klar geworden ist, lautet 123. Wenn Sie ORACLE ~ PWD ~ nicht an buildDockerImage.sh übergeben, ist es das Standardkennwort.


Manchmal glaubt Oracle, dass das Kennwort falsch ist und nicht funktionieren soll. Daher muss 123 möglicherweise durch etwas anderes ersetzt werden


Testen Sie das resultierende Bild


Jetzt können Sie versuchen, den Container basierend auf dem Image auszuführen


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

Das Argument --shm-size = "1g" ist hier wichtig, ohne das der Container startet, aber Oracle 11.2.0.2 selbst kann nicht funktionieren. Dies bedeutet für alle Fälle nicht, dass der Container ein Gigabyte RAM benötigt, sondern etwa 100 Megabyte.


Wenn der Container normal gestiegen ist, können Sie versuchen, eine Verbindung mit der Datenbank herzustellen, die sich dort befindet.


Basisadresse - wahrscheinlich localhost
Port - 1234
Benutzer - SYSTEM
Passwort - 123


Wenn es normal startet, können Sie mit dem nächsten Schritt fortfahren.


DB-Initialisierungsskript


Damit das Programm über das Bild mit der Datenbank arbeiten kann, muss nach dem Start eine Schaltung vorhanden sein. Sie können diese Schaltung in der Erstellungsphase erstellen, aber ich bevorzuge es, wenn der Container gestartet wird.


Nach dem Start sucht der Container im Verzeichnis u01 / app / oracle / scripts / startup und führt alle dort gefundenen SQL-Skripte aus, die Sie verwenden können, indem Sie die Datei dort ablegen, die die Schaltung erstellt. So ähnlich.


 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; 

All dies muss der Datei init ~ db ~ .sql hinzugefügt werden, und die Datei wird mit -v in den Container geworfen


 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} Hier wird es verwendet, weil der absolute Pfad zur Datei benötigt wird. Wenn Sie Windows verwenden, müssen Sie ihn irgendwie anders angeben. Wenn nach dem Start das Schema TEST ~ USER ~ erfolgreich erstellt wurde, können Sie den neu erstellten Container mit den Tests verbinden.


Verwenden eines Bildes in Java-Code


Beim Testen mit der integrierten Datenbank tritt in der Regel dasselbe Problem auf. Wenn die Konfiguration nicht aus dem Cache übernommen werden kann, sammelt Spring sie erneut. Insbesondere wird die integrierte Datenbank neu erstellt, was die Tests natürlich erheblich verlangsamt. Ich habe das Problem mit brachialer Gewalt gelöst, indem ich das Containerhebeteil einfach zu einem Singleton gemacht habe. Wirklich, Wohnung.


Für Oracle XE verfügt Testcontainers über eine speziell vorbereitete Klasse. Zuallererst weiß diese Klasse, dass es sich um einen Container mit einem DBMS handelt und dass wir versuchen müssen, mit jdbc eine Verbindung zur Datenbank herzustellen, um festzustellen, ob dieser Container ausgelöst wird.


Ein Objekt dieser Klasse wartet darauf, dass der Container von selbst gehoben wird. Sie müssen ihm nur mitteilen, welche Protokolle mit dem Kennwort verwendet werden sollen.


 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; } } } 

Außerdem ordnen Testcontainer beim Starten die internen Ports des Containers zufällig definierten nicht belegten externen Ports zu. Daher kann man nicht befürchten, dass der Container nicht aufsteigt, da der Port bereits von jemandem benutzt wird. Ein externer Port kann mit der Methode getOraclePort () aus dem Container abgerufen werden.


Sie können die Containeradresse auch mit der Methode getContainerIpAddress () abrufen, aber jeder Container verfügt über diese Methode.


Nach dem ersten Aufruf der Methode getContainer wird der Container nicht neu erstellt, sondern der vorhandene zurückgegeben. Diese Methode kann jetzt in Bare Java oder in der Spring-Konfiguration verwendet werden, um ein Objekt mit einem Container abzurufen, aus dem Sie die Ports und die Adresse für die Verbindung abrufen können.


Sie können beispielsweise einen Initialisierer erstellen, der beim Erhöhen des Kontexts die Federattribute überschreibt, die für die Verbindung mit der Datenbank verantwortlich sind.


Klasse zum Befestigen von Testcontainern an Spring


Nach dem Auslösen des Containers überschreibt der Initialisierer die Eigenschaften, die für die URL für die Verbindung mit der Datenbank, die Anmeldung, das Kennwort und all das verantwortlich sind.


Wenn es jedoch in application.properties keine Einstellung evilcorp.testcontainers.enabled gibt, wird der Container nicht angehoben und alles funktioniert so, als ob niemand Testcontainer angeschlossen hätte.


 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"); } } 

Diese Konfiguration kann im Spring-Boot-Test verwendet werden, um die Datenbankeinstellungen im laufenden Betrieb zu überschreiben.


Testen Sie mit Testcontainern


Der Test schreibt einfach ein Objekt in die Datenbank und liest es dann, nichts Besonderes.


 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()); } } 

Und ja, fügen Sie Abhängigkeiten zu pom.xml hinzu


  <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> 

Auf diese Weise können Sie ein Oracle DBMS-Docker-Image erstellen und in Java-Code verwenden. Es bleibt nur, das Bild in das Unternehmens-Repository für Artefakte zu stellen und den Start der Tests in einem anderen Docker-Container zu arrangieren. Aber das ist eine ganz andere Geschichte.

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


All Articles