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

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

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

  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实现。这意为着就算你的测试过了,线上的代码还是可能会出错。这是常见的生产环境和测试环境不一致性问题。

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

这种解决方案有以下几个优点:

  • 每个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学习笔记,面试,文档以及视频

部分资料如下:

基于testcontainers的现代化集成测试进阶之路的更多相关文章

  1. 【SSH进阶之路】Hibernate映射——多对一单向关联映射(四)

    [SSH进阶之路]Hibernate基本原理(一) ,小编介绍了Hibernate的基本原理以及它的核心,採用对象化的思维操作关系型数据库. [SSH进阶之路]Hibernate搭建开发环境+简单实例 ...

  2. 浅谈Android进阶之路

    过去十年是移动互联网蓬勃发展的黄金期,相信每个人也都享受到了移动互联网红利,在此期间,移动互联网经历了曙光期.成长期.成熟期.现在来说已经进入饱和期.依然记得在 2010-2013 年期间,从事移动开 ...

  3. Spark进阶之路-Spark HA配置

    Spark进阶之路-Spark HA配置 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 集群部署完了,但是有一个很大的问题,那就是Master节点存在单点故障,要解决此问题,就要借 ...

  4. Scala进阶之路-并发编程模型Akka入门篇

    Scala进阶之路-并发编程模型Akka入门篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Akka Actor介绍 1>.Akka介绍 写并发程序很难.程序员不得不处 ...

  5. GO语言的进阶之路-goroutine(并发)

    GO语言的进阶之路-goroutine(并发) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 有人把Go比作21世纪的C 语言,第一是因为 Go语言设计简单,第二,21世纪最重要的 ...

  6. GO语言的进阶之路-流程控制

    GO语言的进阶之路-流程控制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 流程控制在编程语言中是最伟大的发明了,因为有了它,你可以通过很简单的流程描述来表达很复杂的逻辑.流程控制 ...

  7. GO语言的进阶之路-网络安全之proxy

    GO语言的进阶之路-网络安全之proxy 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在党的带领下,我们大陆的孩子身心健康还是杠杠的,尤其是像我这种农村孩纸,从来不会像<人 ...

  8. GO语言的进阶之路-面向对象编程

    GO语言的进阶之路-面向对象编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 当你看完这篇文章之时,我可以说你的Golang算是入门了,何为入门?就是你去看Docker 源码能看 ...

  9. Java进阶之路

    Java进阶之路——从初级程序员到架构师,从小工到专家. 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序 ...

随机推荐

  1. idea生成可执行jar

    1.创建工程 ①使用idea新建一个maven工程. ②编辑工具逻辑代码 ③完成代码的编写后添加工具调用的main方法以接收参数 至此代码编辑过程已经基本完成 ④在maven管理依赖的时候使用idea ...

  2. UVA11426 GCD - Extreme (II) —— 欧拉函数

    题目链接:https://vjudge.net/problem/UVA-11426 题意: 求 ∑ gcd(i,j),其中 1<=i<j<=n . 题解:1. 欧拉函数的定义:满足 ...

  3. python- 常见算法 python内置模块

    1.冒泡排序 需求:请按照从小到大对列表 [13, 22, 6, 99, 11] 进行排序 原理:相邻两个值进行比较,将较大的值放在右侧,依次比较! li=[39,11,43,88,765,9]for ...

  4. 阿里大于短信服务_异常_01_InvalidTimeStamp.Expired

    一.异常信息 dm.aliyuncs.com InvalidTimeStamp.Expired Specified time stamp or date value is expired. 二.异常原 ...

  5. codeforces 705B B. Spider Man(组合游戏)

    题目链接: B. Spider Man time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  6. C语言中的指针(二)

    指针指向谁,就把谁的地址赋给指针,指针变量和指针指向的内存变量是不一样的.不停的给指针赋值,相当于是不断的改变指针的指向. 在开发中要避免野指针的存在,在指针使用完毕之后,记得要给指针赋值成为NULL ...

  7. vue文件名规范

    之前有看过一些命名规范,也看到说vue文件命名要么全是小写要么就是用小写 + '-':其实看到的时候有点不以意,因为本地能跑起项目:发布能正常访问也就OK了. 但是今天在做自动化部署的时候碰到一个问题 ...

  8. kindle3 破解字体

    在万能的链接里下载kindle-fonts-4.4.N-k3.zip,update后kindle里出现linkfonts/fonts,这里就是存放字体的位置,字体格式需用.ttf. 在linkfont ...

  9. 基于Html5的移动端APP开发框架

    快速增长的APP应用软件市场,以及智能手机的普及,手机应用:Native(原生)APP快速占领了APP市场,成为了APP开发的主流,但其平台的不通用性,开发成本高,多版本开发等问题,一直困扰着专业AP ...

  10. Windows WMIC命令使用详解1

    https://blog.csdn.net/enweitech/article/details/51982114 在CMD和Powershell中 使用WMIC 先决条件: a. 启动Windows ...