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

简介: 本文将介绍淘宝用户运营平台团队最近在实践单元测试过程中遇到的一个问题。

前言

淘宝原用户增长团队(现用户运营平台团队)是比较早践行单测增量覆盖率的团队,坚持了近两年下来,我们积累了数千个test case,在开发新功能、修改原功能的过程中帮助我们发现了许多问题,显著地提升了代码质量、减少线上故障。在这里郑重地向大家推荐,单测是值得认真做的,开头是痛苦的,但是积累一段时间后,量变就会带来质变。



言归正传,接下来谈一谈最近在实践单测过程中遇到的一个问题。在研发协同平台aone(下文简称aone)的发布流水线中,我们针对单元测试设置了增量代码覆盖率85%和test case 100%通过的流程卡点,在每次发布前,要保证test case完全通过才能提交工单。我们遇到了因并发导致的test case失败,调整并发度导致的单测时间过长,但又影响研发效能的问题。最终在并发度和成功率之间找到了一个平衡点,解决了单测流程降低研发效率的问题。


单侧流水线配置

在单测流程中呢,我们主要用到了JUnit、JaCoCo和Surefire三套工具,通过aone提供的容器自动化运行单元测试,搜集测试报告。下面简单介绍一下这三个工具。
▐  JUnit

java界最大名鼎鼎的单元测试框架,无须多言,会java的应该都知道。
▐  JaCoCo

EclEmma团队开发的开源代码覆盖率统计工具,也是java业内最主流的代码覆盖率统计工具。增量代码覆盖率就是通过该工具进行统计的,全量、增量、按类、包统计都支持,非常灵活。
▐  Maven Surefire Plugin

surefire是maven的一个插件,在maven生命周期的test阶段执行单元测试用例。运行完成后还会生成测试报告,方便用户查看单测情况。



我们利用三种工具,加上aone提供的容器和流水线配置能力,完成了自动化单测的流程和发布卡点校验。



单元实践过程


▐  两个阶段


  • 积累test case时期


在刚刚开始单测时,大家新增的代码都相对比较独立,随着业务的发展、工作职责的调整,单测会不断变复杂,不同的service之间互相交织、单测的维护、运行成本都会增加。我们在这个阶段遇到了一个比较棘手的问题。日常开发过程中,单测都是以类为粒度在本地跑的,都能通过后再去流水线验证,一旦提交到流水线,就会遇到个别case失败的问题,一开始排查起来完全没有思路,test case的失败可以说是随机的,任何一个类的任何一个用例都有可能失败。

image.png

经过分析和排查,得出结论是并发导致的,于是我们限制了并发,做了如下配置,确实解决了这个问题。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.16</version>
    <configuration>
        <reuseForks>false</reuseForks>
        <forkCount>1</forkCount>
    </configuration>
</plugin>

大家可以留意一下reuseForks和forkCount参数,这时候我们还没有深究两个配置的含义,只是简单的限制了并发,这也为后续的故事埋下了伏笔。


  • test case达到一定规模时期


在完成了test case的初始积累以后,新的问题又随之而来。因为没有并发,test case又很多,所以每次单测运行时长长达50分钟。也严重影响了大家的研发效率。在分秒必争的发布窗口期,经常会出现大家等着单测跑完提交发布单的情况。


image.png

▐  问题


看了上述两个不同阶段反映的问题,本质上就是成功率和实效性的trade off问题,如何能提高并发、提升运行速度的同时保障成功率,这就是我们需要解决的最终命题。


▐  原理和解决方案


上文提到了reuseForks和forkCount参数,这些都是maven-surefire-plugin提供的配置项,把surefire插件研究清楚了,应该就能解决如何兼顾速度和实效性的问题。


  • Surefire配置详解


parallel

jvm内并行执行

过parallel参数开启,可选为methods,classes,both,suites等

其他参数

  1. useUnlimitedThreads,不限制线程数
  2. threadCount,线程数
  3. perCoreThreadCount,每核(默认true,和threadCount组合使用)
  4. parallelTestsTimeoutInSeconds,timeout时间

  1. 设置了parallel后,useUnlimitedThreads或者threadCount必须设置一个,不然会报错
  2. parallel级别还有suitesAndClasses等更复杂的配置项,本文不多探讨


