如何加快CI(maven编译和单元测试)速度

如何加快CI(maven编译和单元测试)速度

CI速度慢到什么程度会无法接受

CI速度或者单元测试速度一般不会在某一次特定的提交后突然变慢,如果真的是突然变慢,一般来说可以立刻定位到这次提交,问题一般也能解决了,但是就怕没有发现变慢,或者问题不是某一次提交暴露出来的,而是慢慢堆积起来的。

根据Jenkins构建历史,可以看到CI时间趋势,如果CI时间是递增的,并且单元测试、模块数量、代码行数一直在增加,那么总有一天会慢到无法忍受,一旦发现CI速度已经占据每次提交流程的大部分时间,特别是在每次紧急问题提交的时候,很多人都在等待CI结束,以完成代码审核、代码合并、提交验证,如果这种情况下的CI速度已经超出了大部分人可以容忍的限度,那么加快maven编译或单元测试速度就显得非常有必要了。

当然,可以容忍的程度因人而异,对于我个人来说,如果我这次提交的CI时间超过了历史平均值,我觉得就无法忍受,但是每次CI的速度取决于CI环境的并发数、机器的状态等等不可控因素,所以每一次CI都比较又显得不是特别理性,所以这是个很微妙的过程,只要突然某天觉得变慢了,哪怕是因为紧急BUG导致的精神紧张,都可以开始着手考虑能否加快CI速度,即使是最后没办法优化,也会让自己每次等待CI变得心甘情愿,而不是无法忍受。

加快编译速度

增量编译

Maven是默认是基于增量编译的,只要在任何一个包含complie的过程中(比如 mvn install),不使用clean,都是增量编译的,但是maven的增量编译不支持“减量编译”,即如果某次提交删掉了一个类,这个类的class文件并不会在maven编译的结果中删掉,或者某个静态变量的引用在编译时被优化成了直接使用变量值,如果这个静态变量的值在某次提交被修改,那么引用处的值可能不会发生变化,因为因用处的类并没有变化,不会在增量编译过程中重新编译,这类问题参考:

maven增量编译

maven增量编译的思考

增量编译是个好东西,但是要慎用,所以为了避免这样的问题产生,我们一般会在mvn命令后紧跟clean(如 mvn clean install),以清除上次编译的结果,这样增量编译就没有比较的基础,规避了增量编译带来的风险,所以需要谨慎使用增量编译。

模块并行编译

如果一个项目P,下面有多个子项目,比如M A B C D,A B C D共同依赖M,但是A B C D之间并无依赖关系,那么他们完全可以并发构建,这样理论上整个项目的构建时间只需要M的构建时间加上A B C D四个子项目中构建时间最长的那个,而不是之前M A B C D五个子项目构建时间的总和。

模块并行也很简单,只需要给mvn命令加上几个必要的参数即可:

mvn -T 2 clean test: 指定2个线程并行

mvn -T 1C clean test: 指定每个CPU核分配一个线程构建

其中-T可以指定同时多少个线程并发执行对应的阶段,后面的数值要么指定具体线程数,要么指定每个CPU核分配的线程数。

这样的参数可以让mvn各个阶段并发执行,以缩短CI时间。

加快单元测试速度

单元测试并行

单元测试之间应该做到没有任何关联,包括类与类之间、同一个类中的用例之间。那么这些单元测试类或者同一个测试类中的多个单元测试理论上是可以并发执行的,maven的sunfire插件可以做到Junit单元测试并行,具体参考:Running JUnit Tests in Parallel with Maven

对于只包含简单Junit单元测试的应用来说,sunfire插件可以保证所有单元测试的并行执行,并且能够有效减少单元测试执行时间,但是对于spring boot单元测试或者其他需要加载上下文的单元测试,可能并不是很友好。因为正常来说spring boot单元测试都是通过SpringRunner.class或者SpringJUnit4ClassRunner.class等runner来运行的,这些Runner保证同一个模块下的所有单元测试只加载一次spring上下文,但如果使用并行的单元测试,可能需要多次加载spring上下文,那么就会导致单元测试执行时间下降不明显,甚至不降反增。

优化单元测试

刚刚我们提到了可能出现的spring上下文多次加载,即使我们没有使用并行执行单元测试,依然还是会有这样的情况出现。

我们在使用spring boot test时,一般在测试类上加@SpringBootTest注解即可,注解中可以指定一些选项值,比如启动类、特殊配置等。也正因为有这些注解选项值,会导致存在多个选项值不同的SpringBootTest注解,SpringRunner.class或者SpringJUnit4ClassRunner.class认为每个唯一的SpringBootTest注解是一个上下文,那么就会存在不同选项的SpringBootTest注解会被多次加载,从而加长单元测试时间。

PowerMock也会导致单元测试变慢,特别是一些应该写在@BeforeClass而非@Before中的初始化过程,如果写在@BeforeClass中,会显著减少单元测试初始化时间。

Thread.sleep()也可能会导致单元测试变慢,我们应该避免显式地调用Thread.sleep(),而是应该使用一个包装的服务或静态方法,以达到可以被正常mock的效果,那么不管sleep多少时间,都可以mock为doNothing,从而减少等待时间。

如何找到执行时间较长的单元测试

执行mvn test后,控制台会输出各个单元测试类的执行时间,可以将全部输出保存到文本文件中,然后再使用一些命令或者文本处理工具筛选出相关行并排序,控制台输出如下:

[INFO] Running github.clyoudu.match.PowerMockitoTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.655 s - in github.clyoudu.match.PowerMockitoTest
[INFO] Running github.clyoudu.match.SpyTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 s - in github.clyoudu.match.SpyTest

我们可以筛选出形如[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.655 s - in github.clyoudu.match.PowerMockitoTest这样的行,然后再以空格分割,根据Time elapsed: 0.655 s中的0.655降序排列即可,具体命令和示例输出如下:

cat log.txt | grep "Time elapsed:" | sort -t ' ' -k 13rn,13 | head -15
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 90.209 s - in xxx
[INFO] Tests run: 22, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 18.839 s - in xxx
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.079 s - in xxx
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.066 s - in xxx
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.32 s - in xxx
[INFO] Tests run: 11, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.289 s - in xxx
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.405 s - in xxx
[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.22 s - in xxx
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.737 s - in xxx
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.627 s - in xxx
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.594 s - in xxx
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.496 s - in xxx
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.443 s - in xxx
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.307 s - in xxx
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.287 s - in xxx

上面的命令输出整个项目所有单元测类试执行时间最长的Top15,然后可以通过行末的输出找到对应的单元测试类,最后做出可能的优化。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值