技术博客

把集成测试和单元测试写一起是一种什么体验?


今天想跟大家分享一下最近我们在单元测试和集成测试上的一些心得。

我们是属于不到两个披萨就能吃饱的团队,严格来说,一个披萨就够了,主要因为我们有怕胖的女孩子和不吃高热量的老年人。虽然我们人丁并不兴旺,但是我们负责着七个项目,有两个是春节前确定的,我后文中称它俩为A和B。到目前为止,A和B已经运行了两个sprint。

B是内部服务,它并不直接服务于用户(至少目前是这样的),而是服务于公司内的其他项目,比如A项目。A是直接服务于用户的,但是A的稳定性一定程度上依赖于B的稳定性。所以如果我们要保证A和B的质量,那么就要做好B的单元测试,A的单元测试和A的集成测试。这里就有个问题,我们人丁单薄,在保证覆盖率的前提下,如何能够少做一些操作呢?于是我们自己摸索了一条路,把A的单元测试和集成测试写到一起!

具体的思路是这样,我们启动单元测试的命令是 npm run test (我们的开发语言是typescript),然后通过环境变量、参数或其他启动命令在同一个启动入口启动集成测试,比如我们用 npm run integration (等价于TEST_MODE=integration npm run test)来启动集成测试。当运行集成测试时,程序自动加载test/specs/ 目录下以.spec.ts结尾的文件,当运行单元测试时,程序自动加载test/specs/ 目录下以.spec.ts同时不以.integration.spec.ts结尾的文件。

image.png

紧接着要解决第二个问题,写单元测试时,遇到其他模块或外部服务,我们通常把它们mock掉。但是在集成测试时,就需要真刀真枪的与外部服务交互。所以在上图的 environment 文件夹中我们提供了一些类和方法,构建测试时所需的一切环境,比如使用docker启动db、redis和B服务。(有的团队喜欢用docker-compose,我觉得是因场景而异,就不展开了)

image.png

这样以后,无论是单元测试还是集成测试,每一条case该怎么写就怎么写,程序理应和真实环境中的运行效果一样。

然后,我们就要开始解决第三个问题,如何能够保证我们在本地获取到B服务的镜像(涉及到权限和版本控制)?为此我们和运维同学沟通后,为指定IP地址开放了一个私有镜像仓库,在B服务的CD流水线完成制品job时,把B服务的镜像向我们的私有仓库推一份,然后我们通过个人账号,可以从这个仓库中自动下载B服务的镜像。这时A项目集成测试所需的一切条件都就绪了,这是我们实际的运行效果:

image.png

由于春节后刚运行了两个sprint,cases数还不够多,但是我们的覆盖率已经保持在93%以上。B项目的单元测试覆盖率也是93%以上:

image.png

集成测试运行结束后,不要忘了清除不需要的container和volume。

image.png

这个方案实施起来还是挺顺利的,虽然有些小问题,但是基本上1-2天框架就搭建起来了。剩下的就是持续补充和持续维护测试cases。

当然单元测试和集成测试是基础,还需要通过适当的流程让它们发挥更大的价值。我们的代码仓库使用的是github,基于github的pull request,每次有同学想提交代码到主分支,只能通过PR的方式。提交PR时会自动触发我们的CI,运行代码质量分析和单元测试(有集成测试时优先运行集成测试)。要想PR通过,至少有一个人在code review后点了approve按钮。而我们内部有要求,无论是feature和bug,必须要有相关的单元测试或者集成测试,同时也有对覆盖率的要求。通过环环相扣,最终把我们的A服务和B服务的质量保护起来。

将近一个月的远程办公,我们并没有在沟通、协作上出现什么问题,团队之间还是十分信任。一部分归功于我们写程序的人相对来说比较简单,大多数时间都是封闭在自己的思想里。还有一部分归功于我们团队一直对DevOps的工程化实践非常重视,比如项目初期一定要把集成测试搞起来,让CI/CD成为基石。不过最重要的一部分,当然是我们一直用我们自己的产品Worktile。一个出身于协作,致力于研发更简单的团队,这点事算事吗?

本文作者:孙敬云
文章首发于Worktile 官方博客,转载请注明来源。