参数示例如下,代表methods级别并发,10条线程执行。

<plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M7</version>
        <configuration>
          <parallel>methods</parallel>
          <threadCount>10</threadCount>
        </configuration>
      </plugin>
</plugins>


fork

多jvm并行执行

  1. forkCount 最多同时生成的JVM个数,特殊语法是nC,代表n倍的CPU核数,2.5C在4核机器上就是10的意思。
  2. reuseForks 是否重复使用fork出的JVM,true代表一个测试类运行完后,进程继续处理下一个,false代表一个类运行完了JVM销毁,重新生成新的JVM
  3. 默认配置 forkCount=1/reuseFork=true,forkCount设置为0会被自动替换为1


parallel和fork

parallel和fork组合后,就可以有更好的并发效率,也会带来更大的冲突可能。


  • 并发导致case失败原因


surefire的文档原文如下,

image.png

简单说来,就是因为JUnit的实现机制,对于JVM内的线程并发,会出现一些race condition或者其他难以复现的问题;对于forkCount大于1且开启复用的情况,因为测试类是在复用的JVM内,也会因为相同的原因产生并发问题导致测试失败。


  • 结果和建议


在彻底搞清楚surefire的配置原理后,我们回到问题来。经过各种排列组合的尝试,我们得出了比较合适的配置,reuseForks=true/ forkCount=2C,最终效果是每次运行时间在10分钟左右,出错概率较低,通过重跑也能解决。


image.png

小tip

mvn默认是按模块串行的,可开启并行提高整体速度(例:mvn -T 1C clean test),但是在我们的场景下,2000多个test case有1800个都在一个模块里,所以开启并行的效果不大。

其实这个问题没有最优组合,只有最合适的组合。在优化了这个单测耗时最久的应用后,我们又分析了其他几个应用,有的应用test case不多,单测运行时长不长,就没有必要开启并发,优先保证成功率即可;有的应用test case直接相互干扰较小,并发度可以调整得更高……

总的来说,在弄明白了原理之后,还需要具体情况具体分析,“纸上得来终觉浅,绝知此事要躬行”,大家可以分析一下自己应用的情况,结合surefire的并发机制进行实践,相信测过几次以后就能找到最合适的配置组合。



单元实践过程


在整个过程中,笔者还留有两个想法:

  1. 有没有办法通过提高单测代码质量来避免或者降低因为并发引起的失败?一些思路是通过suite分组,将可能冲突的类分开跑,这样的做法可能会极大的提高单测开发成本,投入产出比不高。
  2. test case通过率可以不用严格卡100%,设定到99.5%都能显著的提升效率,因为每次失败的test case是不固定的,所以偶发的个别问题不影响整体的回归。



在实践卓越工程的过程中,笔者深切的感受到纵观整个软件研发的生命周期,有很多值得研究和切入的点,一些微小的改动,都能有效地提升研发效能和交付质量。在当前的环境下,业务竞争日趋激烈,所谓开源节流,“开源”难,重心就会偏向“节流”,降本增效一定会是下一个阶段的重点。而且对于技术人来说,效率一定是永远的追求。其实提升性能、效率往往不是特别高大上的事情,希望大家能在日常繁重的工作之余,有点时间做些有趣的研究,享受技术带来的快乐!



参考资料


  1. https://stackoverflow.com/questions/3365628/junit-tests-pass-in-eclipse-but-fail-in-maven-surefire
  2. https://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html
  3. https://www.baeldung.com/maven-junit-parallel-tests



