1、框架选型
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
在整个自动化测试架构中,单元测试为最基础也是效率最高的层次,如图1所示,单元是整个软件的构成基础,像硬件系统中的零部件一样,只有保证零部件的质量,这个设备的质量才有基础,单元的质量也是整个软件质量的基础。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图1自动化测试架构及效率
在确定单元测试框架设计前,先做了单元测试技术的调研,对目前市面上开源的工具做了比较分析。
图2和图3为两个备选技术框架,选择图2的框架选型1作为单元测试框架。
选型1选择了Roboletric、Junit和Mockito框架,原因为Roboletric是以Java
Junit的方式运行,脱离了对Android环境的依赖,可以直接将case在JVM中运行,而Mockito用于在测试过程中,对于某些不容易构造(如
HttpServletRequest
必须在Servlet
容器中才能构造出来)或者不容易获取的比较复杂的对象(如
JDBC 中的ResultSet
对象),用一个虚拟的对象(Mock
对象)来创建以便测试的测试方法。
Mock最大的功能是将单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够模拟这些依赖,并验证所调用的依赖的行为。图4描述了某代码模块依赖关系,图5为使用Mock解耦后的代码模块依赖关系。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图2框架选型1 图3框架选型2
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图4某代码模块依赖关系 图5 Mock解耦后的代码模块依赖关系
选型2使用Robotium、Android
Test Suite(Instrument),该套框架基于Android
Runtime,必须在模拟器或真机上测试,效率比较低下,但由于是Android
SDK内置支持的单元测试和功能测试框架,其与系统的联系很紧密,测试环境非常接近真实环境,对某些特殊的测试需求,比如需要系统的特定功能属性或模块,使用选型2就比较合适。
Robotium基于Android
Test Suite,提供了更容易使用的测试接口,主要侧重黑盒测试。
表1描述了单元测试技术框架的横向比较情况。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
表1单元测试技术框架比较
优点 |
缺点 |
|
JUnit |
速度快、支持测试覆盖率等代码质量检测工具 |
无法做与Android UI相关的操作、与原生Java有差异 |
Mockito |
解除测试对象的依赖关系、验证行为等 |
无法配置static、final等修释的属性或方法 |
Robolectric |
支持对Android平台依赖类底层的引用和模拟(支持有限) |
无法完成UI Layout的测试、启动速度相对较慢,不利于单独Unit Test的开发 |
Robotium |
紧密结合产品代码测试,适合完成Activity粒度的真实场景下的单元测试 |
依赖安卓平台、速度非常慢 |
Android Instrument |
可以使用绝大多数Android控件和有比较好的设备层支持 |
速度非常慢、依赖平台、需要模拟器或真机支持 |
2、框架移植
主线代码使用了与图2框架选型1一致的单元测试框架,只有一点细微的差别,主线单测框架使用PowerMock,本方案采用Mockito,后查明PowerMock包含了Mockito的功能并做了增强,故最终确定使用PowerMock替换框架选型1的Mockito。
移植后的编译环境为Gradle,需要导入图6中的单元测试依赖包。
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图6单元测试所需依赖包
3、单元测试框架设计
图7描述了单元测试框架,该框架以主线单元测试框架为基础,书签模块单元测试用例为模板设计而成。
Robolectric运行环境运行在JVM里,具有可移植性,Junit、Mockito运行于Robolectric环境上方,专注于单元测试用例编写,而Roboletric用于解耦单元测试与Android运行环境的依赖。
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图7单元测试框架
模块基础框架面向特定功能模块,设计对该功能模块的特定业务的单元测试架构,不同的功能模块该模块基础设计可能不一样,对应于不同业务的不同特性。
模块单元测试用例是基于模块基础框架,编写具体业务的单元测试用例。
4、单元测试应用
4.1书签排序
4.1.1用例编写
根据文档《XXX书签云同步测试用例_20170515.xmind》的顺序小节,编写了14个单测用例。目前测试结果:13个通过测试,1个与预期不符。
图8为单元测试代码入口,用@Test标记的函数为一个单元测试方法。
h4 { margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; line-height: 156%; text-align: justify; page-break-inside: avoid }
h4.western { font-family: "Calibri Light", serif; font-size: 14pt }
h4.cjk { font-size: 14pt }
h4.ctl { font-size: 14pt }
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图8单元测试代码入口
图9为某个单元测试用例的代码示例,使用assertEquals断言case执行结果的正确性。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图9单元测试用例
4.1.2单测效果
图10为单元测试执行结果,从图中可知共14个case,1个failed,13个passed.
h4 { margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; line-height: 156%; text-align: justify; page-break-inside: avoid }
h4.western { font-family: "Calibri Light", serif; font-size: 14pt }
h4.cjk { font-size: 14pt }
h4.ctl { font-size: 14pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图10单元测试执行结果
图11显示与预期不符的case执行结果。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图11与预期不符的case执行结果
4.2书签数据库操作
书签排序部分的单元测试的目标模块是独立于其他业务模块和系统平台的排序算法,实质上是纯JUnit单元测试,没有涉及到Robolectric和PowerMock框架,而项目引入单元测试的主要目的是模拟业务场景,并在这些业务场景下运行健壮性、安全性等方面的测试用例,从中发现代码的问题,进而确保工程质量。
在Android项目下做单元测试,必然要基于Android平台去做单元测试,目前单元测试的框架选定用Robolectric替代Android运行时环境,虽然Robolectric已经较多地应用于Android开发的单元测试,但具体应用到书签云同步模块,该框架是否适用,应用过程会出什么问题,这些仍需要做风险预妨,表现在实操中就是合理安排任务计划,预留一定的时间应对可能出现的风险,同时在做单测方案时考虑层面更深、广度更全面、方案需要做的更详细和更容易落地等。
4.2.1业务模拟
现阶段的业务模拟,主要是模拟本地书签的增删改查和云同步。
图12描述了书签云同步的类图,图中粉色背景部分为单元测试依赖的类,绿色部分为华为去服务SDK中的类。
从图12中有两个主要分支,一个为以BookDaoManager为首的本地数据库管理类群,另一个为以HwCloudSyncManager为首的云同步类群。本地书签的增删改查是基于BookDaoManager去设计cases,同步测试是基于HwCloudSysncManager去设计cases。
h4 { margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; line-height: 156%; text-align: justify; page-break-inside: avoid }
h4.western { font-family: "Calibri Light", serif; font-size: 14pt }
h4.cjk { font-size: 14pt }
h4.ctl { font-size: 14pt }
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图12书签云同步类图
图13描述了模拟书签云同步业务的单元测试架构,JUnit的以@Test标记的Test
method为单元测试入口,在Test
method中调用Robolectric的控制对象和影子对象,通过Robolectric去运行App中的UnitTestActivity,测试用例写在TestActivity,这样就实现了在JVM上单测Android业务模块。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图13书签云同步业务模拟单测架构
值得一提的是,在单元测试架构中,使用者只需关注AndroidInterface和AndroidInterfaceTest的编写,无需关注Robolectric容器和具体App结合的原理和逻辑。
表1图13中主要对象含义
对象 |
子对象 |
作用 |
JUnit |
Test Method |
单元测试入口,用于调用AndroidInterfaceTest中的方法。 |
… |
||
AndroidInterface |
Android Method |
待测方法,运行在Android Runtime中。 |
AndroidInterfaceTest |
Test Method |
专测AndroidInterface中的方法。 |
… |
||
UnitTestActivity |
待测方法运行的容器。 |
4.2.2用例编写
根据文档《XXX书签云同步测试用例_20170515.xmind》的稳定性、边界值等小节,编写了13个单测用例。目前测试结果:13个通过测试,0个与预期不符。
图14~图15为现已实现的用例。
h4 { margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; line-height: 156%; text-align: justify; page-break-inside: avoid }
h4.western { font-family: "Calibri Light", serif; font-size: 14pt }
h4.cjk { font-size: 14pt }
h4.ctl { font-size: 14pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图14书签基本增删改查用例 图15书签稳定性用例
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图16书签边界值用例
图17展示的是JUnit书签数据库操作单测入口。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图17书签数据库操作单测入口
图18为稳定性测试用例中,书签批量增删改的用例代码。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图18书签批量增删改用例代码
图19为书签云同步用例模板,所有的书签云同步用例均按该模板编写,图18中的批量增加、修改和删除用例就是以该模板(BaseBookmarkDB)编写而成。
模板(BaseBookmarkDB)分为五个步骤:
第一步、使用Robolectric构建Activity,并通过ActivityController控制Activity的生命周期到指定状态;
第二步、执行单元测试;
第三步、打印单元测试相关信息;
第四步、使用Robolectric销毁Activity,通过ActivityController控制Activity进入销毁的生命周期序列;
第五步、断言单元测试结果。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图19书签云同步用例模板
4.2.3单测效果
图20为书签增删改查相关用例的执行结果。
h4 { margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; line-height: 156%; text-align: justify; page-break-inside: avoid }
h4.western { font-family: "Calibri Light", serif; font-size: 14pt }
h4.cjk { font-size: 14pt }
h4.ctl { font-size: 14pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图20书签增删改查用例执行结果
4.3书签云同步
书签云同步需要调用华为云服务SDK将本地书签数据同步给云端服务器。用单元测试模拟同步业务,在软件架构上遇到一定的问题,因为单元测试框架不包含华为云服务SDK,将华为SDK导出然后放到单元测试框架也不一定能正常运行,所以现阶段对同步业务的模拟,在设计上采用了一种间接的方式,即通过adb控制手机,将书签数据通过adb传送到手机,然后利用am
start一个SyncActivity,该SyncActivity负责读取书签数据并发起同步请求,最后SyncActivity将同步结果保存到sdcard,PC端以轮询的方式读取sdcard存储的同步结果。
图21为书签云同步拓扑图,也是单元测试同步框架实际场景图,工作人员将A设备通过USB线连接到电脑上,工作人员在电脑上运行测试用例。
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图21书签云同步拓扑图
4.3.1单测框架
图22描述了书签同步单元测试框架的原理。该框架基于C/S架构,Client端运行在电脑上,测试用例跑在Client端,同时要实现一个SyncActivity作为服务端跑在手机上。
h4 { margin-top: 0.49cm; margin-bottom: 0.51cm; direction: ltr; line-height: 156%; text-align: justify; page-break-inside: avoid }
h4.western { font-family: "Calibri Light", serif; font-size: 14pt }
h4.cjk { font-size: 14pt }
h4.ctl { font-size: 14pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图22书签同步单测框架原理
图23为同步单测框架原理流程图。
目前同步单测框架已编码完成,但没有经过调试验证执行结果的正确性及框架运行的稳定性。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图23同步单测框架原理流程图
5、改进方案
5.1书签排序
原有书签排序采用拓扑排序,时间复杂度为O(v+e),在排序过程中各有向边的权值是时间参数,在和千兴的沟通过程中了解到,算法并没有很好地处理不同设备间时间参数的同步,从而有潜在的风险。因此,提出了基于时间同步的改进方案,即统一使用第三方服务器的时间,就可避免因时间参数未同步带来的风险。
图24描述了改进方案的设备时间同步流程。
图25描述了改进方案在网络恢复时的设备时间同步流程。
图26描述了书签云同步时合并书签数据流程。
该方案在云同步时的排序时间复杂度为O(m+n),其中m为本地书签个数,n为云端书签个数。
该方案总的排序时间复杂度(书签云同步时的排序+同步第三方服务器时间后的排序)为O(nlogn)
。
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图24设备时间同步流程
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图25网络恢复时的设备时间同步流程
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图26书签云同步时合并书签数据流程
5.2书签保存策略
在现有实现方案中,用户增加、修改书签(包括书签之间的顺序)和删除书签后,应用会立即将修改回写数据库,在用户频繁改写书签的场景下,会造成比较大的性能开销,因此提出一个改进方案,即在用户进入书签管理界面后所作的书签修改,应用只是修改内存中的数据,在用户退出书签管理页面后,应用才将内存中的书签数据回写数据库,以此避免较多的IO开销。
在某此极端情况下,比如应用crash或应用被系统杀掉,导致内存中的书签数据来不及回写数据库,应对这些极端场景,可进一步被充改进方案,在监测到用户最近一次修改5分钟后,将内存书签数据回写数据库。
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
6、Gradle工程切Ant预研
6.1 Gradle依赖包导出
添加java library工程,配置好该libraray工程对junit、robolectric和powermock的依赖,如图6所示,然后在build.gradle中添加一个copyJar task,copyJar task如图27所示。
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图27导出Gradle jar包task
运行gradle
copyJar,或在图28中双击copyJar,单元测试框架依赖的jar包会保存到build/libs/lib目录。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图28 Gradle projects窗格执行copyJar
6.2
build.xml配置
Ant内置支持JUnit构建,而Robolectric基于JUnit构建,加之PowerMock更倾向于中间件的角色,所以Ant天生支持JUnit
+ Robolectric + PowerMock组成的Android单元测试框架,准备工作只需将JUnit
+ Robolectric +
PowerMock依赖的jar包放到libs目录,并在build.xml配置好指向该libs的classpath。
图29为
Ant打包apk
有向非循环图(DAG),粉色背景标识的任务为单元测试任务,单元测试任务依赖于compile任务,如果要做单运测试,只需要运行ant
junit即可。
另外,Ant引入单元测试框架,只需在原打包任务流的基础上加上junit任务,并使junit任务依赖compile即可,并不会对原打包流程造成大的干扰。
h3 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h3.western { font-family: "Calibri", serif; font-size: 16pt }
h3.cjk { font-family: "宋体"; font-size: 16pt }
h3.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图29 Ant打包apk DAG
图30和图31为Ant下运行单元测试后生成的结果报告。
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
图30 Ant下单元测试报告
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
7、后续工作
调试并验证同步单元测试框架的正确性,然后增加健壮性、安全性、同步方面的用例编码实现。
目前的单元测试框架仅应用于书签云同步模块,并处于效果检验期,如果单元测试效果较明显,项目后续还会将单元测试应用于各新增功能模块的开发和维护。
9、总结
单元测试框架搭建及书签云同步顺序单元测试用例编写,是本人第一次进入项目做的事情,前后涉及技术框架选型、主线框架移植、单元测试案例编写、单元测试案例及测试结果校对等事项,因为暂时没有代码权限,目前仅对书签云同步的排序进行了单元测试用例编写及测试,共14个单测用例,其中13个单测用例通过校验,1个与预期不符,该单测用例原因目前正在排查。
单元测试是很基础,也是很重要的开发质量保证模块,虽然开发人员大多数不愿意写单元测试,但是从长远来看,单元测试的作用是相当大的,体现在修改Bug后的代码校验、新人接手某功能模块、模块修改后的质量保证、最大化地减少软件功能集成及发布后的Bug发生概率。
虽然单元测试有很多优点,但也有缺点,比如单元测试代码往往是所测目标代码的2~4倍的体量,经过时间的积累,单元测试代码的维护是一个问题。
h2 { margin-top: 0.46cm; margin-bottom: 0.46cm; direction: ltr; line-height: 173%; text-align: justify; page-break-inside: avoid }
h2.western { font-family: "Calibri Light", serif; font-size: 16pt }
h2.cjk { font-size: 16pt }
h2.ctl { font-size: 16pt }
p { margin-bottom: 0.25cm; direction: ltr; line-height: 120%; text-align: justify }
原文地址:https://www.cnblogs.com/tgltt/p/9550216.html