android 源码下载部分单元测试代码分析

最近因为业务需要,在研究android源码的下载部分。因为要考虑代码质量的问题,所以就针对源码的测试用例做了一些分析,并在源码用例的基础上添加了我们自己增加接口部分的用例。测试代码的分析比较偏门,网上也很少见,这里权作探讨,欢迎纠错。

在分析这个代码之前,能想到的一些问题:

1.下载这部分涉及到网络环境、数据库、contentprovider、广播等依赖硬件或外部环境较多的部分,如何测试;

2.网络环境复杂多变,如何能够测试全面;

3.我们了解下载系统的复杂程度,是以DownloadService为核心控制下载任务的刷新,
DownloadProvider供增删改查各种任务为基础,供app用户管理下载任务、查询下载进度和状态,真正下载的实现则在DownloadThread中——这样一个复杂的模块的测试从何下手。

然后,大体分析一下测试源码的结构,我使用EA导入代码工程的方式生成了类图,再大体调整了一下位置布局,很方便。

来仔细分析一下测试源码的类结构:

  1. 以继承关系为骨架看的话,可以分为三层,很明显抽象类是不能直接作为干活的测试类的,继承关系中的叶子节点类才是真正跑用例的类。那么其余部分肯定是为跑测试做各方面的准备;

  2. 最基础的 AbstractDownloadProviderFunctionalTest,是第一层的中心。左边两个是它的内部类,并且还维护着这两个内部类的实体;右面是对下载系统源码中SystemFacade的一个模拟实现类的依赖,同样维护着一个实体;在看各位组件的类名:TestContext、MockContentResolverWithNotify、FakeSystemFacade,可以说是假货一条街。总结来说,第一层就是以AbstractDownloadProviderFunctionalTest为中心的组合关系,该类维护了一系列假货的实体,那么大概齐是什么功能也能猜个八九了,具体是怎样还要读源码来验证;

  3. 第二层在继承前类的同时又有拓展。该层已经有一个叶子类了,DownloadProviderFunctionalTest,从一些共有方法能看到测试用例的定义,同时根据类名也能看出一二;AbstractPublicApiTest则是它的兄弟类,继续抽象,仔细一点可以看出这里添加了一个重要的实体:DownloadManager,继续做准备吧,至于它的内部类Download结构上看不出什么,还需要看接口和源码才能了解具体功能;

  4. 第三层,就都是叶子类了,也是大规模测试用例的聚集地。在前面两层的准备之后,可谓万事俱备,PublicApiFunctionalTest、ThreadingTest,看名字就知道了。

  5. HelpersTest 该类是个闲散人员,不依赖任何环境的模拟。所以可以猜测是测试纯功能函数的。

了解了大体结构之后,就需要读源码了。走读一下源码,不多,大概不到1500行的样子。然后就可以拿到大体上每个类都做了什么,如下:

  1. AbstractDownloadProviderFunctionalTest 是基础抽象类,这里完成了context、systemFacade、server、contentResolver等基础环境的假冒伪劣活动,同时提供了伪造服务器响应的方法供子类使用;

  2. AbstractPublicApiTest 是上面那位的子类,同样是抽象的,这里封装了以DownloadID为核心的内部类Download供后续断言使用;引入了DownloadManager的工具实体,同时提供了伪造客户端请求的各种方法供子类使用;

  3. DownloadProviderFunctionalTest 绕开了对DownloadManager的测试,它使用暴力插入数据库的方法添加下载,单纯的测试下载器的功能,所以它只继承了①;

  4. PublicApiFunctionalTest 主要是针对DownloadManager的功能api测试,这里模拟了各种各样的下载环境,实现了各种网络情况下的功能单元测试;

  5. ThreadingTest 主要针对频繁开下载服务的特殊情况进行测试;

  6. HelpersTest 则是比较独立的测试,测试/module/DownloadSystem 中Util包 Helpers 工具类中的方法;

好。最后我们回到刚开始的那几个问题。通过类图解析和代码走读大概就能得到答案。

其一,android junit框架提供了对android service的测试支持,上面的所有测试类都是基于ServiceTestCase 实现的;

其二,可以找到各种mock工具来模拟各种依赖于硬件、网络的接口实现,下载测试中使用的就是 mockwebserver.jar,来模拟服务器、响应;

其三,测试代码也需要伪造相关的环境,context、resolver,包括功能源码将系统相关的信息抽象成接口,在测试的过程中测试代码可以实现测试所需要的fake版本,不得不说这就是代码可测试性良好的一个体现,目的就是绕开或模拟任何不可预期的依赖,来针对测试目标代码完成对逻辑的测试;

其四,对于各种网络环境的测试,实际上是通过模拟不同的服务器响应来实现的,绝大部分用例都是围绕这个思路来编写的。不过是否能覆盖到所有情况的下载还是未知数;

其五,针对一个复杂的功能系统来测试,整个测试代码的架构显然是需要提前设计的。庖丁解牛一般,一步一步把所有的环境都模拟好,再动手编写各个细节点的测试用例。当然,这与被测试的功能源码的结构设计、可测试性的考虑也是有关系的。这就是传说中的设计吧,路漫漫其修远兮啊。

感谢您赏个荷包蛋~