蘑菇街支付金融Android单元测试实践(3)

发表于:2016-05-20来源:推酷作者:不详点击数: 标签:单元测试
现在,关键的地方来了,Component本身是不生产dependency的,它只是搬运工而已,真正生产dependency的地方在 Module。所以,创建Component需要用到Module,不同的

  现在,关键的地方来了,Component本身是不生产dependency的,它只是搬运工而已,真正生产dependency的地方在 Module。所以,创建Component需要用到Module,不同的Module生产出不同的dependency。在正式代码里面,我们使用正常的Module,生产正常的DataModel。而在测试环境中,我们写一个TestingModule,让它继承正常的Module,然后 override掉生产DataModel的方法,让它生产mock的DataModel。在跑单元测试的时候,使用这个TestingModule来创建Component,这样的话,DataActivity通过Component得到的DataModel对象就是mock出来的DataModel对象。

  使用这种方式,所有production code都不用专门为testing增加任何多余的代码,同时还能得到依赖注入的其他好处。

  Robolectric:解决Android单元测试最大的痛点

  接下来讲讲Android单元测试最大的痛点,那就是JVM上面运行纯JUnit单元测试时是不能使用Android相关的类的,因为我们开发用到的安卓环境是没有实现的,里面只定义了一些接口,所有方法的实现都是throw new RuntimeException("stub");,如果我们单元测试代码里面用到了安卓相关的代码的话,那么运行时就会遇到 RuntimeException("Stub")。

  要解决这个问题,一般来说有三种方案:

  使用Android提供的Instrumentation系统,将单元测试代码运行在模拟器或者是真机上。

  用一定的架构,比如 MVP 等等,将安卓相关的代码隔离开了,中间的Presenter或Model是存java实现的,可以在JVM上面测试。View或其他android相关的代码则不测。

  使用 Robolectric 框架,这个框架基本可以理解为在JVM上面实现了一套安卓的模拟环境,同时给安卓相关的类增加了其他一些增强的功能,以方便做单元测试,使用这个框架,我们就可以在JVM上面跑单元测试的时候,就可以使用安卓相关的类了。

  第一种方案能work,但是速度非常慢,因为每次运行一次单元测试,都需要将整个项目打包成apk,上传到模拟器或真机上,就跟运行了一次app似得,这个显然不是单元测试该有的速度,更无法做TDD。这种方案首先被否决。

  刚开始,我们采用的是Robolectric,原因有两个:

  我们项目当时还没有比较清楚的架构,android跟纯java代码的隔离没有做好;

  很多安卓相关的代码,还是需要测试的,比如说自定义View等等。

  然而慢慢的,我们的态度从拥抱Robolectric,到尽量不用它,尽量使用纯java代码去实现。可能大家觉得安卓相关的代码会很多,而纯 java的很少,然而慢慢的你会发现,其实不是这样的,纯java的代码其实真不少,而且往往是核心的逻辑所在。之所以尽量不用Robolectric,是因为Robolectric虽然相对于Instrumentation testing来说快多了。但毕竟他也需要merge一些资源,build出来一个模拟的app,因此相对于纯java和JUnit来说,这个速度依然是很慢的。

  用具体的数字来对比说明:

  运行Instrumentation testing:几十秒,取决于app的大小

  Robolectric:10秒左右

  JUnit:几秒钟之内

  当然,虽然运行一次Robolectric在10秒左右,但是对比运行一次app,还是要快太多。因此,刚开始的时候,从Robolectric开始完全是OK的。

  以上就是现在我们这边单元测试用到的几个基本技术:JUnit4 + Mockito + Dagger2 + Robolectric。基本来说,并没有什么黑科技,都是业界标准。

  一个具体的案例

  接下来,我通过一个具体的案例,跟大家介绍一下,我们这边的一个app,具体是怎么单测的。

  这里是我们收银台界面的样子:

  假设Activity名字为CheckoutActivity,当它启动的时候,CheckoutActivity会去调一个CheckoutModel 的loadCheckoutData()方法,这个方法又会去调更底层的一个封装了用户认证等信息的网络请求Api类(mApi)的get方法,同时传给这个Api类一个callback。这个callback的做的事情是将结果通过 Otto Bus (mBus) post出去。CheckoutActivity里面Subscribe了这个Event(方法名是onCheckoutDataLoaded()),然后根据Event的值相应的显示数据或错误信息。

  代码简写如下:

  这里,CheckoutActivity里面的mCheckoutModel、CheckoutModel里面的mApi、CheckoutModel里面的mBus,都是通过Dagger2注入进去的。在做单元测试的时候,这些都是mock。

原文转自: http://www.infoq.com/cn/articles/mogujie-android-unit-testing