你的CI/CD管线是否很慢?等待时间是否让你感到没有生产力?平行测试是减少等待时间的一个不可或缺的技术。掌握它是获得CI/CD最大收益的关键。
什么是并行测试?
尽管可能很明显,但值得重复的是,测试必须是自动化的。在开始讨论平行测试时,我们不能不澄清,只有当它是自动化时才有意义。
我们开始的定义可能看起来很简单:当两个或多个测试同时运行时,我们说我们在进行并行测试。另一种方法是通过展示非并行测试的描述来证明这一点。看一下这个持续集成管道:

系列测试
这里的测试是有顺序的,每个步骤只能在前一个步骤完成后开始。即使每个测试只需要几秒钟,整个过程仍然可以达到几分钟。等待构建不仅仅是一种烦恼,它还会让人分心和耗费精力。
一旦我们开始使用并行测试,我们将开始看到更多类似的东西:

平行测试
通过把独立的测试放在一起,我们可以得到快速的反馈,并在每次做出改变时节省宝贵的时间。我们不仅不会失去对我们正在处理的问题的关注,而且最终每周都能收回许多生产时间。
这就是平行测试的力量。就像升级你的互联网数据计划或在高速公路上增加更多的车道一样,它增加了带宽--让你做更多的工作,更快地走得更远。
当并行测试是CI/CD的最好朋友时
确定管道有效性的试金石是测量其总运行时间。CI/CD是关于反馈循环的--我们越早得到一个结果,我们就越早可以修复、重构和迭代。当持续集成花费超过10分钟,或者持续交付让我们等待超过20分钟时,我们就该开始考虑优化了。
大型测试套件是低垂的果实,应该是优化的起点。我们首先确定运行时间最长的测试,并检查是否有可能将其分解成较小的独立作业。然后,我们重复这个过程,直到管道的速度足以满足我们的需要。

不能被分解的测试有时可以被重新安排,这样它们就不会妨碍其他工作。

怎样才能使一个测试成为并行化的良好候选者?检查它的输入和输出。测试是否产生其他进程需要的东西?运行这些测试需要什么?一个测试的依赖性越少,它就越有可能在并行中良好地工作。
下面是并行测试的几个典型的好用例。
单元库
单体仓库是包含许多独立项目的代码库。只要这些项目是独立的或松散耦合的,单库就很适合并行测试。
当分离是不可能的,当这些项目牢固地相互关联时,使用并行化就变得很困难。 我们可以通过专用的单体工具(如lerna或yarn)来缓解这个问题。
Semaphore的特点是对monorepos的一流支持,并且可以配置为只对改变的代码运行测试:

当我们有相互关联的组件时,这个主题的一个变体就会发生。例如,在同一个资源库中的客户端和服务器,可以分别进行测试。

静态代码分析测试
代码分析测试是平行测试的另一个优秀候选者。静态测试代表了在寻找代码中的错误的第一道防线。我们在这个类别中发现了诸如linters、覆盖率报告和复杂性分析工具。所有这些都能有效地并行运行。

针对多种环境和操作系统的测试
我们在这里尽可能地使用术语环境,从浏览器到混合的暂存机和生产机,从选择移动设备到不同的数据集或API端点。这个类别还可以包括检查应用程序的本地化和国际化功能。
例如,为iOS和Android等平台测试一个应用程序是非常适合并行化的:

这同样适用于在混合云环境中测试代码。

版本和回归测试
在不同的运行时间上测试代码,可以让我们找到兼容性错误。下面的例子使用一个作业矩阵,在Java SDK和应用程序版本的组合中运行测试。