相关文章
|
8天前
|
监控 测试技术 持续交付
软件测试中的性能瓶颈分析与优化策略
性能瓶颈,如同潜伏于软件深处的隐形障碍,悄然阻碍着系统的流畅运行。本文旨在揭示这些瓶颈的形成机理,剖析其背后的复杂成因,并汇聚一系列针对性的优化策略,为软件开发者提供一套系统性的解决方案。
|
4月前
|
监控 测试技术 UED
软件测试中的性能瓶颈定位与优化策略
在软件开发的生命周期中,性能测试是确保产品质量的关键步骤之一。本文深入探讨了性能测试的重要性,并提出了一套系统的性能瓶颈定位与优化策略。通过分析现代软件系统中常见的性能问题,结合最新的研究成果和行业最佳实践,文章详细介绍了如何运用科学严谨的方法来识别和解决性能瓶颈。此外,本文还强调了逻辑严密的问题分析框架和数据驱动的决策过程对于提升软件性能的重要性。
|
14天前
|
监控 算法 测试技术
软件测试中的性能瓶颈分析与优化策略
本文旨在深入探讨软件测试过程中性能瓶颈的识别与优化方法。通过对性能瓶颈的概念、分类及其成因进行分析,结合实际案例,提出一套系统的性能瓶颈诊断流程和针对性的优化策略。文章首先概述了性能瓶颈的基本特征,随后详细介绍了内存泄漏、资源竞争、算法效率低下等常见瓶颈类型,并阐述了如何通过代码审查、性能监测工具以及负载测试等手段有效定位问题。最后,结合最佳实践,讨论了代码级优化、系统配置调整、架构改进等多方面的解决措施,旨在为软件开发和测试人员提供实用的性能优化指导。
|
5月前
|
运维 Kubernetes 测试技术
容器技术:优化软件测试流程的利器
本文介绍了容器技术的概念、优势和历史发展,对比了容器与虚拟机的区别,并提及了Docker和Kubernetes等常见容器技术。容器作为轻量级虚拟化工具,提供高效、灵活的应用部署方式,广泛应用于软件开发、云计算和微服务架构。随着技术演进,容器将在边缘计算、人工智能等领域发挥更大作用,推动行业变革。
77 3
|
23天前
|
关系型数据库 MySQL 测试技术
《性能测试》读书笔记_数据库优化
《性能测试》读书笔记_数据库优化
24 7
|
7天前
|
缓存 监控 算法
软件测试中的性能瓶颈定位与优化策略
性能瓶颈,如同隐藏在系统深处的“拦路虎”,悄无声息地制约着软件的表现。本文将揭示如何通过一系列科学方法,识别并消除这些障碍,从而显著提升软件性能,确保用户享受到流畅无阻的数字体验。
|
2月前
|
存储 人工智能 自然语言处理
知识库优化增强,支持多种数据类型、多种检索策略、召回测试 | Botnow上新
Botnow近期对其知识库功能进行了全面升级,显著提升了数据处理能力、检索效率及准确性。新版本支持多样化的数据格式,包括PDF、Word、TXT、Excel和CSV等文件,无需额外转换即可直接导入,极大地丰富了知识来源。此外,还新增了细致的文本分片管理和编辑功能,以及表格数据的结构化处理,使知识管理更为精细化。 同时,平台提供了多种检索策略,包括混合检索、语义检索和全文检索等,可根据具体需求灵活选择,有效解决了大模型幻觉问题,增强了专业领域的知识覆盖,从而显著提高了回复的准确性。这些改进广泛适用于客服咨询、知识问答等多种应用场景,极大提升了用户体验和交互质量。
58 4
|
2月前
|
测试技术
软件测试中的心理学:如何优化测试流程
【8月更文挑战第6天】本文深入探讨了软件测试过程中的心理学因素,揭示了测试人员的心理动态对测试效率和质量的影响。通过分析测试人员的压力源、动机以及团队间的沟通问题,提出了一系列改善策略,如建立积极的反馈机制、提供持续的职业培训和优化工作环境等,旨在提升软件测试的整体效能。文章最后提出一个开放性问题,邀请读者思考如何在不断变化的技术环境中维持测试团队的心理健康和动力。
|
2月前
|
Java 测试技术
hyengine microbench测试问题之提升jit优化如何解决
hyengine microbench测试问题之提升jit优化如何解决
|
2月前
|
SQL 缓存 关系型数据库
MySQL配置简单优化与读写测试
MySQL配置简单优化与读写测试
下一篇
无影云桌面