作者:吴静纯

团队:腾讯移动品质中心 TMQ

导读

如火如荼的 EP 建设中小鹅收到了一个小小的需求,如何知道每个版本变更了哪些插件间接口呢,有没有及时覆盖?

问开发,看代码,看变更日志貌似有那么点不太智能,重点是也不能保证有没有遗漏,不能解决测试童鞋的完美主义兼强迫症,有没有一份及时统一的视图可以来 review 插件间接口的变更和覆盖情况呢?

插件间接口示例

既然是统计插件间接口,我们先认识下手管的插件间接口定义,在手管插件化框架中,各插件相互平行,插件间接口调用即插件数据传递通过框架封装的统一接口进行通信,由框架进行底层的数据封装和传递,具体实现为各插件间维护一份插件对外的插件间接口配置,编译时在框架生成对应的插件常量,插件内部重载消息函数通过判断传递的接口常量进行对应消息处理从而实现接口间同步/异步数据传递。

插件间接口变更统计

每次编译前框架都会解析接口配置 xml 生成统一的插件接口常量表,那插件的变化情况我们可以从这里入手,从每次编译生成的常量定义中来找到各版本插件接口的变更情况,通过与上个版本的插件列表定义的插件名参数及返回值做一一对比,就可以知道当前版本的接口变更情况以便及时补充接口用例:

7.4 变更接口有 69 个,数量有些超出大家的想象,也顺手给 7.0~7.5 版本做了个小统计,统计结果手管目前的插件间接口达到了 740+,每个版本呈不断增长趋势。

这么多接口是否都是有效的接口呢?

目前已有的插件间接口用例覆盖程度有多少呢?

经过这么多版本的迭代相信应该有不少多余的水分,插件内的代码各 FT 通常会清理的比较及时也有一些现成的工具做冗余代码清理,但对外的接口大多担心外部兼容性及依赖问题通常清理不及时,有没有什么好的办法来梳理下,给这些对外接口把把脉呢?

插件间接口规则抽象

有没有类似调用链的分析工具呢?但插件化框设计各插件是平行的,调用链均指向框架接口无法解决我们的问题。虽然现成的调用链工具达不到需求,但我们可以借鉴下调用链的方法,重新抽象规则来建立一张我们想要的接口定义 - 实现 - 调用的关系图:

抽取规则如下:定义 - 实现 - 调用是一个正常接口的三要素。如果三要素有任一缺失,我们可以推测该接口可能无人调用可以清理或者实现者已清理但仍有调用。

规则一:接口定义,在框架中有定义的插件及插件接口常量认为插件已定义。

规则二:接口实现,在插件工程中有调用到本插件常量的则认为是本插件内部的接口实现,如 projectA 中有调用 CosntA.functionid.interfacea1,可以认为是接口 a1 已实现,记录插件 A 的 a1 接口的实现地址。

规则三:接口调用,在插件工程中调用到非被插件常量的则认为是外部接口调用,如 projectA 中有调用 ConstB.functionid.interfaceb1,则认为工程 A 调用了插件 B 的 b1 接口,在 b1 接口的调用链中添加该插件的调用记录及文件地址。

插件间接口规则实现

考虑插件间接口是通过传递接口常量来完成数据传递,我们可以通过代码扫描来构建我们的上述规则,结合我们的自定义需求来看看目前 android 常用的三款静态代码扫描工具:

从扩展性的角度看,coverity 作为商业软件虽然官方文档也支持自定义扩展,但相关资料太少,个人更倾向于 lint 和 findbugs,不会写还可以从源代码里面偷偷师,考虑到插件间接口传递的是接口常量,字节码在编译优化过程中常量字段被替换可能导致部分路径无法回溯,也不利于我们对结果做进一步的整理分析,所以最终选定 lint 进行源码扫描处理。

选定了工具之后实现部分就水到渠成了,按 lint 规则扩展来添加需要的检查规则,下图虚线模块是每个自定义规则需要扩展的地方:

1、注册规则,声明扫描范围为 JAVA_FILE_SCOPE:


2、实现检测器,检测器是实现检查逻辑的主体,自定义的 FunctionDetector 检测器继承自 Detector 并实现 Detector.JavaScanner 接口,并定义我们关注的扫描节点:


