共计 5004 个字符,预计需要花费 13 分钟才能阅读完成。
本文首发于微信公众号“Shopee 技术团队”。
作者:Hao、Qianguang,来自 Shopee Digital Purchase 团队。
1. 背景
随着我的项目不断深入迭代,业务逻辑以及用户场景日渐简单,补充和保护单元测试保护的老本也变得越来越高。测试笼罩品质通过测试用例评审或者人工 Code Review 的形式费时费力,单凭多方沟通和教训累积的办法,往往不够精确,也难以避免开发人员存在在代码上线前“夹带私货”的场景,并且没有量化的、直观的主观数据来撑持。
为了在无限的工夫及人力老本内保障我的项目品质,实现对我的项目品质的精细化治理,咱们研发了 Finder —— 全栈代码测试覆盖率及用例发现零碎(下文简称 Finder),通过精确化的数据量化代码品质,从而实现精准化测试。本文将介绍 Finder 的总体架构,以及它作为品质保障体系中的重要一环,如何在我的项目中实现精确化测试。
2. Finder 我的项目介绍
Finder 次要分为两个模块,一个是 测试过程中对代码测试覆盖率进行收集与统计 ,一个是 剖析代码和用例的映射关系,精准确定回归测试范畴。
代码测试覆盖率统计模块,可能满足多环境、多需要、多服务的简单测试场景,实时收集测试过程中的覆盖率信息,并生成覆盖率统计报告;其反对多端语言接入(Web、React Native、Golang),实现前后端我的项目全笼罩,买通整体研发流程。
相干用例发现模块,针对变动的代码剖析函数调用关系,追溯残缺的调用关系链路,标记出所影响的 API 接口及相干用例,确定测试回归范畴。
3. 架构设计
Finder 分为代理层、应用层、外围服务层三个模块:
- 代理层 Finder Agent,负责后期数据采集的工作,包含编译阶段的代码插桩、覆盖率数据的采集、解析源码函数调用关系信息等;
- 应用层 Finder Platform,蕴含需要信息管理、分支信息管理、单文件覆盖率染色图展现、API 信息展现、测试用例关联、函数调用链展现等页面模块;
外围服务层 Finder Server,其中,覆盖率剖析和服务调用剖析是最次要的两个模块:
- 覆盖率剖析,对收集到的覆盖率数据进行数据聚合、差别增量剖析、数据修改等操作;
- 服务调用剖析,将源码解析工具传输来的数据进行数据结构转换后,实现调用拓扑图生成、API 信息关联、测试用例发现等操作。
在后期实现数据采集的接入工作后,测试人员无需额定操作,失常进行业务测试,测试实现后能够在可视化平台中查看覆盖率数据、相干测试用例等信息。
4. 实现计划
4.1 代码测试覆盖率模块
代码覆盖率模块次要分为两个步骤,第一步是通过对我的项目源码插桩,采集覆盖率信息;第二步是对覆盖率信息进行剖析,通过可视化平台展现统计报告。
步骤一:插桩与数据采集
代码插桩,意为在程序中插入一些代码,用于跟踪被测程序的某些信息。对于覆盖率测试而言,插桩的目标是检测程序中可执行语句被执行(即被笼罩)的状况。
Finder 反对 JS 和 Golang 两种程序语言的代码覆盖率统计,接入成本低,对业务需要无侵入性。
- 对于 JS 程序,咱们在 babel 编译阶段进行插桩操作,注入到全局对象 window 中。咱们提供了上报覆盖率数据的 npm 包 —— coverage-report,前端利用接入后会被动上报数据到 Finder Server;
- Golang 程序的覆盖率接入对业务我的项目零侵入,只须要引入 Finder Agent —— 覆盖率收集工具:它在编译阶段对源码插桩,包含 Git 信息和覆盖率信息注入;插桩实现后的服务会启动一个统计覆盖率的 Http Server,Finder Server 通过该 Http Server 提供的接口定时申请覆盖率数据。
步骤二:差别覆盖率统计
比起全量代码的覆盖率,实际上咱们更关怀改变代码局部的覆盖率状况。能够通过 Git 指令比照性能分支与 master 分支的代码差别,过滤无关代码,只针对改变代码局部进行覆盖率统计。
为实现差别覆盖率的计算,咱们约定以行(line)作为维度的构造示意 Git 代码差别和覆盖率信息,满足两者一一对应的关系。
首先将 Git 分支比照失去的 Diff 数据转换成以代码行作为索引的数组格局,元素的值有空值和 + 两种枚举值:
- 空值代表没有改变;
- + 代表有改变。
覆盖率数组也转换成以代码行作为索引的数组格局,元素的值有 -1、0、1 三种枚举值:
- -1 代表无需统计覆盖率的代码(例如空格,正文等);
- 0 代表未笼罩的代码(尚未执行);
- 1 代表已笼罩的代码(已被执行)。
将 Diff 数据与覆盖率数组进行合并,将非改变代码的覆盖率值置为 -1。失去最终的差别覆盖率数据。
关键问题一:数据源标准化
因为不同程序语言上报的覆盖率数据结构都不统一,咱们须要将数据源标准化,对立转化以行(line)为维度的数组构造。
首先,前端利用上报的覆盖率数据蕴含三种维度的覆盖率数据:
- Statement Coverage:语句维度的覆盖率数据;
- Branch Coverage:条件维度的覆盖率数据,例如 if/else、switch、三元运算符等;
- Function Coverage:函数维度的覆盖率数据。
对于这三类覆盖率,咱们做出以下数据转换:
咱们 采纳与操作 将三类覆盖率数据合并为行覆盖率,即一行代码满足三类覆盖率才算被执行过。
而后端 Golang 服务采集的覆盖率数据,如下图。
同样,咱们也须要将其转换为行维度的数组构造。
关键问题二:确保代码变更后的覆盖率正确性
在集成测试的过程中,开发人员不可避免地会进行代码更新。当代码更新后,咱们心愿可能记录新代码改变覆盖率的同时,仍然放弃原有代码的覆盖率数据。所以咱们须要获取新旧提交之间的代码差别,对原有覆盖率数据进行“修改”操作:
对原有的覆盖率数据,先删除 diff 后果中被移除的代码对应的覆盖率数据,再插入 diff 后果中新增的代码对应的覆盖率数据。
举个例子:
比照首次提交 commit 1 和 master 分支,失去首次改变:新增三行代码。
标记这三行代码的行索引 3、4、8,并统计它们的覆盖率。
比照 commit 1 和 commit 2,失去两次提交之间的改变:删除之前两行代码,新增一行代码。
移除原数据中第 3、4 行数据,而后插入新改变代码的行索引 7(删除后的索引地位)并统计新改变的覆盖率。
修改后的后果和 commit 2 与 master 分支比照的后果统一,既保留了首次改变的覆盖率数据,也记录了新改变的覆盖率数据。
关键问题三:单环境的代码覆盖率采集
在理论开发流程中,研发环境只有一套,咱们往往不能独占它,所有周版本需要都在 staging 环境开发,在 test 环境测试。这样会呈现一个问题:因为这种繁多环境的测试流程,导致统计进去的覆盖率无奈辨别来自哪个具体性能分支,进而影响对各性能需要的测试范畴判断。
为了解决这个问题,咱们提出两个计划:
第一个计划是搭建多泳道研发环境,通过 PFB(Per Feature Branch)的模式对各需要开发环境进行隔离。PFB 是一个能够疾速搭建虚拟环境的工具。它反对将多个性能分支版本同时部署在同一个测试环境中,且每个性能分支可领有独自的流量拜访,从而实现不同的性能分支可领有绝对隔离的虚拟环境。
然而因为这个架构改变较大,间隔落地到我的项目中还需一段时间,因而第二个计划应运而生:咱们 对繁多的集成分支进行性能追溯,判断出变动代码是属于合并进来的哪一个性能分支,并进行分类展现。
- 通过 Git Blame 命令从集成分支中判断出到各行代码对应的 commit hash;
- 通过 Git Branch 命令查找 commit hash 来自哪一个性能分支。
通过以上两步寻找到每行代码与性能分支的对应关系,与覆盖率后果进行映射后可得出对繁多集成分支的性能追溯成果,解决单环境的代码覆盖率采集问题。
4.2 相干用例发现模块
相干用例发现模块的要害是通过追溯变动代码的调用关系链,确定测试回归范畴。这个过程次要分为两个阶段:源码解析阶段 和数据分析阶段。
步骤一:源码解析
咱们在 Gitlab CI 接入源码解析工具,当有新代码提交时,触发源码解析。
通过对形象语法树 AST 和两头转换代码 SSA 进行解析,失去以下数据:
- 我的项目 Git 信息
- 函数根本信息
- 函数调用关系
- RPC 调用信息
- API 信息
最初将这些数据都打包对立发送到 Finder 服务端。
步骤二:数据分析
Finder 服务端接管到解析数据后,进行差别增量代码的函数调用关系全链路追溯,最初标记出相干的 API 信息及测试用例。具体追溯流程如下:
- Git Diff 失去性能分支与 Master 分支比照的代码改变;
- 通过差别增量代码的所在行数,确定所属函数体,进入步骤 3;
- 追溯指定函数的调用关系,直到寻找到 API 触发函数;
- 若遇到 RPC 调用的状况,进行 RPC 调用关系追溯,找到 RPC 调用方函数,持续回到步骤 3 的操作;
- 追溯工作实现,失去从变动函数到 API 触发函数的调用关系全链路;
- 通过以上失去的 API 触发函数,标记相干的 API 信息及测试用例。
每次代码变更都会触发上述流程,失去最新的函数调用链路信息,在可视化前台中展现该性能分支的变动函数调用关系链,以及相干测试用例。
关键问题:后端微服务架构串联,全链路笼罩
目前咱们后端服务采纳了分布式的微服务架构,例如一个购买商品的流程,当用户进入 APP 浏览商品时,会发动申请通过网关层,转发到商品服务,而后在创立订单时会调起订单服务,订单服务再调用用户信息服务和促销服务,整个过程还会调用多个根底服务共同完成一次操作流程。
咱们心愿除了解析服务内的调用关系外,也须要服务间的调用关系串联起来,失去残缺的调用链路。
为了解决这个问题,咱们针对我的项目中的 RPC 调用关系进行动态解析,通过寻找调用方函数——RPC Command——被调用方函数三者的关联关系,对调用方函数和被调用方函数进行标记,当函数调用关系链追溯到被调用方函数时,匹配到其对应的调用方函数,持续向上追溯,从而失去服务间的残缺调用关系链路。
5. 实际利用
Finder 通过接入团队中的我的项目流程管理系统,在整个需要生命周期的各个环节施展着不同的作用。
5.1 提测阶段
5.1.1 覆盖率提测达标
开发人员必须达到规定的覆盖率规范能力提测,对于覆盖率未达标的状况,开发人员须要反推代码逻辑是否存在问题,进一步剖析后期技术方案设计不够正当,还是对技术计划的实现有误,或者是在实现过程中造成的策略性放弃等。
5.1.2 覆盖率定制化
针对不同我的项目会有定制化的覆盖率要求,反对配置须要疏忽统计覆盖率的文件类型以及目录。
对于挪动端利用,提供 iOS 和 Android 双平台的覆盖率数据统计,这样可能提前裸露挪动端的兼容性问题,保障提测交付品质。
5.2 测试阶段
5.2.1 覆盖率实时剖析
测试人员染指测试后能够在 Finder 平台上实时查看覆盖率剖析后果,理解该测试版本变更代码的测试笼罩状况。
5.2.2 覆盖率染色图
通过代码覆盖率染色图辅助整个测试过程,不便测试人员对测试用例进行查漏补缺,尽可能保障所有要害场景都能笼罩到;同时能够驱动测试人员增强对代码的了解,充沛把控代码品质。
5.2.3 需要维度覆盖率统计
通过关联我的项目管理系统,从需要级别的角度,提供该需要下各模块的覆盖率数据,包含不同的后端服务和前端利用,辅助测试人员做决策,掂量品质危险。
5.3 回归阶段
标记变更范畴
依据 代码变更范畴 推断出 函数变更范畴,进一步标记出相干测试用例,从而实现用例和代码的关联关系。
用例发现剖析报告中会标记出该需要变更局部的相干信息,包含 变更的函数调用关系链路和受影响的 API 接口,测试人员从中能够疾速确定回归测试范畴,通过更小的维度更精准地度量测试品质,针对性进行自动化 / 手工测试,极大缩小回归阶段的工作量,以较小的回归老本保障较高的测试品质。
6. 将来瞻望
Finder 作为我的项目中品质保障体系中的一环,目前还处于初级阶段。在将来咱们会继续寻找 Finder 在研测协同体系中的更优实际。
1)反对更多维度的覆盖率数据统计
咱们心愿在统计方面能够提供更产品化更精细化的数据,包含关键字覆盖率、相干代码覆盖率、高风险覆盖率等,可能更好地辅助测试人员做决策。
2)精准记录 Bug 轨迹
后续咱们打算反对通过以增加标记的模式独自统计覆盖率,通过记录 bug 的残缺代码轨迹,还原问题的复现门路,精确定位问题所在。
3)建设用例知识库
构建用例与代码映射关系,造成用例知识库,反对对代码和用例进行关联治理,准确举荐改变代码关联的用例,提供测试用例优化倡议。