El código debe probarse en el DBMS con el que funcionará. Testcontainers es una biblioteca que le permite usar casi cualquier DBMS en pruebas unitarias con la misma facilidad que las bases de datos integradas como HSQLDB o H2. Solo habría una imagen acoplable

Este artículo está dedicado al ensamblaje de una imagen acoplable que es conveniente para usar con Testcontainers. Cuando intenté hacerlo, tuve problemas y aquí comparto mi solución.
Recopilaré la imagen para Oracle 11, porque es pequeña y tengo suficiente versión 11. Con otras versiones, el enfoque es casi el mismo.
Para dejar en claro cómo usar la imagen, también habrá un código Java que demuestre el uso de la imagen para probar las aplicaciones Spring Boot. El método de conexión a los contenedores de prueba que he dado probablemente no sea el mejor. Pero, en primer lugar, muestra cómo usar la configuración especificada al crear la imagen. En segundo lugar, es simple. Y en tercer lugar, casi no está vinculado a Spring, incluso puede estar atascado en el código Java, en el que no hay nada más que vacío estático público main.
Se supone que el lector está superficialmente familiarizado con Docker y Testcontaners, y también conoce bien Java. Para compilar, debe usar Linux, si está compilando en Windows, necesitará usar msys2 o algo así.
El código de demostración se carga en el github aquí https://github.com/poxu/testcontainers-spring-demo Los scripts corregidos para ensamblar la imagen se pueden ver en mi tenedor de las instrucciones de Oraklov https://github.com/poxu/docker-images/tree/ master / OracleDatabase / SingleInstance
Construir una imagen de Docker
Oracle no proporciona imágenes para la ventana acoplable, pero publicó instrucciones detalladas sobre cómo ensamblarlas en el github.
Desafortunadamente, no es posible usar estas imágenes en contenedores de prueba porque el contenedor que se inicia desde esta imagen comienza de dos a 20 minutos.
Esto es inaceptable para su uso en pruebas unitarias, por lo que debe realizar sus propios cambios en los scripts, pero primero, es mejor intentar ensamblar el contenedor de acuerdo con las instrucciones proporcionadas por Oracle. Haré un breve recuento aquí, una instrucción más completa se puede encontrar en este enlace https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance
Ensamblaje de la imagen según las instrucciones de Oracle
Primero, debe clonar el repositorio con instrucciones sobre cómo ensamblar la imagen.
git clone https://github.com/oracle/docker-images.git
Luego obtenga el paquete rpm para la versión express autorizada de Oracle 11.2.0.2. No es muy difícil, solo necesita registrarse en el sitio web de Oracle, ir a la página de descarga de Oracle DBMS, seleccionar la versión 11.2.0.2 XE allí y descargar el archivo rpm empaquetado oracle-xe-11.2.0 -1.0.x86 ~ 64 ~ .rpm.zip.
Coloque el archivo en el repositorio git descargado en el directorio docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 /
A continuación, vaya al directorio docker-images / OracleDatabase / SingleInstance / dockerfiles y ejecute el comando
./buildDockerImage.sh -v 11.2.0.2 -x
La ventana acoplable ensamblará una imagen llamada oracle / database: 11.2.0.2-xe en función de la cual debe crear el contenedor con este comando
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
El contenedor comienza unos minutos, porque después de comenzar crea una nueva base de datos, y este proceso no es rápido.
Después de unos minutos, aparecerá un banner en la consola.
############################
¡LA BASE DE DATOS ESTÁ LISTA PARA USAR!
############################
Después de eso, puede conectarse a la base de datos utilizando el inicio de sesión del SISTEMA, la contraseña es 123, la dirección de conexión es localhost y SID es XE.
Si todo funcionó, puede continuar con el proceso de creación de una imagen en contenedores de prueba. De lo contrario, es mejor leer primero el manual de Oracle y descubrir qué está mal.
Como ya descubrimos, el contenedor se inicia durante mucho tiempo debido al hecho de que después del inicio se crea una base de datos. En algunos casos, esto puede ser conveniente, pero ahora esto está causando un daño total. Es necesario preparar el contenedor para su uso inmediatamente después del lanzamiento.
Refinamiento manual de imagen
Una forma de obtener la imagen de la base de datos terminada es esperar hasta que se inicie el contenedor y se complete la creación de la base de datos, y luego guardar el contenedor en una nueva imagen.
Solo es importante no iniciar el contenedor con el argumento --rm, de lo contrario, la ventana acoplable lo golpeará inmediatamente después de detenerse.
docker commit --message "container for unit tests" <container id> my/oracle-11.2.0.2-for-unit-tests
Esto creará una nueva imagen del contenedor, que se lanzará no solo en unos minutos, sino en 20-30 segundos.
Modificación del proceso de ensamblaje de imágenes para obtener una imagen terminada inmediatamente después del ensamblaje
Por supuesto, es bueno tener instrucciones para ensamblar la imagen en forma de código para que pueda comenzar el ensamblaje con un comando y no haya necesidad de esperar a que el contenedor se inicie y cree una imagen basada en ella con sus manos.
La última línea del dockerfile indica el comando que se ejecuta después del inicio.
CMD exec $ORACLE_BASE/$RUN_FILE
\ $ ORACLE ~ BASE ~ / \ $ RUN ~ FILE ~ apunta al archivo docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 / runOracle.sh
Si detiene este contenedor y luego lo ejecuta nuevamente, como resultado, por primera vez, el script creará la base de datos, y la segunda vez simplemente lo iniciará. Se puede suponer que si ejecuta el script en la etapa de ensamblaje de la imagen, la imagen se ensamblará desde la base de datos ya creada.
Pero en el camino hacia la implementación de este plan audaz, surge una complicación.
El script se ejecuta para siempre, es decir, hasta que llega una señal en el contenedor de que el trabajo debe completarse. Esto se resuelve de manera bastante simple. La última línea del archivo runOracle.sh contiene el comando de espera. Sabemos que en la etapa de ensamblaje no necesita esperar nada, debe terminar el trabajo y, por lo tanto, colocaremos una declaración condicional en el script.
Verificará si el argumento --running-while-building no se pasa al archivo y si se pasa este argumento, no espere señales, simplemente interrumpa el trabajo. Es decir, haz esto:
if [ "$1" != "--running-while-building" ] then wait $childPID fi
Bueno, en el dockerfile agregamos otra llamada de script, solo en la etapa de ensamblaje. Resultará así.
RUN $ORACLE_BASE/$RUN_FILE --running-while-building CMD exec $ORACLE_BASE/$RUN_FILE
Cambios necesarios para su uso en pruebas
Eliminar el uso de volumen
Necesito encontrar la linea
VOLUMEN ["\ $ ORACLE ~ BASE ~ / oradata"]
Y comentarlo. No es necesario usar volúmenes, ya que todos los cambios se realizarán después de cada ejecución de prueba, pero pueden surgir fácilmente problemas al usar volúmenes al copiar imágenes.
Eliminar archivos innecesarios
Necesito agregar líneas
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 && \
Justo antes de la línea
chmod ug+x $ORACLE_BASE/*.sh
Esto eliminará de la imagen todos los archivos que no son necesarios para fines de prueba. Cuanto más pequeña es la imagen, mejor.
Eliminar división de imagen
Para reducir la imagen, debe crearla utilizando el argumento de squash . Eliminará la separación en capas de la imagen, lo que reducirá aún más su volumen. El argumento de squash es experimental, por lo que debe habilitarlo por separado. En diferentes sistemas operativos, esto se hace de diferentes maneras.
Para reenviar argumentos a la ventana acoplable, docker-images / OracleDatabase / SingleInstance / dockerfiles / buildDockerImage.sh proporciona el argumento -o. Es decir, para reenviar el argumento --squash al docker, debe llamar a buildDockerImage.sh de esta manera
./buildDockerImage.sh -o '--squash'
Cambiar nombre de imagen
Hasta la fecha, la imagen es significativamente diferente de lo que Oracle propone hacer, por lo que debe cambiar su nombre. Para hacer esto, ya necesita editar el archivo buildDockerImage.sh. El script toma el nombre de la imagen de la variable IMAGE ~ NAME ~, cuyo valor se establece directamente en el archivo
Justo aqui
# Oracle Database Image Name IMAGE_NAME="oracle/database:$VERSION-$EDITION"
Me cambié a
# Oracle Database Image Name IMAGE_NAME="my/oracle-for-habr:$VERSION-$EDITION"
Establecer contraseña de base de datos
Esta contraseña se establece en la variable de entorno ORACLE ~ PWD ~ durante el primer inicio del contenedor. Pero tenemos la configuración de la base de datos durante la creación de la imagen, por lo que la variable debe definirse en esta etapa. Si no es necesaria la capacidad de establecer una contraseña para cada ensamblaje a través de la línea de comando, simplemente puede ingresarla en el dockerfile:
ENV ORACLE_PWD=123
Si para algo necesita poder determinar la contraseña nuevamente con cada compilación, para reenviar el argumento a la ventana acoplable, puede usar nuevamente -o
./buildDockerImage.sh -v 11.2.0.2 -x -o '--squash --build-arg ORACLE_PWD=123'
Esto transferirá la variable de entorno ORACLE ~ PWD ~ al dockerfile, pero el dockerfile no lo pasará a los scripts que se ejecutan durante la compilación. Para que él haga esto, debe agregar la instrucción ARG al dockerfile.
ARG ORACLE_PWD=default
La contraseña, como probablemente ya se haya aclarado, será 123, y si no pasa ORACLE ~ PWD ~ a buildDockerImage.sh, entonces estará predeterminado.
A veces, Oracle cree que la contraseña es incorrecta y no quiere funcionar, por lo que puede ser necesario reemplazar 123 por otra cosa
Prueba elevando la imagen resultante
Ahora puede intentar ejecutar el contenedor en función de la imagen
docker run --rm --name dockertest_habr \ -p 1234:1521 \ -p 5678:5500 \ --shm-size="1g" \ my/oracle-for-habr:11.2.0.2-xe
El argumento --shm-size = "1g" es importante aquí, sin el cual se inicia el contenedor, pero Oracle 11.2.0.2 en sí mismo no puede funcionar. Esto, por si acaso, no significa que el contenedor necesitará un gigabyte de RAM, ya que consume unos 100 megabytes.
Si el contenedor se ha elevado normalmente, puede intentar conectarse a la base de datos, que se encuentra allí.
Dirección base: probablemente localhost
Puerto - 1234
Usuario - SISTEMA
Contraseña - 123
Si comienza normalmente, puede continuar con el siguiente paso.
Script de inicialización de DB
Para que el programa pueda trabajar con la base de datos desde la imagen, es necesario que haya un circuito allí después del lanzamiento. Puede crear este circuito en la etapa de construcción, pero prefiero hacerlo cuando se inicia el contenedor.
Después de comenzar, el contenedor buscará en el directorio u01 / app / oracle / scripts / startup y ejecutará todos los scripts sql que encuentre allí, que puede usar colocando el archivo allí que creará el circuito. Algo asi.
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;
Todo esto debe agregarse al archivo init ~ db ~ .sql, y el archivo se arroja al contenedor usando -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} Aquí se usa porque se necesita la ruta absoluta al archivo, cuando se usa Windows, debe especificarlo de alguna manera diferente. Si, después de comenzar, el esquema TEST ~ USER ~ se creó con éxito, puede proceder a atornillar el contenedor recién creado a las pruebas.
Usando una imagen en código Java
Cuando se prueba con la base de datos integrada, como regla general, surge el mismo problema. Si la configuración no se puede tomar del caché, Spring la recopila nuevamente. En particular, vuelve a crear la base de datos integrada, lo que, por supuesto, ralentiza considerablemente las pruebas. Resolví el problema con la fuerza bruta simplemente haciendo que la parte de elevación del contenedor fuera única. Real, condominio.
Para Oracle XE, Testcontainers tiene una clase especialmente preparada. En primer lugar, esta clase sabe que estamos hablando de un contenedor con un DBMS y que para determinar si está activado, debemos intentar conectarnos a la base de datos usando jdbc.
Un objeto de esta clase esperará a que el contenedor se levante solo, solo necesita decirle qué registros con contraseña usar.
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() {
Además, testcontainers, cuando se inicia, asigna los puertos internos del contenedor a puertos externos desocupados definidos al azar. Por lo tanto, no puede temer que el contenedor no se levante, porque el puerto ya está siendo utilizado por alguien. Se puede obtener un puerto externo del contenedor utilizando el método getOraclePort ().
También puede obtener la dirección del contenedor utilizando el método getContainerIpAddress (), pero cualquier contenedor tiene este método.
Después de la primera llamada al método getContainer, el contenedor no se volverá a crear, pero se devolverá el existente. Este método ahora se puede usar en Java desnudo o en la configuración de Spring para obtener un objeto con un contenedor desde el cual puede extraer los puertos y la dirección para la conexión.
Por ejemplo, puede hacer un inicializador que, al generar el contexto, anulará los atributos de primavera que son responsables de conectarse a la base de datos.
Clase para unir Testcontainers a Spring
Después de levantar el contenedor, el inicializador anula las propiedades responsables de la URL para conectarse a la base de datos, inicio de sesión, contraseña y todo eso.
Pero, si no hay una configuración de evilcorp.testcontainers.enabled en application.properties, el contenedor no se levantará y todo funcionará como si nadie conectara los contenedores de prueba.
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) {
Esta configuración se puede utilizar en la prueba de arranque de primavera para sobrescribir la configuración de la base de datos sobre la marcha.
Prueba usando Testcontainers
La prueba simplemente escribe un objeto en la base de datos y luego lo lee, nada especial.
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()); } }
Y sí, agregue dependencias a 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>
Se trata de cómo puede hacer una imagen acoplable Oracle DBMS y usarla en código Java. Solo queda colocar la imagen en el repositorio corporativo de artefactos y organizar el lanzamiento de las pruebas dentro de otro contenedor acoplable. Pero esta es una historia completamente diferente.