构建矩阵
并行化的好处
使用并行化有很多好处:
- 速度:更快的构建立即转化为在更短的时间内做更多的事情。更快的测试周期意味着我们可以更快推出功能,更频繁地发布软件。
- 更快的恢复时间:这是指团队从失败中恢复的时间,比如一个主分支断裂或一个糟糕的发布。并行管道投下了一张更广阔的网,让我们更早地发现问题,修复问题,并迅速发布修复。
- 减少瓶颈:并行测试很容易扩展到多个环境。例如,一旦我们在Android的一个版本上进行了测试,将其扩展到其他版本不应该影响整个管道的运行时间。
并行测试的局限性
并行测试的祸根是慢作业。即使是一个异常缓慢的作业,也足以使总的运行时间沉没。其原因是,无论我们使用多少并行化,流水线都不可能比它最慢的作业快。正如沃伦-巴菲特所说。"你不可能通过让九个女人怀孕而在一个月内产生一个婴儿"。
解决办法是识别和分析慢速作业,看看它们是否可以被优化或分解。为了对此有所帮助,Semaphore提供了一个测试总结功能,帮助我们分析许多流行测试框架的作业输出。而且在许多情况下,简单地使用更快的机器会更有意义。
在进行并行测试时,还有其他注意事项需要注意:
- 成本:并行测试将导致更高的资源利用率,在按使用付费的计费计划中,这将导致月底更大的账单。也就是说,获得的更高的生产力大大超过了增加的成本,所以你可以把它看作是一种投资。我们自己的研究表明,一个快速的CI/CD管道提供了41比1的投资回报。
- 复杂的依赖关系:当由于项目的性质而不可能分离组件时,平行测试就变得不切实际了。一般不建议使用紧密耦合的组件,解除耦合的努力永远不会浪费。但只要存在高度的相互依赖,就很难使用并行测试。
- 竞赛条件:当测试需要访问相同的外部资源时,如API或数据库,我们可能会遇到竞赛条件或速率限制,使我们出现假阳性。这个问题也可能发生在测试结果被缓存的时候。在大多数情况下,我们可以通过重写测试逻辑来确保竞赛条件不会发生。
- 优化强迫症:有可能走过头了,并行运行所有的测试,浪费了资源,同时没有得到相应的速度提升。首先运行快速和基本的测试,使我们能够在几秒钟内得到琐碎错误的反馈,同时保持CI/CD总时间的控制。
- 平台限制:你的CI/CD平台可能会对并行化设置上限。虽然Semaphore没有技术限制,但有一个安全配额,可以通过提交支持请求来提高。
- 片状测试:片状测试无缘无故地失败或成功。可能有很多原因导致片断性。例如,它可能是由于对测试顺序的依赖,缺乏资源,或外部依赖而发生。在任何情况下,你只需要处理它们,而不是依靠重新运行它们或忽略假阴性作为短期解决方案。
水平平行化与垂直平行化
一般来说,我们在CI/CD中有两种扩展并行测试的方式:垂直和水平。
垂直并行化发生在使用能够隔离任务并同时运行的工具时,利用CI机器的许多核心。例子有Pants、Bazel或Earthly。
垂直测试是非常好的,因为它们几乎总是非常不需要动手,工具会决定并自动找出运行测试的最佳方式。反过来说,一旦我们超过了一台机器的容量,我们就必须弄清楚如何在测试集群中分配负载。
另一方面,在水平并行化中,我们明确配置每个作业,并通过设计指导执行流程。我们将在下一节中看到一些例子,说明这一点是如何工作的。
这两种并行化在Semaphore上都运行良好。你可以通过选择更强大的机器来扩展使用垂直并行化的测试。而且,由于Semaphore是一种基于云的服务,你可以得到即时、自动的水平并行化。
如何在Semaphore中实现测试并行化
Semaphore支持四个级别的垂直并行化:作业、块、管道和工作流。
1.平行作业
我们使用并行作业将一个大任务分解成更容易管理的块。例如,在代码上运行静态分析工具或用作业矩阵测试不同的平台版本。

作业级并行化
作业并行化是最简单的形式,它只是在块中加入一个以上的作业。

Semaphore将同时运行区块中的每一个作业,为每一个作业使用单独的、干净的环境。

2.平行块
独立的区块可以并行运行,可以有自己的并行作业。这是并行化的第二个层次。并行块被用于monorepo设置和测试一个应用程序的独立组件。它们也有助于测试不同的环境和在主要的步骤序列之外运行更长的作业。

块级并行化
当块没有依赖关系时,Semaphore会自动并行运行块。


3.平行化流水线
平行化管道通常用于同时部署到多个目标:

管线级并行化
要同时运行管道,请启用自动推广,并设置启动条件以触发持续交付或持续部署。

4.平行工作流
在最后一个层次,我们有工作流并行化。为了使事情更清楚一些,工作流是由版本库中的变化触发的一组管道。我们可以选择通过将工作流分配给队列来并行或串联运行工作流。

工作流级别的并行化
Semaphore使用队列进行工作流,因此配置并行工作流对于避免在高度活跃的存储库工作时等待管道至关重要。要了解更多关于Semaphore如何管理同时进行的工作流程,请查看并行管道页面。
注意,只有当你使用付费、试用或开源计划时,你才能利用Semaphore的并行化优势。另一方面,免费计划限制用户一次只能做一个作业。
设置并行测试的提示
以下是设置并行测试的几个要点。
- 首先是快速测试:首先保持快速测试有助于我们快速失败,同时减少时间和运行更长时间的测试的成本。例如,先运行静态代码测试,然后是集成和端到端测试。
- 具有类似设置步骤的测试:使用一个块来运行具有类似准备步骤的测试。例如,你可能需要启动一个数据库来进行端到端测试和集成测试。你可以在同一个块中并行运行这两种测试。
- 识别依赖性:块的依赖性应该模仿项目的内部依赖性。例如,先并行运行所有的提示和单元测试,然后再继续运行更多的套件,如验收测试和集成测试,这样更有意义。
- 编写可并行的测试:在设计测试用例时牢记这些概念,将有助于你以后编写一个最佳管道。
总结
并行测试让我们做得更多,而等待得更少。这是一个必不可少的工具,要保持敏锐,准备好,这样我们才能始终建立一个快速的反馈回路。然而,这不是一门精确的科学,所以当你开始使用并行测试时,要逐渐放松,允许有一点试验和错误,为你的项目找到正确的平衡。
要继续阅读有关测试的文章,请查看这些伟大的文章: