内容简介:调研中发现,iOS代码覆盖率如要大规模应用到功能/回归测试,需解决如下三个痛点:
背 景
唯品会特卖会App承载着前端选购、下单、支付等核心业务,业务逻辑异常复杂,以iOS端为例,代码行数已达50万。2018年初,我们启动了研发流程的优化,大版本迭代周期由3周缩减至2周,伴随而来的是测试独占时长的压缩。如何在更短的测试时间内保证App代码质量是对测试团队一次挑战。长期以来,黑盒测试依赖于测试人员的能力和业务熟悉度,测试质量的高低没有客观数据可衡量。推进精准测试是我们的必经之路,而代码覆盖率是必须要跨过的槛,有见及此,特卖会App团队着手研究iOS及Android代码覆盖率的应用落地。本文重点讲解iOS代码覆盖率的应用实践。
调研中发现,iOS代码覆盖率如要大规模应用到功能/回归测试,需解决如下三个痛点:
-
苹果开发工具Xcode仅支持单元测试的代码覆盖率,而且只能在本地连线调试。而实际的测试设备多达20+,把设备连接电脑再收集覆盖率是不可行的,我们必须做到收集自动化,而且对用户无感知。
-
App的功能分支测试周期大约有4天,此期间可能存在多次的开发代码提交、缺陷修复,以哪一个版本作为覆盖率统计是值得思考的。我们尝试约定在某个稳定版本做全面的功能测试,但效果并不好,因为前期已测过的功能没必要重测一遍,时间也不允许。因此,我们需要支持行覆盖率的多版本合并。
-
鉴于app的运行模式仅支持离线插桩,我们需要处理大量手机用户,不同时间节点的覆盖率数据。
技术选型
首先,代码覆盖率要考虑插桩,插桩方式有3种:1)源码插桩;2)中间代码插桩;3)可执行文件的二进制插桩。iOS使用clang作为前端编译器,负责生成AST语法树,并将代码编译成LLVM bitcode;而LLVM作为后端编译器负责生成平台相关的机器语言。其编译插桩过程属于中间代码插桩,简化的过程可参考图1。LLVM脱胎于GCC,也可使用gcda/gcno记录代码覆盖率。其中:
-
gcno是notes file,插桩编译后生成;
-
gcda是data file,与gcno对应,由程序执行时记录;
LCOV作为GCC Code Coverage的开源前端工具,支持行/函数/分支覆盖的报告输出。因此,我们使用LCOV作为iOS代码覆盖率生成工具。
图1: 插桩编译原理
iOS覆盖率 工具整体框架如下:
图2: 系统架构
-
前端UI 接入公司的覆盖率平台,支持覆盖率的收集、生成报告、邮件通知;
-
调度层 负责分发消息至具体执行的机器 Analyser , Analyser 由 jenkins 封装 , 节点的机器必须为 Mac 系统,物理机或虚拟机均可;
-
分析器 支持覆盖率文件的合并、差异以及报告的输出。另外分析器负责接收终端上报覆盖率数据;
-
文件系统 管理覆盖率文件的中间数据以及版本构建信息 。
实现原理
我们设计出一整套解决方案,请看图3
图3:iOS代码覆盖率实现时序图
具体处理时序如下:
1) Xcode打包服务器在CI中自动插桩,并根据commit id,上传与之对应的标签文件gcno;
2) 用户通过OTA方式安装app,在测试过程中只需把app置于后台,覆盖率数据即可从缓存刷入硬盘,并通过http接口上传至文件服务器;
3) 覆盖率平台可通手工触发方式,收集指定分支、commit id的覆盖率文件info;
4) 待预处理完毕后,覆盖率平台可生成合并的或差异的覆盖率报告;
接下来我们从如下三方面剖析技术实现:
-
Xcode插桩
-
Pod上传服务器
-
覆盖率文件的合并/差异处理
(一) Xcode插桩
在BuildSettings分别设置Instrument Program Flow、Generate Legacy Test Coverage File为True,即可打开插桩。
图4: Xcode 插桩设置
(二) Pod上传服务器
通过Pod的插件方式,我们使用C++混编,调用外部的__gcov_flush方法把覆盖率gcda从内存刷到硬盘。为了方便交叉编译,GCC设计了两个与生成路径相关的变量GCOV_PREFIX,GCOV_PREFIX_STRIP,如下图。
图5: Pod的插件实现
在生成GCDA后就要考虑上传到文件服务器了,我们采用AFNETWORKING库,把多个GCDA文件整合在一个接口上传,文件大小约7M Bytes
(三) 覆盖率文件预处理,支持差异/多版本合并
我们使用开源LCOV来处理文件,gcda+gcno+源码可生成中间文件info,info文件包含了最终呈现报告的所有信息,包括源码路径、函数名、函数执行次数、函数总数、函数在源文件的位置、行号、行执行次数、行总数。为支持行差异覆盖率,我们自定义了新字段CA/CF/CH,请见图6。
图6: 中间文件info数据结构
对于两个不同版本的合并,使用平移。原则是把旧版本代码里的未变更的行覆盖平移到新版本。请见图7。
图7: 新旧版本git diff对比
具体流程如下,旧代码的覆盖率与git diff 比较,先判断是否块内代码,如不是块内代码则平移行号至新版本;若是块内代码,则再判断是否为已删除的行号,若是,则跳过不做平移;若不是已删除的行,则平移行号,请见图8。或许读者有疑问,两个版本的上下文都变了,平移行覆盖率后的可信度高么?笔者想强调的是,覆盖率报告好比一份健康检查报告,只关心异常点,没有覆盖的行就是异常点。在使用平移算法后那些依然没有覆盖的行就是没经探索的荒地,将是重点关注的对象。
图8: 两个版本的行覆盖率合并流程
我们再扩展至n个版本的合并,可沿用以上的平移算法,只需把最后一个版本作为平移的基准版本即可,可参考下图。
图9: 多版本的行覆盖率合并流程
至此,iOS覆盖率整套实现方案描述完毕。
实践效果
iOS覆盖率 工具 已接入至覆盖率平台,可通过服务器管理指定待收集的系统平台,分支;平台支持多分支同时收集。功能测试分支的覆盖率收集时间可控制在20分钟内。覆盖率收集完成后,平台可设定行差异覆盖率的对比基线,可设定一个分支内合并的多个版本,效果请见图10、11、12。
图10: 移动测试列表服务器管理
图11: 生成覆盖率报告页面
图12: iOS覆盖率报告的目录结构
最后,我们看看该工具的应用情况,特卖会App当前每两周发一个版本,在功能测试阶段,各条业务线(包括特卖/基础/用户/创新)已开始使用iOS覆盖率检测工具,从使用的经验看,功能测试阶段的代码行差异覆盖率达到90%,未覆盖的行主要包括三项,风险可控:
-
异常捕捉
-
非空指针的保护
-
冗余预留代码
精准测试方法总结
结合iOS覆盖率工具,我们总结出目前适合特卖会app的精准测试策略,请见图13。在测试分析阶段,我们注重需求差异,技术差异的分析,结合用例地图得出测试策略,并指导我们进行冒烟/功能测试,每个测试阶段的Code Review着重点有所不同,在冒烟测试阶段,CR着重检查开关、接口名、接口参数、model字段;在功能测试阶段,CR重点关注接口异常/跳转、行为;回归阶段则优先检查Bug Fix的代码改动。测试阶段均通过行差异覆盖率检查测试遗漏,并反馈更新测试策略。
图13: 特卖会App精准测试策略
下一步计划
目前使用的人工分析+工具辅助的方式进行精准测试,在这条探索的道路上我们还有很长的路要走。人工分析受制于技术,需求背景,能力和经验,我们很难保证质量。如何把已有的覆盖率数据转化为事前分析的参考,并结合整理的用例-模块(文件、函数、分支)映射库实行自助分析、测试用例集推荐,将是我们下一步研究的重点。
推荐阅读
容器环境应用一键拉起实践
“唯技术”一档专为唯品技术人发声的公众号
欢迎投稿!!
只要是 技术相关的文章 尽管砸过来!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。