当前位置: 首页 > 科技观察

一次单元测试优化过程总结

时间:2023-03-12 18:07:06 科技观察

前言测试用例帮助我们在开发新功能和修改原有功能的过程中发现了很多问题,显着提高了代码质量,减少了上线失败。在这里郑重推荐给大家,单考还是值得认真做的。刚开始是痛苦的,但经过一段时间的积累,量变会导致质变。言归正传,说说最近在单机测试实践中遇到的一个问题。在研发协作平台aone(以下简称aone)的发布流水线中,我们为单元测试设定了增量代码覆盖率85%、测试用例100%通过的流程卡点。每次发布前,都要确保测试用例完全通过才能提交工单。我们遇到了并发导致的测试用例失败,调整并发度导致的测试时间过长,也影响了研发效率。最终在并发度和成功率之间找到了一个平衡点,解决了单测流程降低研发效率的问题。单边流水线配置在单元测试过程中,我们主要使用JUnit、JaCoCo和Surefire三套工具,通过aone提供的容器自动运行单元测试并收集测试报告。下面简要介绍这三个工具。?JUnit是java世界最著名的单元测试框架。不用说,懂java的人应该都知道。?JaCoCoEclEmma团队开发的开源代码覆盖率统计工具,也是Java业界最主流的代码覆盖率统计工具。增量代码覆盖率是通过这个工具统计的,支持全量、增量、逐类、包统计,非常灵活。?MavenSurefirePluginsurefire是maven的一个插件,在maven生命周期的测试阶段执行单元测试用例。操作完成后会生成测试报告,方便用户查看单次测试情况。我们使用了三个工具,加上aone提供的容器和流水线配置能力,完成了自动化的单测流程和发布卡点验证。单元练习过程?累积测试用例期的两个阶段。刚开始单元测试的时候,大家添加的代码都比较独立。随着业务的发展和岗位职责的调整,单元测试会越来越复杂。不同的服务单项测试的维护和运营成本会增加。这个阶段我们遇到了一个比较棘手的问题。在日常的开发过程中,单测是在类的粒度上在本地运行的。测试通过后,会去流水线进行验证。一旦提交到pipeline,就会遇到个别case失败的问题。刚开始调查的时候,一点想法都没有。测试用例的失败可以说是随机的,任何类的任何用例都可能失败。经过分析排查,确定是并发引起的,于是我们限制并发,做了如下配置,确实解决了问题。<插件>org.apache.maven.pluginsmaven-surefire-plugin2.16false1可以注意reuseForks和forkCount参数。这个时候我们还没有深究这两种配置的含义,只是简单的限制了并发,这也为后续的故事埋下了伏笔。当测试用例达到一定规模时,测试用例的初步积累完成后,新的问题随之而来。因为没有并发,测试用例很多,所以每次单测最多跑50分钟。也严重影响了大家的研发效率。在分秒必争的releasewindow期间,经常会出现大家等着单机测试跑完提交releaseorder的情况。?问题从以上两个不同阶段所反映的问题来看,本质上是一个成功率和效果的权衡问题。如何在保证成功率的同时提高并发和速度,是我们需要解决的终极命题。?原理及解决方案上面提到的reuseForks和forkCount参数是maven-surefire-plugin提供的配置项。把surefire插件研究清楚后,应该解决速度和效果如何平衡的问题。Surefire配置详解paralleljvm中的并行执行是通过parallel参数开启的,可选择为methods,classes,both,suites等参数useUnlimitedThreads,无限线程数threadCount,线程数perCoreThreadCount,percore(默认true,combinedwiththreadCount)parallelTestsTimeoutInSeconds,超时时间注意设置parallel后,useUnlimitedThreads或threadCount必须设置一个,否则对于parallel级别和suitesAndClasses等更复杂的配置项会报错。本文不讨论更多参数示例如下,代表方法级并发,10个线程执行。org.apache.maven.pluginsmaven-surefire-plugin3.0.0-M7methods10forkmulti-jvm并行执行forkCount最大同时生成的JVM数,特殊语法为nC,即代表n倍CPU核心数,2.5C在4核机器上代表10个。reuseForks是否重用fork出来的JVM,true表示一个测试类运行后,进程继续处理下一个,false表示一个类运行完毕,销毁JVM,重新生成一个新的JVM。默认配置forkCount=1/reuseFork=true,forkCount设置0会自动替换为1parallel和forkparallel和fork结合后,可以有更好的并发效率,带来更大的冲突可能性。surefire关于并发导致case失败原因的文档原文如下。简单的说,就是因为JUnit的实现机制。对于JVM中的线程并发,会存在一些raceconditions或者其他难以复现的问题;对于forkCount大于1且启用了多路复用的情况,因为测试类在复用的JVM中,并发问题也会导致测试失败,同样的原因。结果与建议彻底理解surefire的配置原理后,我们回到问题。在尝试了各种排列组合后,我们得出了一个更合适的配置,reuseForks=true/forkCount=2C,最终效果是每次运行时间在10分钟左右,出错概率低,重新运行即可解决.小tipmvn默认是按模块序列化的,可以开启并行提高整体速度(例如:mvn-T1Ccleantest),但是在我们的场景中,2000多个测试用例中有1800个在一个模块中,所以enableParallelism影响不大。其实这个问题没有最优组合,只有最合适的组合。在优化了单次测试耗时最长的应用后,我们分析了其他几个应用。有些应用测试用例不多,单次测试运行时间不长,不需要开启并发,可以先保证成功率。;有些应用测试用例直接相互干扰较少,并发度可以调的高一些……总的来说,理解原理后,需要具体情况具体分析。如果你知道这一点,你需要自己去做。”大家可以结合surefire的并发机制来分析自己的应用和实践。相信经过多次测试,您一定能找到最合适的配置组合。在单元实践的整个过程中,笔者还有两个想法:有没有办法通过提高单元测试代码的质量来避免或减少并发导致的失败?一些想法是通过套件分组来分离可能冲突的类。这种方式可能会大大增加单测开发的成本,投入产出比也不高。测试用例的通过率不需要严格为100%。设置为99.5%可以显着提高效率,因为每次失败的测试用例不是固定的,所以偶尔出现的个别问题不会影响整体回归。在实践优秀工程的过程中,笔者深感纵观软件开发的整个生命周期,有很多值得研究和入门的点。一些小的改动,可以有效提高研发效率和交付质量。在当前环境下,商业竞争日趋激烈。所谓增收节支难,重点将放在“节流”上。降本增效必将是下一阶段的重点。而对于技术人来说,效率一定是永恒的追求。其实,提高性能和效率,往往并不是什么特别高深的事情。希望大家在每天繁重的工作之余,可以有时间做一些有趣的研究,享受科技带来的快乐!