(1)查找插件接口定义:

在扫描工具中我们可以按抽象语法树来进行代码节点的查找,在 Android Lint 中 scanner 通过 lombok.ast(Abstract Syntax Tree 抽象语法树)API 来进行代码节点的查找,有兴趣的童鞋可以参照 Eclipse AST 介绍。

前面说到,手管编译前编译脚本会根据插件配置在框架生成相应的插件及接口常量类:

因此插件接口我们可以重写 visitClassDeclaration(ClassDeclarationnode) 函数在类声明节点中查找解析相应的类文件,将 functionid 的内部类的所有常量定义加入接口名 list,并收集相应的 location 信息:


(2)查找插件接口实现和调用:

获取插件接口实现,调用本插件的插件接口常量可以认为是该插件间接口的实现,在 visitVariableReference(VariableReference node) 重载函数中对于调用到的常量判断为插件常量格式(如 PiConst.FunctionId.FunctionName)则获取其插件常量判断是否为本插件的接口,如是,获取其 location 信息写入实现位置。

获取插件接口调用,调用非本插件的接口常量则认为是对外部接口的调用,将插件名及 location 信息加入到该接口的调用列表中。

3、确认全部插件工程都扫描完成后,在 afterCheckProject(Context)重载函数中判断每个接口状态:

1)有实现有调用列表的为正常接口;

2)无实现仍有调用的为冗余未清理接口,可清理接口定义及调用;

3)有实现但无调用的疑为冗余未清理接口,可清理接口定义及实现;

4)仅有定义,疑为冗余未清理接口,可清理接口定义。

得到了 748 个接口的状态信息,有 30% 接口有清理空间,我们抽查了主界面的几个,比如主界面 REPORT_MESSAGE 接口为 5.x 的消息中心接口,在 7.0 改版时该功能已全部去掉但仍有 6 个其他业务插件引用在继续给主界面发消息。

是否可清理呢?

答案是肯定的,接口定义及外部插件的引用均可删除,只删除定义会导致编译不过通知引用插件删除相应的调用即可。旧版本插件调用是否会有 crash 问题呢?

插件化框架无法保证插件是一定存在的,插件进行接口间调用时就需要进行容错处理,所以插件间接口也不是只增不减的,可以删除。

我们粗略做个统计:

接口定义(xml 配置接口及参数返回值定义不会进入编译)常量接口 1 行,非 normal 接口共 240 个;

接口实现,接口参数及返回值均值为 2.05 个,假设为 10 行,有实现但无调用的有 148 个;

接口调用,import 及无效调用假设每个引用 5 行,无实现的调用列表有 40 个。

240+148*10+40*5=1920 保守估计可以清理约 2000 行代码,相关的资源及配置也可以做进一步清理。

插件间接口视图的其他应用扩展

除了代码清理,插件间接口梳理结果是否还有其他应用呢?

比如查看插件用例覆盖程度,插件间接口测试也是通过调用插件接口调用来进行接口验证,因此调用列表中包含 pitest 插件的可认为是已覆盖的插件间接口,过滤调用列表中包含 pitest 的有 178 个,目前插件间接口 pitest 的覆盖率为 23.8%。

比如作为插件用例的下架指引,状态为非 NORMAL 或者插件列表如果仅有 pitest 插件的可推测该接口已废弃,测试用例可以考虑从日常监控中下架。

比如调用列表数量是否可认为是接口覆盖的优先程度,调用较多的是否可认为该接口使用率更高优先级更高,需要更多的关注和验证呢?

……

插件间接口整理只是我们静态代码扫描在缺陷/规范扫描之外结合业务的一个小应用,通过梳理业务定义处理规则,把代码的问题回到代码中来处理。照此思路,一些日益膨胀的公共 lib 库,裁剪对外提供的 sdk 也可以进行精确的梳理瘦身,其他各层级的接口是否也可以梳理出这样的可视化图表呢?统计点是否也可以进行类似的梳理验证呢?

结合业务代码扫描我们还可以做的更多,也许你也有更多的代码扫描的应用场景也欢迎大家一起探讨~

关注微信公众号:腾讯移动品质中心 TMQ,获取更多测试干货!


↙↙↙阅读原文可查看相关链接,并与作者交流