Android单元测试研究与实践Word下载.docx
《Android单元测试研究与实践Word下载.docx》由会员分享,可在线阅读,更多相关《Android单元测试研究与实践Word下载.docx(22页珍藏版)》请在冰豆网上搜索。
在Java中,编写代码面对的只有类、对象、函数,编写单元测试时可以在测试工程中创建一个对象出来然后执行其函数进行测试,而在Android中,编写代码需要面对的是组件、控件、生命周期、异步任务、消息传递等,虽然本质是SDK主动执行了一些实例的函数,但创建一个Activity并不能让它执行到resume的状态,因此需要JUnit之外的框架支持。
当前主流的单元测试框架AndroidTest和Robolectric,前者需要运行在Android环境上,后者可以直接运行在JVM上,速度也更快,可以直接由Jenkins周期性执行,无需准备Android环境。
因此我们的单元测试基于Robolectric。
对于一些测试对象依赖度较高而需要解除依赖的场景,我们可以借助Mock框架。
Android单元测试环境配置
Robolectric环境配置
Android单元测试依旧需要JUnit框架的支持,Robolectric只是提供了Android代码的运行环境。
如果使用Robolectric3.0,依赖配置如下:
1.testCompile
'
junit:
4.10'
2.testCompile
org.robolectric:
robolectric:
3.0'
Gradle对Robolectric2.4的支持并不像3.0这样好,但Robolectric2.4所有的测试框架均在一个包里,另外参考资料也比较丰富,作者更习惯使用2.4。
如果使用Robolectric2.4,则需要如下配置:
1.classpath
robolectric-gradle-plugin:
0.14.+'
//这行配置在buildscript的dependencies中
2.
3.apply
plugin:
robolectric'
4.
5.androidTestCompile
2.4'
上述配置中,本文将testCompile写成androidTest,并且常见的Android工程的单元测试目录名称有test也有androidTest,这两种写法并没有功能上的差别,只是Android单元测试TestArtifact不同而已。
TestArtifact如图3所示:
图3TestArtifact
在Gradle插件中,这两种Artifact执行的Task还是有些区别的,但是并不影响单元测试的写法与效果。
虽然可以主动配置单元测试的项目路径,本文依旧建议采用与TestArtifact对应的项目路径和配置写法。
Mock配置
如果要测试的目标对象依赖关系较多,需要解除依赖关系,以免测试用例过于复杂,用Robolectric的Shadow是个办法,但是推荐更加简单的Mock框架,比如Mockito,该框架可以模拟出对象来,而且本身提供了一些验证函数执行的功能。
Mockito配置如下:
1.repositories
{
3.jcenter()
5.}
6.
7.dependencies
8.
9.testCompile
"
org.mockito:
mockito-core:
1.+"
10.
11.}
Robolectric使用介绍
Robolectric单元测试编写结构
单元测试代码写在项目的test(也可能是androidTest,该目录在项目中会呈浅绿色)目录下。
单元测试也是一个标准的Java工程,以类为文件单位编写,执行的最小单位是函数,测试用例(以下简称case)是带有@Test注解的函数,单元测试里面带有case的类由Robolectric框架执行,需要为该类添加注解@RunWith(RobolectricTestRunner.class)。
基于Robolectric的代码结构如下:
1.//省略一堆import
3.@RunWith(RobolectricTestRunner.class)
5.public
class
MainActivityTest
7.@Before
9.public
void
setUp()
11.//执行初始化的操作
12.
13.}
14.
15.<
a
href="
16.
17.public
testCase()
18.
19.//执行各种测试逻辑判断
20.
21.}
22.
23.}
上述结构中,带有@Before注解的函数在该类实例化后,会立即执行,通常用于执行一些初始化的操作,比如构造网络请求和构造Activity。
带有@test注解的是单元测试的case,由Robolectric执行,这些case本身也是函数,可以在其他函数中调用,因此,case也是可以复用的。
每个case都是独立的,case不会互相影响,即便是相互调用也不会存在多线程干扰的问题。
常见Robolectric用法
Robolectric支持单元测试范围从Activity的跳转、Activity展示View(包括菜单)和Fragment到View的点击触摸以及事件响应,同时Robolectric也能测试Toast和Dialog。
对于需要网络请求数据的测试,Robolectric可以模拟网络请求的response。
对于一些Robolectric不能测试的对象,比如ConcurrentTask,可以通过自定义Shadow的方式现实测试。
下面将着重介绍Robolectric的常见用法。
Robolectric2.4模拟网络请求
由于商业App的多数Activity界面数据都是通过网络请求获取,因为网络请求是大多数App首要处理的模块,测试依赖网络数据的Activity时,可以在@Before标记的函数中准备网络数据,进行网络请求的模拟。
准备网络请求的代码如下:
1.public
prepareHttpResponse(String
filePath)
throws
IOException
3.String
netData
=
FileUtils.readFileToString(FileUtils.
5.toFile(getClass().getResource(filePath)),
HTTP.UTF_8);
7.Robolectric.setDefaultHttpResponse(200,
netData);
9.}//代码适用于Robolectric
2.4,3.0需要注意网络请求的包的位置
由于Robolectric2.4并不会发送网络请求,因此需要本地创建网络请求所返回的数据,上述函数的filePath便是本地数据的文件的路径,setDefaultHttpResponse()则创建了该请求的Response。
上述函数执行后,单元测试工程便拥有了与本地数据数据对应的网络请求,在这个函数执行后展示的Activity便是有数据的Activity。
在Robolectric3.0环境下,单元测试可以发真的请求,并且能够请求到数据,本文依旧建议采用mock的办法构造网络请求,而不要依赖网络环境。
Activity展示测试与跳转测试
创建网络请求后,便可以测试Activity了。
测试代码如下:
1.<
3.public
testSampleActivity(){
5.SampleActivity
sampleActivity=Robolectric.buildActivity(SampleActivity.class).
7.create().resume().get();
9.assertNotNull(sampleActivity);
11.assertEquals("
Activity的标题"
sampleActivity.getTitle());
Robolectric.buildActivity()用于构造Activity,create()函数执行后,该Activity会运行到onCreate周期,resume()则对应onResume周期。
assertNotNull和assertEquals是JUnit中的断言,Robolectric只提供运行环境,逻辑判断还是需要依赖JUnit中的断言。
Activity跳转是Android开发的重要逻辑,其测试方法如下:
testActivityTurn(ActionBarActivity
firstActivity,
Class
secondActivity)
5.Intent
intent
new
Intent(firstActivity.getApplicationContext(),
secondActivity);
7.assertEquals(intent,
Robolectric.shadowOf(firstActivity).getNextStartedActivity());
//3.0的API与2.4不同
9.}
Fragment展示与切换
Fragment是Activity的一部分,在Robolectric模拟执行Activity过程中,如果触发了被测试的代码中的Fragment添加逻辑,Fragment会被添加到Activity中。
需要注意Fragment出现的时机,如果目标Activity中的Fragment的添加是执行在onResume阶段,在Activity被Robolectric执行resume()阶段前,该Activity中并不会出现该Fragment。
采用Robolectric主动添加Fragment的方法如下:
addfragment(Activity
activity,
int
fragmentContent){
5.FragmentTestUtil.startFragment(activity.getSupportFragmentManager().findFragmentById(fragmentContent));
7.Fragment
fragment
activity.getSupportFragmentManager().findFragmentById(fragmentContent);
9.assertNotNull(fragment);
startFragment()函数的主体便是常用的添加fragment的代码。
切换一个Fragment往往由Activity中的代码逻辑完成,需要Activity的引用。
控件的点击以及可视验证
testButtonClick(int
buttonID){
5.Button
submitButton
(Button)
activity.findViewById(buttonID);
7.assertTrue(submitButton.isEnabled());
9.submitButton.performClick();
11.//验证控件的行为
对控件的点击验证是调用performClick(),然后断言验证其行为。
对于ListView这类涉及到Adapter的控件的点击验证,写法如下:
1.//listView被展示之后
3.listView.performItemClick(listView.getAdapter().getView(position,
null,
null),
0,
0);
与button等控件稍有不同。
Dialog和Toast测试
测试Dialog和Toast的方法如下:
testDialog(){
3.Dialog
dialog
ShadowDialog.getLatestDialog();
5.assertNotNull(dialog);
7.}
testToast(String
toastContent){
11.ShadowHandler.idleMainLooper();
13.assertEquals(toastContent,
ShadowToast.getTextOfLatestToast());
15.}
上述函数均需要在Dialog或Toast产生之后执行,能够测试Dialog和Toast是否弹出。
Shadow写法介绍
Robolectric的本质是在Java运行环境下,采用Shadow的方式对Android中的组件进行模拟测试,从而实现Android单元测试。
对于一些Robolectirc暂不支持的组件,可以采用自定义Shadow的方式扩展Robolectric的功能。
1.@Implements(Point.class)
ShadowPoint
5.@RealObject
private
Point
realPoint;
7....
__constructor__(int
x,
y)
11.realPoint.x
x;
13.realPoint.y
y;
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(一个可替代真实函数的结果的返回值);
3.//上述代码是设置sample.dosomething()的返回值,当执行了sample.dosomething()这个函数时,就会得到someAction,从而解除了对真实的sample.dosomething()函数的依赖
上述代码为被测函数定义一个可替代真实函数的结果的返回值。
当使用这个函数后,这个可验证的结果便会产生影响,从而代替函数的真实结果,这样便解除了对真实函数的依赖。
同时Mock框架也可以验证函数的执行次数,代码如下:
//Mock得到一个对象
3.list.add
(1);
//执行一个函数
5.verify(list).add
(1);
//验证这个函数的执行
7.verify(list,time(3)).add
(1);
//验证这个函数的执行次数
在一些需要解除网络依赖的场景中,多使用Mock。
比如对retrofit框架的网络依赖解除如下:
1.//代码参考了参考文献[3]
MockClient
implements
Client
5.@Override
7.public
Response
execute(Request
request)
9.Uri
uri
Uri.parse(request.getUrl());
11.String
responseString
;
13.if(uri.getPath().equals("
/path/of/interest"
))
15.responseString
返回的json1"
//这里是设置返回值
17.}
else
19.responseString
返回的json2"
23.return
Response(request.getUrl(),
200,
nothing"
Collections.EMPTY_LIST,
TypedByteArray("
application/json"
responseString.getBytes()));
24.
25.}
26.
27.}
28.
29.//MockClient使用方式如下:
30.
31.RestAdapter.Builder
builder
RestAdapter.Builder();
32.
33.builder.setClient(new
MockClient());
这种方式下retrofit的response可以由单元测试编写者设置,而不来源于网络,从而解除了对网络环境的依赖。
在实际项目中使用Robolectric构建单元测试
单元测试的范围
在Android项目中,单元测试的对象是组件状态、控件行为、界面元素和自定义函数。
本文并不推荐对每个函数进行一对一的测试,像onStart()、onDestroy()这些周期函数并不需要全部覆盖到。
商业项目多采用Scrum模式,要求快速迭代,有时候未必有较多的时间写单元测试,不再要求逐个函数写单元测试。
本文单元测试的case多来源于一个简短的业务逻辑,单元测试case需要对这段业务逻辑进行验证。
在验证的过程中,开发人员可以深度了解业务流程,同时新人来了看一下项目单元测试就知道哪个逻辑跑了多少函数,需要注意哪些边界——是的,单元测试