对于这个流程,我们做了如下的单元测试:
CheckoutActivity启动单元测试:通过Robolectric提供的方法,启动一个Activity。验证里面的mCheckoutModel的loadCheckoutData()方法得到了调用,同时参数(订单ID等)是对的。
CheckoutModel的loadCheckoutData单元测试1:调用CheckoutModel的loadCheckoutData()方法,验证里面的mApi对应的get方法得到了调用,同时参数是对的。
CheckoutModel的loadCheckoutData单元测试 2:mock Api类,指定当它的get方法在收到某些调用的时候,直接调用传入的callback的onSuccess方法,然后调用CheckoutModel的 loadCheckoutData()方法,验证Otto bus的post方法得到了调用,并且参数是对的。
CheckoutModel的loadCheckoutData单元测试 3:mock api类,指定当它的get方法在收到某些调用的时候,直接调用传入的callback的onFailure方法,然后调用CheckoutModel的 loadCheckoutData()方法,验证Otto bus的post方法得到了调用,并且参数是对的。
CheckoutActivity的onCheckoutDataLoaded单元测试1:启动一个CheckoutActivity,调用他的onCheckoutDataLoaded(),传入含有正确数据的Event,验证相应的数据view显示出来了
CheckoutActivity的onCheckoutDataLoaded单元测试2:启动一个CheckoutActivity,调用他的onCheckoutDataLoaded(),传入含有错误信息的Event,验证相应的错误提示view显示出来了。
这里需要说明的一点是,上面的每一个测试,都是独立进行的,不是说下面的单元测试依赖于上面的。或者说必须先做上面的,再做下面的。
这部分较为详细的代码放在 github (https://github.com/ChrisZou/android-unit-testing-tutorial)上,groupshare这个package里面。
其他的问题
以上就是我们这边做单元测试用到的技术,以及一个基本流程,下面聊聊其他的几个问题。
哪些东西需要测试呢?
所有的Model、Presenter/ViewModel、Api、Utils等类的public方法
Data类除了getter、setter、toString、hashCode等一般自动生成的方法之外的逻辑部分
自定义View的功能:比如set data以后,text有没有显示出来等等,简单的交互,比如click事件,负责的交互一般不测,比如touch、滑动事件等等。
Activity的主要功能:比如view是不是存在、显示数据、错误信息、简单的点击事件等。比较复杂的用户交互比如onTouch,以及view的样式、位置等等可以不测。因为不好测。
CI和code coverage: Jacoco
要把单元测试正式化,CI是非常重要的一步,我们有一个运行Jenkins的CI server,每次开发者push代码到master branch的时候,会运行一次单元测试的gradle task,同时使用 Jacoco 做code coverage。
这里有个坑要特别注意,那就是项目里面的gradle Jacoco插件和 Jenkins的Jacoco插件 的兼容性问题。我们用的gradle Jacoco插件是7.1,更高版本的好像有问题。然后对应的Jenkins的Jacoco插件需要1.0.19或更低版本的,更高版本的jenkins plugin不支持低版本的gradle Jacoco项目版本。实际上,这点在Jenkins的Jacoco插件首页就有说明:
(点击放大图像)
但是我当时没注意,所以覆盖率数据一直出不来,折腾了好一会,最后还是在同事的帮助下找到问题了。
遇到的坑,以及好的practice建议
接下来讲讲我们遇到的一些坑,以及一些好的practice建议。
1. Native libary
无论是纯JUnit还是Robolectric,都不支持load native library,会报UnsatisfiedLinkError的错。所以如果你的被测代码里面用到了native lib,那么可能需要给System.loadLibrary加上try catch。
如果是被测代码用到的第三方lib,而里面用到了native lib的话,一般有两种解决办法,一种是将用到native lib的第三方类外面自己在包一层,然后在测试的情况下mock掉。第二种是用Robolectric,给那个类创建一个shadow class。
第一种方法的好处是可以在测试的时候随时改变这个类的返回值或行为,缺点是需要另外创建一个wrapper类,会有点繁琐。第二种方式不能随时改变这个类的行为,但是写起来非常简单。所以,看自己的需要,选择相应的方法。
这两种方法,也是解决static method, final class/method不能mock的主要方式。
2. 尽量写出易于测试的代码
static method、直接new object、singleton、Global state等等这些都是一些不利于测试的代码方式,应该尽量避免,用依赖注入来代替这些方式。
原文转自: http://www.infoq.com/cn/articles/mogujie-android-unit-testing