2.
3.public void testButtonClick(int buttonID){
4.
5.Button submitButton = (Button) activity.findViewById(buttonID);
6.
7.assertTrue(submitButton.isEnabled());
8.
9.submitButton.performClick();
10.
11.//验证控件的行为
12.
13.}
对控件的点击验证是调用performClick(),然后断言验证其行为。
对于ListView这类涉及到Adapter的控件的点击验证,写法如下:
1.//listView被展示之后
2.
3.listView.performItemClick(listView.getAdapter().getView(position, null, null), 0, 0);
与button等控件稍有不同。
Dialog和Toast测试
测试Dialog和Toast的方法如下:
1.public void testDialog(){
2.
3.Dialog dialog = ShadowDialog.getLatestDialog();
4.
5.assertNotNull(dialog);
6.
7.}
8.
9.public void testToast(String toastContent){
10.
11.ShadowHandler.idleMainLooper();
12.
13.assertEquals(toastContent, ShadowToast.getTextOfLatestToast());
14.
15.}
上述函数均需要在Dialog或Toast产生之后执行,能够测试Dialog和Toast是否弹出。
Shadow写法介绍
Robolectric的本质是在Java运行环境下,采用Shadow的方式对Android中的组件进行模拟测试,从而实现Android单元测试。
对于一些Robolectirc暂不支持的组件,可以采用自定义Shadow的方式扩展Robolectric的功能。
1.@Implements(Point.class)
2.
3.public class ShadowPoint {
4.
5.@RealObject private Point realPoint;
6.
7....
8.
9.public void __constructor__(int x, int y) {
10.
11.realPoint.x = x;
12.
13.realPoint.y = y;
14.
15.}
16.
17.}//样例来源于Robolectric官网
上述实例中,@Implements是声明Shadow的对象,@RealObject是获取一个Android对象,constructor则是该Shadow的构造函数,Shadow还可以修改一些函数的功能,只需要在重载该函数的时候添加@Implementation,这种方式可以有效扩展Robolectric的功能。
Shadow是通过对真实的Android对象进行函数重载、初始化等方式对Android对象进行扩展,Shadow出来的对象的功能接近Android对象,可以看成是对Android对象一种修复。
自定义的Shadow需要在config中声明,声明写法是@Config(shadows=ShadowPoint.class)。
Mock写法介绍
对于一些依赖关系复杂的测试对象,可以采用Mock框架解除依赖,常用的有Mockito。
例如Mock一个List类型的对象实例,可以采用如下方式:
1.List list = mock(List.class); //mock得到一个对象,也可以用@mock注入一个对象
所得到的list对象实例便是List类型的实例,如果不采用mock,List其实只是个接口,我们需要构造或者借助ArrayList才能进行实例化。
与Shadow不同,Mock构造的是一个虚拟的对象,用于解耦真实对象所需要的依赖。
Mock得到的对象仅仅是具备测试对象的类型,并不是真实的对象,也就是并没有执行过真实对象的逻辑。
Mock也具备一些补充JUnit的验证函数,比如设置函数的执行结果,示例如下:
1.When(sample.dosomething()).thenReturn(someAction);//when(一个函数执行).thenReturn(一个可替代真实函数的结果的返回值);
2.
3.//上述代码是设置sample.dosomething()的返回值,当执行了sample.dosomething()这个函数时,就会得到someAction,从而解除了对真实的sample.dosomething()函数的依赖
上述代码为被测函数定义一个可替代真实函数的结果的返回值。
当使用这个函数后,这个可验证的结果便会产生影响,从而代替函数的真实结果,这样便解除了对真实函数的依赖。
同时Mock框架也可以验证函数的执行次数,代码如下:
1.List list = mock(List.class); //Mock得到一个对象
2.
3.list.add
(1); //执行一个函数
4.
5.verify(list).add
(1); //验证这个函数的执行
6.
7.verify(list,time(3)).add
(1); //验证这个函数的执行次数
在一些需要解除网络依赖的场景中,多使用Mock。
比如对retrofit框架的网络依赖解除如下:
1.//代码参考了参考文献[3]
2.
3.public class MockClient implements Client {
4.
5.@Override
6.
7.public Response execute(Request request) throws IOException {
8.
9.Uri uri = Uri.parse(request.getUrl());
10.
11.String responseString = "";
12.
13.if(uri.getPath().equals("/path/of/interest")) {
14.
15.responseString = "返回的json1";//这里是设置返回值
16.
17.} else {
18.
19.responseString = "返回的json2";
20.
21.}
22.
23.return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json",responseString.getBytes()));
24.
25.}
26.
27.}
28.
29.//MockClient使用方式如下:
30.
31.RestAdapter.Builder builder = new RestAdapter.Builder();
32.
33.builder.setClient(new MockClient());
这种方式下retrofit的response可以由单元测试编写者设置,而不来源于网络,从而解除了对网络环境的依赖。
在实际项目中使用Robolectric构建单元测试
单元测试的范围
在Android项目中,单元测试的对象是组件状态、控件行为、界面元素和自定义函数。
本文并不推荐对每个函数进行一对一的测试,像onStart()、onDestroy()这些周期函数并不需要全部覆盖到。
商业项目多采用Scrum模式,要求快速迭代,有时候未必有较多的时间写单元测试,不再要求逐个函数写单元测试。
本文单元测试的case多来源于一个简短的业务逻辑,单元测试case需要对这段业务逻辑进行验证。
在验证的过程中,开发人员可以深度了解业务流程,同时新人来了看一下项目单元测试就知道哪个逻辑跑了多少函数,需要注意哪些边界——是的,单元测试