基于testcontainers的现代化集成测试进阶之路

大型的软件工程项目除了大量的产品级代码外必不可少的还有大量的自动化测试。自动化测试包含从前端到后端甚至到产品线上不一样模块和环境的各类类型的测试。一个比较经典的关于自动化测试分布的理论就是测试金字塔,是说在一个正常的项目中合理的测试数量应该是单元测试 > 组件测试 > 集成测试 > 端到端测试(系统测试)> 人工验证测试。这个理论大致上是合理的,由于从测试代码的复杂度和执行时间看单元测试 < 组件测试 < 集成测试 < 端到端测试(系统测试)< 人工验证测试,因此咱们理所固然应该分配更多的时间和精力到容易理解和执行快速的测试中去,好比单元测试。固然关于这些测试分类和界定的见解众说纷纭,好比组件测试和集成测试,有时甚至是端到端测试,都一律被称为集成测试,由于它们在不一样的系统层面试图去测试两个模块或者系统间的集成情况。前端

最经典的集成测试的例子应该是后端系统应用层和数据层之间的集成测试了吧。数据层能够是传统的数据库,也能够是Kafka Stream这样的新宠。一般这种集成测试有几种思路:git

  1. 部署到staging环境中,而后在测试中发送请求到系统A,那个请求会包含对数据层系统B的读写操做。这个其实算是跳过集成测试到了端到端测试。但这种思路弊端不少,测试代码复杂度高,路径覆盖率低,从写出bug到检测到bug的周期也很长,不是理想的解决方案。github

  2. 在测试中使用In-memory Embedded Database(一般是实际数据库系统B的纯内存化实现版本,主要用于这种测试环境里),这样就能细化到测试系统A里面模块X对数据库B的某个写操做,并且能够在本地编写、运行、调试,和上面的解决方案比已经有了很大的改进。可是这个解决方案仍是有几个弊端:面试

  3. 不少In-memory Embedded Database只提供一个特定版本的实现,好比MongoDB 3.2,但若是你的实际数据库版本是4.0,那么不少新的数据库功能在测试里根本覆盖不了。 有些In-memory Embedded Database甚至没有实现100%的接口兼容,或者不同的实现方式,好比关系型数据库的transaction实现。这意为着就算你的测试过了,线上的代码仍是可能会出错。这是常见的生产环境和测试环境不一致性问题。redis

受益于Docker的普及化,testcontainers提供了另一种更为友好的集成测试解决方案。简单地讲就是在测试环境中动态建立须要的依赖服务的容器,好比动态建立一个Mongo 3.6的容器、建立一个RabbitMQ 最新发布版的容器,而后在测试中配置测试环境让测试应用使用建立好的容器暴露的可调用地址,测试结束后把使用过的容器销毁防止依赖服务状态迁移致使其余的测试莫名地挂掉。docker

这种解决方案有如下几个优势:数据库

  • 每一个Test Group都能像写单元测试那样细粒度地写集成测试,保证每一个集成单元的高测试覆盖率
  • Test Group间是作到依赖隔离的,也就是说它们不共享任何一个Docker容器;假如两个Test Group都要用到Mongo 4.0,会建立两个容器供它们单独使用
  • 保证了生产环境和测试环境的一致性,代码部署到线上时不会遇到由于依赖服务接口不兼容而致使的bug
  • Test Group能够并行化运行,减小总体测试运行时间。相比较有些in-memory 的依赖服务实现没有实现很好的资源隔离,好比端口,一旦并行化运行就会出现端口冲突。
  • 得益于Docker,全部测试均可以在本地环境和CI/CD环境中运行,测试代码调试和编写就如同写单元测试

固然,它也有几个劣势:编程

  • 测试运行时间长:由于每一个Test Group须要动态建立和销毁Docker容器,这两个步骤不少时候占用了大部分测试运行时间。固然客观地讲,这个等待时间仍是秒级别的,因此仍是能接受的。若是你再并行运行测试,整体运行时间仍是可控的。
  • 测试编写、调试体验由于上面一点而受到影响
  • 资源占用率高:大部分的build agent都是一个虚拟机,甚至是一个docker进程,再加上还要给每一个Test Group分配资源跑它们的依赖服务,整个build agent的CPU、内存使用率都会增长很多。在繁忙的时候甚至出现性能退化问题。解决方法就是scale up/out build agent。

从编程语言支持度来讲,目前testcotainers的github org上提供了Java, Scala, Go, Rust, NodeJs, Python, C#的类库。从成熟度来讲确定是Java的类库最为成熟,已被很多开源项目使用。其余语言的类库能够想象不可避免会有些坑须要踩。后端

举一个官网的例子来讲明如何使用testcontainers类库:框架

public class RedisBackedCacheIntTestStep0 {
    private RedisBackedCache underTest;

    @Before
    public void setUp() {
        // Assume that we have Redis running locally?
        underTest = new RedisBackedCache("localhost", 6379);
    }

    @Test
    public void testSimplePutAndGet() {
        underTest.put("test", "example");

        String retrieved = underTest.get("test");
        assertEquals("example", retrieved);
    }
}

上面的JUnit测试中动态建立了一个redis:5.0.3-alphine容器,在setUp方法里获取该容器的公开地址和接口从而建立咱们要测试的RedisBackedCache实例,而后在测试里轻轻松地调用该实例的方法、验证结果。

testcontainers Java 提供了几个现成的使用频率较高的容器的类封装,好比大部分数据库(MySQL, Postgres, Cassandra, Neo4j), UI测试的Webdriver,ElasticSearch,Kafka, Nginx等等。若是你没找到现成的封装,你老是能够调用更底层的GenericContainer。它也支持主流的Java测试框架,JUnit4, JUnit 5, TestNG,Spock。总的来讲对于写Java的同窗这个类库使用起来仍是很是爽的!

END

彩蛋福利

免费获取Java学习笔记,面试,文档以及视频

部分资料以下:

相关文章
相关标签/搜索