SpringOne 2022第6个议题《Introduction to Testcontainers》,用3个Demo展示了TestContainers for Java的重要特性,主讲人:Oleg Selajev,辅助人:Cora lberkleid。PS:Oleg Selajev的演讲风格非常有激情。

image-20230217074026357

1.概述

1.1.集成测试对微服务架构的影响深远

演讲者核心观点是:

  • 在传统架构下,测试成本的投入情况是**“单元测试">"集成测试">"E2E测试”**(如下图左半部分)。
  • 在微服务架构下,存在数量巨大的微服务进程,不太可能全量测试和发布。
    • 更合理的做法:仅对本轮迭代有关的微服务进行集成测试,局部发布。
    • 如下图右半部分:如果集成测试投入更多,那么与本次发布相关的微服务影响范围是相对可控的,进而E2E测试可能会更简单。
  • 总之,集成测试在微服务架构下越来越重要

image-20230217091453992

1.2.集成测试的新方法

主讲人在这部分展示了没有TestContainers之前使用TestContainers之后的集成测试手段的差异:

  • 之前

    • 在服务器上部署被测微服务、依赖的组件(如:数据库、Redis、Kafka等)
    • 在服务器上以Docker的形式部署被测微服务、依赖的组件
    • 在本地电脑上部署被测微服务、依赖的组件
    • 对部署成本高的依赖组件,采用模拟的方式
      • 如:Wiremock这类Mock Server
      • 如:采用h2数据库进行测试,规避安装PostgreSQL这种更重型的数据库

    image-20230218065338566

  • 之后

    • STEP1.TestContainers提供了丰富的Module(这些Module对应各类流行的中间件、数据库等)
      • 比如:Redis Module对应Redis
      • 比如:Kafka Module对应Kafka
    • STEP2.这些Module,各自都提供了API(这些API封装了Docker API)
      • 这就意味着我们可以通过某个Module的API,操纵对应的Docker容器
      • 比如:Redis Module提供1个启动redis容器的API,调用以后,就会在服务器上真的运行起来1个Redis的镜像
    • STEP3.TestContainers再和各类流行的测试框架集成,管理Module对应的被依赖组件的容器的生命周期
      • 比如:TestContainers在JUnit的setUp方法中创建了Redis容器,在TearDown方法中销毁Redis容器

image-20230218102321877

1.3.TestContainers发展历史

演讲者在这一部分讲述了TestContainers创建时的小故事:

  • 2014年,一位名叫Moshe Eshel的大神在GitHub上写了DockerContainerRule.java

  • 这段代码Override了JUnit的@Before@After

    • 在JUnit执行before方法时,会启动指定的Docker容器
    • 在JUnit执行after方法时,会关闭指定的Docker容器
  • 2017年,TestContainers完全替代了DcokerContainerRule.java

image-20230218101112791

听演讲的时候,没太跟上演讲者的语速,和chatGPT印证了一下:

  • 的确可以认为:DcokerContainerRule.java是TestContainers的灵感来源

image-20230218101251008

1.4.TestContainers成熟度

从主讲人表述的信息,TestContainers的确很成熟:

  • TestContainers项目在GitHub上是6.6k星
  • Docker项目发布的第2年就创建了,是这类测试框架的老前辈
  • TestContainers宣称Works with anything that runs in a Docker container

image-20230218103001722

  • 在2022年被ThoughtWorks的咨询报告中,TestContainers被评为Adopt采纳级

image-20230218103330486

2.Demo0

2.1.TestContainers的测试流程

这是演讲者的第1个Demo:

  • 代码解读
    • 在测试类上,增加了@Testcontainers注解
    • 为测试类创建了GenericContainer对象,此对象表示1个Docker容器
    • @Before方法中,调用GenericContainer对象,打印它的Docker容器Id
  • **日志解读:**从日志看,TestContainers完整地管理了1个容器的生命周期
    • JUnit先执行@BeforeEach方法
    • JUnit会连接Docker服务器
    • Ryuk被启动(Ryuk是啥呢?稍后解释)
    • 进行系统检查
    • 启动容器镜像

image-20230218103524464

  • Ryuk是什么?
    • Ruyk是Moby项目中的1个工具
    • 这个工具支持对Docker镜像的管理
    • chatGPT回答如下:

image-20230218104759166

2.2.如何重用1个容器

  • 代码解读:仅需要在测试类创建GenericContainer对象时,将该对象设置为static
  • 日志解读
    • 执行测试用例1时,TestContainers创建的Docker容器ID尾号bc739b5a
    • 执行测试用例2时,TestContainers创建的Docker容器ID尾号也是bc739b5a

image-20230218104923764

3.Demo1

3.1.端口配置

  • 代码解读
    • 在创建GenericContainer对象时,通过withExposedPorts方法开放了80端口

image-20230218111638548

    • 在测试用例2中,通过GenericContainer对象的getHostgetFirstMappedPort方法,获得Docker容器的IP和端口

image-20230218111714728

3.2.日志操作

  • 代码解读
  • 获得日志的方式1:调用GenericContainer对象的getLogs方法

image-20230218112057805

  • 获得日志的方式2:使用Slf4jLogConsumer对象

image-20230218112301488

3.3.使用Dockfile

  • 代码解读
    • 创建GenericContainer对象时,指定DockFile文件路径

image-20230218112544145

4.Demo2

4.1.redis+kafka+PostgreSQL

  • 代码解读
    • 创建RedisContainer对象和KafkaContainer对象

image-20230218113102821

    • 创建PostgreSQLContainer对象

image-20230218113215484

    • 设置Redis容器、PostgreSQL容器的参数:

image-20230218113306162

4.2.混沌测试

Chaos Test是主讲人最出彩的一段,通过Toxiproxy,可以很方便地模拟高负载、网络故障等,实战中非常实用

  • 代码解读
    • 创建Network对象

image-20230218115356716

    • 创建Toxiproxy容器对象

image-20230218115429306

    • 通过Toxiproxy容器对象,创建redis容器的网络连接代理对象

image-20230218115553720

    • 设置redis容器的网络延时为2s,这样,我们就可以模拟生产环境上redis存在2s延时的网络环境了。

image-20230218115652712

  • chatGPT对Toxiproxy的介绍:

image-20230218114014618

5.小结

通过演讲者的3个Demo,可以得到如下的判断:

  • TestContainers已经是相当成熟的集成测试组件了

  • 通过TestContainers提供的丰富的Module,极大地降低了集成测试时,依赖组件的部署和维护成本