该代码应在可与其一起使用的DBMS上进行测试。 Testcontainers是一个库,通过该库,您可以像在嵌入式数据库(例如HSQLDB或H2)中一样轻松地在单元测试中使用几乎所有DBMS。 只有一个码头工人形象

本文专门介绍可方便与Testcontainers一起使用的docker映像的组合。 当我尝试做到这一点时,我遇到了问题,在这里我分享了我的解决方案。
我将收集Oracle 11的映像,因为它很小并且我有足够的版本11。 对于其他版本,方法大致相同。
为了清楚说明如何使用该映像,还将有Java代码演示该映像在测试Spring Boot应用程序中的用法。 我提供的连接到测试容器的方法可能不是最好的。 但是首先,他演示了如何使用创建图像时指定的设置。 其次,这很简单。 第三,它几乎不受Spring的束缚,甚至可以卡在Java代码中,其中只有公共静态void main。
假定读者对Docker和Testcontaner有所了解,并且对Java熟悉。 要构建,需要使用linux,如果要在Windows下构建,则需要使用msys2或类似的东西。
演示代码已上传到github https://github.com/poxu/testcontainers-spring-demo可以在我的Oraklov说明的分支中查看经过纠正的用于组装图像的脚本https://github.com/poxu/docker-images/tree/主/ OracleDatabase / SingleInstance
构建一个Docker镜像
Oracle不为docker提供映像,但是在github上发布了有关如何组装它们的详细说明。
不幸的是,无法在测试容器中使用这些映像,因为从该映像启动的容器从2到20分钟开始。
对于在单元测试中使用,这是不可接受的,因此您需要对脚本进行自己的更改,但是首先,最好尝试按照Oracle提供的说明组装容器。 我将在这里进行简单的复述,此链接上有更完整的说明https://github.com/oracle/docker-images/tree/master/OracleDatabase/SingleInstance
根据Oracle的指示进行图像组装
首先,您需要使用有关如何组装映像的说明克隆存储库。
git clone https://github.com/oracle/docker-images.git
然后获得Oracle 11.2.0.2。授权快速版本的rpm软件包,这不是很困难,您只需要在Oracle网站上注册,转到Oracle DBMS下载页面,在此处选择11.2.0.2 XE版本,然后下载打包的rpm文件oracle-xe-11.2.0 -1.0.x86〜64〜.rpm.zip。
将文件放在docker-images / OracleDatabase / SingleInstance / dockerfiles / 11.2.0.2 /目录下的已下载git存储库中
接下来,转到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,连接地址为localhost,SID为XE。
如果一切正常,则可以继续在testcontainers下创建图像的过程。 如果不是,最好先阅读Oracle的手册,找出问题所在。
我们已经发现,由于启动后会创建数据库,因此容器启动了很长时间。 在某些情况下,这可能很方便,但是现在造成了完全的伤害。 发射后必须立即准备使用容器。
手动图像细化
从完成的数据库中获取图像的一种方法是等待容器启动并完成数据库的创建,然后将容器保存到新图像。
重要的是不要使用--rm参数启动容器,否则docker在停止后会立即击败它。
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文件的最后一行包含wait命令。 我们知道在组装阶段您不需要等待任何事情,您需要完成工作,因此我们将在脚本中添加条件语句。
它将检查参数--running-while-building是否未传递到文件,以及是否传递了此参数,则不等待任何信号,而只是中断工作。 也就是说,这样做:
if [ "$1" != "--running-while-building" ] then wait $childPID fi
好吧,仅在组装阶段,在dockerfile中添加另一个脚本调用。 结果会是这样。
RUN $ORACLE_BASE/$RUN_FILE --running-while-building CMD exec $ORACLE_BASE/$RUN_FILE
测试中使用所需的更改
消除卷用量
需要找到线
VOLUME [“ \ $ 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
这将从图像中删除测试目的不需要的所有文件。 图像越小越好。
移除图像分割
为了缩小图像,您需要使用squash参数构建它。 它将去除图像中的分层,从而进一步减小其体积。 壁球参数是实验性的,因此您必须单独启用它。 在不同的操作系统中,这是以不同的方式完成的。
要将参数转发给docker,docker-images / OracleDatabase / SingleInstance / dockerfiles / buildDockerImage.sh提供了-o参数。 也就是说,要将--squash参数转发给docker,您需要像这样调用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"
设置数据库密码
在容器的第一次启动过程中,此密码是在环境变量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本身无法工作。 以防万一,这并不意味着该容器将需要1 GB的RAM,它会占用大约100 MB的内存。
如果容器正常上升,则可以尝试连接到该数据库。
基址-最有可能是本地主机
端口-1234
用户-SYSTEM
密码-123
如果正常启动,则可以继续进行下一步。
数据库初始化脚本
为了使程序能够从映像中使用数据库,启动后必须有电路。 您可以在构建阶段创建此电路,但是我更喜欢在容器启动时进行创建。
启动后,容器将在目录u01 / app / oracle / scripts / startup中查找并执行在该目录中找到的所有sql脚本,您可以通过将文件放在此处以创建电路来使用这些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〜方案,则可以继续将新创建的容器拧入测试。
在Java代码中使用图像
通常,在使用内置数据库进行测试时,会出现相同的问题。 如果无法从缓存中获取配置,则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() {
同样,测试容器在启动时会将容器的内部端口映射到随机定义的未占用的外部端口。 因此,您不必担心容器不会升起,因为该端口已被某人使用。 可以使用getOraclePort()方法从容器获取外部端口。
您也可以使用getContainerIpAddress()方法获取容器地址,但是任何容器都具有此方法。
第一次调用getContainer方法后,将不会重新创建容器,但是将返回现有的容器。 现在,可以在裸露的Java或Spring的配置中使用此方法,以获取带有容器的对象,您可以从该容器中拉出连接的端口和地址。
例如,您可以创建一个初始化程序,该初始化程序在引发上下文时将覆盖负责连接到数据库的spring属性。
用于将Testcontainer附加到Spring的类
引发容器后,初始化程序将覆盖负责URL的属性,以用于连接数据库,登录名,密码和所有其他内容。
但是,如果application.properties中没有执行evilcorp.testcontainers.enabled的设置,则不会提升容器,并且一切都会正常进行,就好像没有人连接testcontainer。
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) {
可以在Spring Boot测试中使用此配置来即时覆盖数据库设置。
使用测试容器进行测试
测试只是将一个对象写入数据库,然后读取它,没什么特别的。
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 docker映像并将其用于Java代码的方法。 剩下的只是将映像放入公司工件的仓库中,并在另一个Docker容器中安排测试的启动。 但这是一个完全不同的故事。