以下为作者观点:
一、引言
精准测试是一套计算机测试辅助分析系统,精准测试的核心组件包含,软件覆盖率分析、用例和代码的双向追踪、智能回归测试用例选取、缺陷定位、测试用例聚类分析、测试用例自动生成系统,这些功能完整的构成了精准测试技术体系。
我司(申万宏源证券股份有限公司)金融证券业务产品的测试多为手工黑盒测试,在实际测试过程中,面临着缺乏精准测试指导和测试质量度量,回归测试范围不明确,测试用例管理不高效等问题。因此,我司决定开展精准测试建立手工黑盒测试和白盒代码之间的桥梁,从经验型方法向技术型方法转型,提高测试效率和测试质量。
二、业务场景分析及问题挖掘
2.1基金服务架构
我司基金项目使用Java语言作为主要开发语言,基于SSM(Spring + SpringMVC + MyBatis)架构进行开发。本项目由多个模块和组件组成,主要包含内部员工端、管理人端、投资者端、网站端、微信端和接口服务6个模块,模块间存在大量的业务逻辑和交互,其中一个关键交互如图1所示。而且本项目是一个流程密集型项目,流程结构复杂,执行路径分支繁多,图2是管理人端和内部员工端在产品申购业务中的关键交互流程图,通过改变某些权限,还会导致本流程产生不同的变化。
综上所述,在测试环节中,测试团队必须确保每个模块和组件都能正确地工作,并且相互之间的协同性良好,需要严格保证每一个流程和分支都能够正确地工作。因此,对代码覆盖率要求较高。当前,本项目的测试主要是手工黑盒测试,还有部分接口自动化和UI自动化测试用例,只能依靠人工经验规划测试范围,很难覆盖所有可能的流程和分支情况,无法保证满足项目高代码覆盖率的需求。因此,我司拟引入精准测试来解决这个问题。
图1 基金项目模块交互图
图2 管理人端和内部员工端的交互流程图
2.2测试流程梳理
经过我们对于测试流程的调研,以及与测试团队的讨论分析,我们梳理了我司基金团队当前的测试流程,如图3所示。通过与测试团队进行深入沟通和仔细分析,我们发现并收集了当前测试流程存在的问题,如表1所示。
图3 基金团队当前测试流程
问题编号 | 问题描述 |
1 | 以黑盒功能测试为主,测试重点依赖主观经验 |
2 | 测试人员对于提测需求的复杂度缺乏客观判断 |
3 | 测试人员在评估提测需求对已有功能的影响面时,缺乏精确的判断 |
4 | 回归测试环节中只测试新增用例,忽略了老用例,存在漏测风险 |
5 | 全量回归测试的人力和时间成本巨大 |
6 | 存量用例已达1000多条,存在用例冗余、过时的情况 |
7 | 需要识别和梳理出主干用例库 |
8 | 对主干用例的判断依靠人工经验,缺少更高效的判断方式 |
…… | …… |
表1 当前测试流程存在的问题
2.3痛点挖掘
经过与测试团队的充分讨论、分析和总结,我们将表1中当前测试流程存在的问题,归纳为以下3个方面:
1.缺少测试指导
应用随着业务发展在不断扩展,各个应用的代码复杂度不断增加,测试人员如何准确、全面地判定代码修改的影响范围变得越来越重要;代码的修改可能会导致接口逻辑发生重大变化,因此测试人员需要评估这些变化对上层应用的影响;业务的快速迭代导致测试时间不断被压缩,测试人员只能依靠对代码和业务的了解和个人经验来圈定需求对应的测试范围。在进行测试之前,测试人员迫切需要得到明确的测试指导来解决上述问题并进行优化。
这样的测试指导应包括以下方面:首先,本次代码修改的影响范围,包括文件、类、方法、行级别,以帮助测试人员能够多方面、精准地了解代码修改的影响范围;其次,推荐代码修改影响范围涉及的测试用例,以帮助测试人员对影响范围做准确的针对性测试。总之,通过精准的数据来提供专业测试指导,测试人员将能够在敏捷开发模式下更加高效进行测试工作。
2.推荐回归用例
在回归测试环节中,由于全量回归测试会消耗超额的人力和时间成本,测试人员通常只能聚焦于本次变动的需求和主干用例进行回归测试。当前,我司测试人员只能通过对代码和业务的了解来确定回归测试用例,这对测试人员的要求极高,并且存在漏测的风险,一旦判定失误导致系统出现问题,可能给公司带来较大损失。因此,在回归测试环节中,测试人员迫切需要本次代码修改相关的测试用例集合,进行精确、针对性的回归测试。
我们可以通过代码修改的影响范围,推荐出对应的测试用例集合用于回归测试。在测试资源有限的前提下,通过这种方式我们将用例精简,缩小回归测试范围,可以有效地检测和验证本次代码修改是否会对系统产生影响,在保障低风险漏测情况下,减少回归测试所需的时间和工作量,提高测试效率。
3.用例管理维护
在整个测试环节中,测试人员都必须做好对测试用例库的有效管理和维护。在功能测试环节中,我司基金业务已积累了上千条功能测试用例,不可避免地存在一些已过时、低质量、低频度、重复覆盖的用例,我们需要对功能测试用例库进行管理和维护;在发版前,我司会在仿生产环境执行核心用例,核心用例库也需要定期清理无效用例和添加新用例。因此,我们需要提供一些高效管理和维护测试用例库的新方法。
在对测试用例库的管理和维护方面,我们可以记录并提供每个测试用例的推荐次数和代码覆盖率报告,以帮助测试人员高效地管理和维护用例库。
2.4技术选型
通过以上的梳理和分析,我司决定开展精准测试建立手工黑盒测试和白盒代码之间的桥梁,提高测试效率和测试质量。
精准测试的核心是代码覆盖率的采集。在测试领域,代码覆盖率是一项关键的指标,用于衡量测试过程中测试用例对源代码的覆盖程度,评估测试的广度和深度,确定测试用例是否足够全面,从而提高测试的质量和准确性。不同编程语言对应不同的代码覆盖率工具,比如C/C++使用GNU Gcov、LLVM/Clang的Code Coverage、BullseyeCoverage、Intel Compiler的Code Coverage等,Java使用JaCoCo、Coberturu、EMMA等。其中,JaCoCo支持丰富的覆盖率指标,也提供了丰富的报告生成和可视化功能,拥有开源社区支持和与主流构建工具的紧密集成能力。由于我司基金服务使用Java语言作为主要开发语言,因此我司精准测试拟使用JaCoCo进行代码覆盖率测试。
三、技术实践解析
精准测试可以用于多个应用场景,如用例智能推荐、增量代码覆盖率评估、用例库维护等,其中最典型、核心的应用场景就是用例智能推荐。用例智能推荐是指我们可以通过代码的变动精准推荐出相关的回归测试用例,这使得用例数量精简且更具针对性,能够大幅提高回归测试效率和测试质量。因此,我司首先在基金团队进行了用例智能推荐系统的应用实践,我们将在下文中详细解析其关键技术。
3.1技术实施流程
在用例智能推荐系统中,针对测试用例的智能推荐可以通过以下步骤实现:首先,进行测试用例录制以生成代码覆盖率文件;接下来,解析代码覆盖率文件并将其中的测试用例和覆盖函数集合存储到数据库中;然后,获取项目代码仓库中两个分支之间的差异函数;最后,基于差异函数,推荐出与之相关的测试用例。
结合我司基金项目的实际情况,我们将用例推荐系统的技术实施流程总结为图4,主要分为用例录制、代码覆盖率解析、代码差异解析和测试用例推荐4个流程。
图4 技术实施流程
3.2代码覆盖率解析
本环节是用例推荐系统技术实施流程的第二个环节,主要使用了org.jacoco工具包,即JaCoCo API。图4为本环节的流程图。首先,准备好代码覆盖率文件(.exec文件)、字节码文件(从测试服务器下载)、源码文件。然后,创建一个Analyzer对象来分析字节码文件和.exec文件,分析结果存入CoverageBuilder对象。接下来,遍历CoverageBuilder获得覆盖的类集合,遍历覆盖类集合IClassCoverage获取覆盖的方法集合。最后,将测试用例ID和覆盖的方法集合存入数据库。
JaCoCo将构造函数和静态初始化器也视为方法,但是我们并不需要关注他们的覆盖情况,可以将其过滤掉。项目中可能存在方法重载的情况,我们需要获取方法参数以区分。在这里,我们引入了JavaParser工具包获取方法参数(具体原理见3.3):解析字节码获取覆盖类后,找到覆盖类对应的源文件(.java)并将其转换为抽象语法树(AST),获取类中所有方法,若方法被覆盖,则获取其方法参数。在将源文件转换为AST的过程中如果报错,可能是因为该类存在内部类并且内部类被覆盖了。举个例子,如果类A含有内部类B,编译后会生成名为A$B的字节码文件,若A$B被覆盖了,那么我们根据A$B去找A$B.java会报错,A$B应该对应A.java文件。
图5 代码覆盖率解析流程图
3.3代码差异解析
在解析并存储每个测试用例与其覆盖函数集合的映射关系之后,我们需要对项目代码仓库的两个分支进行代码差异解析,以获取差异函数。在代码差异解析环节中,我们主要使用了org.eclipse.jgit和com.github.javaparser工具包。图6为本环节的流程图。
首先,我们主要通过JGit工具包进行了差异类的获取。通过使用org.eclipse.jgit.api包中的类和接口,我们能够以API的方式与Git仓库进行交互,将两个指定分支的代码仓库克隆到本地代码仓库。通过使用org.eclipse.jgit.revwalk和org.eclipse.jgit.treewalk包中的类和接口,我们可以获取两个分支对应的Git对象,将其仓库对象转换为树对象,并进行解析。通过使用org.eclipse.jgit.api.Git和org.eclipse.jgit.api.DiffComand类,我们可以从已解析的两个树对象中获得差异类;通过使用org.eclipse.jgit.diff.DiffFormatter类,我们可以将差异类的详细信息(包括变更行)提取出来。
然后,我们通过JavaPaser工具包进行了差异方法的获取。通过使用com.github.javaparser.JavaPaser类,我们解析出新类、旧类的所有方法,并且排除了接口类的所有方法。我们自定义了一个MethodVisitor类继承com.github.javaparser.ast.visitor包中的 VoidVisitorAdapter类,重写其visit()方法。在visit()方法中,我们计算了每个方法的MD5值,并获取了方法的参数。若MD5值不同,表示两个方法存在差异,通过比较新类、旧类中每个方法的MD5值,我们获取了差异方法。需要注意的是,MD5值在判断两个方法是否相同时,会将注释(单行、多行、文档注释)、空行等细微差异计算为变更,然而在我们的预期中这些差异不应该被判定为变更,因此我们需要尽量减少这些差异。对于注释,我们可以使用com.github.javaparser.ast.comments工具包来移除注释。
图6 代码差异解析流程图
四、实践过程注意事项
4.1 实践过程注意事项
我司在进行用例智能推荐系统的实践时,遇到了一些问题,我们将其中关键的问题及其解决方式进行了梳理和归纳,如表2所示。
序号 | 流程 | 场景描述 | 思考及解决 |
1 | 用例录制 | JaCoCo插桩不成功 | javaagent参数填写测试服务器的IP,规划好端口不要重复 |
2 | dump出的代码覆盖率不准确 | 用例录制时,要保证没有其他人员在使用测试服务器环境测试用例,防止干扰 | |
3 | 逐一录制测试用例时,需要在dump以后清除测试覆盖率信息,以隔离两次dump | dump命令中带上参数--reset,就可以在完成本次dump后清除机器上的测试覆盖率信息 | |
4 | 在执行接口自动化测试时,部分步骤执行失败 | 部分步骤执行失败会产生不完全的测试覆盖率信息,我们需要先dump --reset清除测试执行信息,然后再重新执行测试用例,重新dump | |
5 | 若录制完成后需取消插桩,还原catalina.sh设置后,发现进程仍带javaagent参数 | 可能时tomcat进程执行shutdown.sh时没有杀死进程,可以使用kill -15 杀死进程 | |
6 | 代码覆盖率解析 | 读取源码失败 | 可能是项目目录不规范,检查项目目录是否是src/main/java/com |
7 | 字节码和源码不完全匹配,导致覆盖率数据不准确 | 保证字节码和源码版本分支匹配;不同的编译环境编译出的字节码可能不同,最好直接从测试服务器上下载字节码 | |
8 | 存在方法重载的情况,需要获取方法参数进一步区分 | 使用JavaParser获取方法参数 | |
9 | 获取方法参数时,字节码多出了一个A$B.class,无法找到对应的A$B.java | 在Java中,如果一个类A包含内部类B,会额外生成A$B.class,以便正确加载和访问内部类。我们将A$B.class溯源到A.java文件以获取方法参数。 | |
10 | 需要验证解析出的覆盖函数结合正确 | 可以在java程序中使用JaCoCo API生成代码覆盖率报告 | |
11 | 在解析需要剔除掉一些不关注的方法 | 剔除一些不关注的方法,例如构造函数和静态初始化器 | |
12 | 代码差异解析 | 可能存在方法重载,需要获取方法参数进一步区分 | 使用JavaParser获取方法参数 |
13 | 使用MD5值判定两个函数是否相同的方法可能存在误判 | 空行、注释、空格的增改不算有效差异,可以在计算MD5之前去掉注释、格式化代码 | |
14 | 测试用例推荐 | 存在某些函数无推荐用例的情况 | 可能时因为新增/修改需求导致代码新增/修改,尚未录制新增的测试用例;或者是用例库不够全面 |
…… | …… | …… | …… |
表2 实践过程注意事项
4.2 用例和不同粒度代码映射分析
代码覆盖率解析时,使用JaCoCo API可以直接获取类、函数、行、指令、分支、复杂度6个级别的代码覆盖率。代码差异解析时,使用Git工具可以直接获取类、行2个级别的差异,结合JavaParser可以解析出函数级别的差异。下面我们将从模块、类、函数和行4个级别进行用例和不同粒度代码映射的对比分析:
4.2.1模块级别
模块级别是指项目中的不同功能模块或子系统,每个模块包括对应功能/需求的类,粒度最粗。模块级别映射适合模块化结构的项目,要求每个模块之间存在清晰的功能隔离,并且每个模块有自己的测试用例集合。在工程实现方面,代码覆盖率解析时需额外判定覆盖的类属于哪个模块,然后存储测试用例与模块之间的映射关系,消耗的存储空间很少;在差异代码分析时,同样需要额外判定差异类的模块归属,获取差异模块。
针对我司基金项目,如果想从模块级别进行代码映射,首先需要人工将项目模块化,根据功能/需求划分项目模块,圈定每个模块包含哪些类。然而我司基金项目各端之间存在复杂繁多的交互,流程密集,根据功能/需求对模块范围、模块测试用例集合的人工划分、维护的成本较高,故不采用此级别。此外,如果变动代码涉及的模块较广,推荐出的测试用例集合也会非常之多,达不到我们的精简回归测试用例的目标。
4.2.2类级别
类级别是指项目中的每个 Java 类,粒度较粗。通过用例和类级别代码映射分析,我们可以针对每个差异类进行用例推荐。在工程实现方面比较简单,代码覆盖率解析时,可直接获取并存储项目名、覆盖类的信息,消耗存储空间少;在差异代码分析时,可直接获取差异类。
我司项目较为复杂,如果仅映射到类级别,粒度较粗,无法精确定位变动,导致推荐的用例过多。例如S类含有a、b、c、d、e五个方法,分别映射case1、case2、case3、case4、case5五个测试用例,当A类中只有c方法发生变动时,最合理的是只推荐c方法对应的case3,但是类级别的映射会把A类对应的五个测试用例都推荐出来,存在无效的用例推荐。我们通过实践,发现类级别代码映射有时会推荐超80%的测试用例,并未达到精简回归测试用例的目标,故不采用此级别。
4.2.3函数级别
函数级别是指每个 Java 方法,粒度较细,适合引入方法调用链分析。在差异代码分析阶段,我们可以结合方法调用链进行分析,定位差异方法的上下游影响域,更全面地评估代码变动的影响面,增加用例推荐的精确度。在工程实现方面,代码覆盖率解析时需通过JavaParser获取方法入参,需要存储项目名、类名、函数名(包括入参)的信息,存储空间消耗适中;在差异代码分析时,需要通过MD5判定差异函数,并且需要通过JavaParser获取方法入参。
我司基金项目一个需求的变更/新增往往影响函数级别的代码变动,因此函数级别的映射足以达到精确定位变动的目标。我们通过实践,证明了工程实现的可行性,发现推荐的测试用例数较为合理,可以精简到原来回归用例数的15~20%,达到我们的精简回归测试用例的目标。
4.2.4行级别
行级别是指每个源代码文件中的每一行代码,粒度太细。在工程实现方面,代码覆盖率解析时,需要存储项目名、类名、行数的信息,存储空间消耗较大;在差异代码分析时,直接获取差异类、差异行的信息即可。
对于我司基金项目,行级别的粒度太细,随着测试用例数的不断增加,消耗的存储空间越来越大,查询速度的限制逐渐明显,系统的性能变差;在分析用例推荐结果时,需要人工根据差异行行号找到其归属的差异函数进行分析,不够高效,故不采用此级别。
五、基于精准改进流程设计
5.1 改进后的测试流程
图7为我司基金团队引入精准测试后的测试流程。我们在精准测试中通过用例录制、代码覆盖率解析,建立了用例知识库。在测试初,进行代码差异解析,生成了代码差异文档,帮助测试人员了解代码变动的影响面,改变了测试人员依赖个人经验判定变更范围的现状。在回归测试中,结合代码差异解析结果和用例知识库进行测试用例推荐,生成了用例推荐报告,根据已去重的推荐用例集合进行精确回归,降低了测试成本,提高了测试效率和质量。
下面附上我司精准测试实践结果。精准实践产出的代码差异文档如图8所示,用例推荐报告如图9所示。我们的用例推荐报告包括了项目分支和用例知识库的基本信息,函数变动个数,推荐用例的个数,推荐用例集合及推荐次数,推荐用例详情等信息。本次迭代共有50个差异函数,其中4个函数有推荐用例,表明这些函数与测试用例成功映射;46个函数没有推荐用例,表明用例知识库中没有与这些函数相关的测试用例。通过深入分析,我们发现,其中41个函数与新增方法相关(新增函数或调用了新增函数的函数),其测试用例也是新增的,因此我们无须关注他们的推荐情况;另外5个函数与新增函数无关,在上个版本就存在,但是我们并没有录制过与其相关的测试用例,因此没有推荐,这表明了我们的测试用例范围广度不足。综上所述,我们需要扩充用例录制的范围,且回归测试用例集合应是推荐测试用例与新增测试用例的并集。
图7 改进后测试流程
图8代码差异文档
图9用例推荐报告
5.2 展望
当前我司已经成功落地了精准测试中最典型、核心的应用场景——用例智能推荐。在用例智能推荐系统的基础上,我们可以进一步扩展和优化,比如在用例录制阶段从半自动化操作优化为自动化录制;在差异代码分析阶段可以加入方法调用链分析,更全面地评估代码变动的影响面;利用精准测试技术指导对用例库的维护;生成增量代码覆盖率报告以评估对新需求的测试质量等。
5.2.1调用链分析
在差异代码分析阶段中,解析完差异函数之后,可以加入方法调用链分析环节。方法调用链是指一个方法调用另一个方法的过程可以形成一个调用链。通过分析方法调用链,可以追踪方法之间的依赖关系和执行顺序,从而更好地理解代码的执行流程。结合方法调用链,我们可以定位差异方法的上下游影响域,进而更精确地识别出受到影响的所有方法,更全面地评估代码变动的影响面,并据其进行用例智能推荐。
具体来说,我们可以使用静态分析技术或者动态分析技术来构建方法调用链。静态分析技术可以在编译阶段或运行前对代码进行分析,主要通过解析代码的结构和语义来生成方法调用链。静态分析速度快,只能获取到静态的调用链信息,不支持获取如动态代理、反射等运行时产生的调用链。动态分析是指在程序运行时收集和分析代码执行的信息,通过监控程序的执行流程和方法调用情况,来捕获方法调用链。动态分析能够获取准确的运行时调用链信息,但可能对性能有一定影响。
目前业界主要通过静态分析来获取方法调用链。在Java语言中,可以使用字节码操纵框架来进行静态调用链分析,例如ASM、Javassist。ASM提供了底层的、细粒度的字节码操作API,可以直接操作字节码指令,因此在性能方面表现更好;Javassist 抽象了字节码的结构和指令集,提供了易于使用的API,上手难度低。在Java语言中,可以使用 Java Agent 织入获取动态方法调用链,但是需要谨慎评估和测试对应用程序性能的影响。
5.2.2用例库维护
在用例库维护过程中,可以使用以下指标来评估和管理测试用例的质量和有效性:
-
代码覆盖率:测试覆盖率是衡量测试用例覆盖到代码或功能的程度。
-
推荐次数:用例推荐次数越多,表眀该测试用例复用性越高,质量越高。
-
缺陷追踪:通过跟踪与测试用例关联的缺陷数量和严重性,可以评估用例是否能够发现问题和提供足够的信息。
-
用例通过率:记录用例的执行结果(通过、失败、阻塞等)和通过率(通过的用例数量与总用例数量的比例),可以帮助评估用例的适用性和准确性,低通过率的用例可能需要进一步审查和调整。
-
用例优先级:主要根据业务需求进行人工评估。
5.2.3增量代码覆盖率报告
在回归测试阶段,执行推荐的回归用例与本次新增的用例,采集增量代码覆盖率并生成报告。通过增量代码覆盖率报告判定本次回归的测试质量,是否达到了可上线的代码覆盖率指标。如果没有达到指标,可以通过增量代码覆盖率报告定位漏测代码,增加对应的测试用例。
5.2.4自动录制可行性
目前我司已经录制了接口自动化用例,下一步将整理并录制UI自动化用例和部分手工用例,丰富用例知识库。并且考虑与Devops平台集成,将用例录制的半自动化操作优化为自动化录制。此外,我们将在代码覆盖率解析阶段,通过方法调用链分析来建立调用链知识库。当代码变动导致调用链发生改变时,结合调用链知识库,我们可以识别出哪些测试用例与覆盖函数之间的映射关系发生变更,进而触发自动录制和解析,实现测试用例与覆盖函数之间的映射关系的自动更新。
六、总结
为了提高我司(申万宏源证券股份有限公司)的基金项目的测试效率和测试质量,我们引入了精准测试,并成功进行了用例智能推荐系统的应用实践。通过用例智能推荐系统,我们可以获取代码变动的情况,精准推荐出变动相关的回归测试用例,提供代码差异报告和用例推荐报告指导测试人员进行测试。在回归测试环节,精准推荐可以实现15.91%的用例压缩,可以将原先需要通过的803个接口压缩到了仅需通过264个接口,相当于原先的32.88%,我们能够更加高效地选择具有代表性和重要性的测试用例,可以在保持测试覆盖率的同时,减少冗余的测试,有效提高了回归测试的效率和质量。
未来,在用例智能推荐系统的基础上,我们会深入开展,调用链分析、用例库维护指导、增量代码覆盖率分析和自动录制等工作,进一步实现对业务测试的提质增效。
最后感谢每一个认真阅读我文章的人,下方这份完整的软件测试教程已经整理上传完成,需要的朋友们可以文末自行领取:【保证100%免费】
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!