Android中的单元测试
上一篇 / 下一篇 2012-08-13 09:36:35 / 个人分类:杂谈
随着Agile的普及,以及开发人员对测试重要性的认识逐步加深,单元测试已经成了越来越多软件项目开发中不可缺少的一部分。无论项目是不是采用TDD的形式来进行开发,单元测试都能够为项目的修改和重构提供一定的保障。51Testing软件测试网 sY_:R2K&?FfS
Ed.t|;C6Y)ie*M0 Android作为主要的移动平台之一,吸引了无数的开发人员。但面对Android平台和环境的种种限制,很多开发人员往往有心无力,很难为其项目添加全面有效的单元测试。51Testing软件测试网_.E+}(q#x#M0V
4`]%PQ/Dg;on%Up0 Android平台的开发环境中集成了一个测试框架(InstrumentedTest),用于支持其单元测试和验收测试。Robotium同样提供一个类似于Selenium的测试框架,使得开发人员可以对应用的功能进行验证。这两种方式提供的测试环境都类似于集成测试,它们的测试用例都 需要运行在模拟器上,通过对模拟器的操作或者mock,来触发函数调用,进而对其结果进行验证。这种方法通常粒度较大,测试的编写和维护较为困难,而最为 重要的是,由于测试需要运行于模拟器或测试机器上,我们在运行前需要将测试和应用打包,进行部署安装,并最终运行在模拟器或测试机器的Delvik虚拟机 上,其运行速度较普通的单元测试要慢许多,如果使用TDD来进行开发,根本无法达到快速开发的要求。
(PF;v/D9r[051Testing软件测试网9W$Y/j%zDA,`1c之所以这些框架的测试用例都需要在 模拟器中运行,是因为我们平时在开发时所使用的Andorid.jar是被精简过的,只是用于日常开发的,它只是一个placeholder,使得我们在 开发时能够不出编译错误,它完全是一个stub包,其中所有的类都只是Android平台接口的一个stub,如果在代码中运行这个 Android.jar,它们所有的方法都只会抛出一个java.lang.RuntimeException(“Stub!”)异常。所以,一旦测试代码需要真正调用Android平台相关的类或接口,它们就必须运行于真正实现了Android的环境,如模拟器或者是测试机器。
)gXIb[r/b@&iC(~S051Testing软件测试网W@|,km@1i我们的另外一个选择是只对POJO进行单元测试,如果遇到Android相关的代码,就使用Mock框架对其进行模拟。这种方式一定程度上可以解决我们 的问题,但这意味着我们需要大量的在测试环境中使用mock和stub。另外,虽然Android中界面的布局通常使用XML来实现,但项目的代码中还是 会存在各种对界面的操作和更新,UI和逻辑的耦合使测试更加不易。
qi:}YT;I4~0[2k051Testing软件测试网C.pm(P,aW而且即使这样,由于Android平台的复杂性(static方法,final方法和类,Context和Resources的管理),我们也很难对Android相关的代码进行测试,以保证测试率。
f2{0~U:H)M Xp051Testing软件测试网)i?+OnjID那么如何能够在不增加开发成本的情况下,有一个稳定快速的单元测试环境呢?
J z.aVK+B;F0w"BiU2aMS0 我们目前的选择是使用MVP模式和Robolectric
$B1F!_h%pY_H051Testing软件测试网-Q7J5|"Sv(l6k"cHAndroid的体系结构非常适合于使用MVP模式进行开发,与MVC模式不同,Android中的Activity并不是一个标准的 Controller,它的首要职责是加载应用的布局和初始化用户界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提 升,Activity类的职责不断增加,以致变得庞大臃肿。当我们将其中复杂的逻辑处理移至另外的一个类(Presneter)中时,Activity其 实就是MVP模式中View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻 辑(复杂的逻辑交由Presenter处理)。如图所示:51Testing软件测试网 V(v w.x+l+g,pB2w(N#\
通过这种职责的分离,一方面代码的可读性得到了提高,另一方面我们可以更为方便地通过mock Activity的方式对各种逻辑(Presenter中的方法)进行测试。51Testing软件测试网7I;Awu*m
对于测试环境的搭建和测试Android相关的代码,我们则借助于Robolectric的帮助。51Testing软件测试网)mQ/V:{0m??&~
Robolectric在其所提供的测试框架中,完全模拟了Android SDK的jar文件(不会再有恼人的stub异常),它使得我们的测试可以运行于JVM之上(速度得到大幅度的提升),因此我们可以用它对Android 应用进行测试驱动开发。Roblectric同时实现了Android中对XML的解析,模拟了View,Layout,以及资源的加载,它使得 Android的环境对于开发人员来说更像是一个黑盒,从而使开发人员不用大量使用mock,就可以方便的对资源状态和Android相关的代码进行测 试。51Testing软件测试网{ [o}o&~b0@7V
Robolectric是如何做到这点的呢?51Testing软件测试网)a4Ta}D1\
Robolectric使用了javassist在运行时动态修改Android.jar中类的byte code,Robolectric会在JVM加载Android.jar包的时候,重写其中类的方法。Roblectroic会让这些方法有返回值 (null或是0) 而不是抛出异常 ,或者将这些方法调用转向Shadow Objects来模拟Android SDK的实现。Shadow Objects是Robolectric在运行时插入到Android.jar包相应的类中的,它们会实际处理方法的调用,并记录相应的状态,以备在 assert的时候进行查询。如图所示。Robolectric提供了大量的Shadow Objects,覆盖了测试开发过程中绝大多数逻辑功能的需要。51Testing软件测试网EDaG~Kc
Y4{uZu H K6k3V051Testing软件测试网/h2C w4RxRobolectric的使用51Testing软件测试网(T^:z1E(I/|-n
@4[2g F]2S9xN&c0 基于Robolectric的测试需要使用其特定的test runner(RobolectricTestRunner)来运行,我们可以通过扩展RobolectricTestRunner来创建一个自己的 test runner,并在其构造函数中设定需要加载的AndroidManifest.xml和resource目录 。如:
UPN ?%D+w.n!~Ii3X051Testing软件测试网U3O"vn O)FE5MO*D*ljA+];r D8\0public class MyTestRunner extends RobolectricTestRunner {51Testing软件测试网;b1|5R3m@7{
dl.k public MyTestRunner(Class<?> testClass) throws InitializationError { :vWK#S-V(ZK0 super(testClass, new RobolectricConfig(new File("my_app/AndroidManifest.xml"), new File("my_app/res"))); +|!C)DIV"@]\,X0 }51Testing软件测试网Q5Z!P5{L } |
6cr#I-q j'A#_0 有了自己的test runner,我们可以来写一个简单的Robolectric测试了51Testing软件测试网)Br rtQ,G
51Testing软件测试网9t%uyP{,giC2g51Testing软件测试网9pp:F&O