Robolectric使用教程.docx
- 文档编号:11242770
- 上传时间:2023-02-26
- 格式:DOCX
- 页数:21
- 大小:21.84KB
Robolectric使用教程.docx
《Robolectric使用教程.docx》由会员分享,可在线阅读,更多相关《Robolectric使用教程.docx(21页珍藏版)》请在冰豆网上搜索。
Robolectric使用教程
Robolectric使用教程
概述
Android的单元测试可以分为两部分:
Localunittests:
运行于本地JVM
Instrumentedtest:
运行于真机或者模拟器
如果使用Local测试,需要保证测试过程中不会调用Android系统API,否则会抛出RuntimeException异常,因为Local测试是直接跑在本机JVM的,而之所以我们能使用Android系统API,是因为编译的时候,我们依赖了一个名为“android.jar”的jar包,但是jar包里所有方法都是直接抛出了一个RuntimeException,是没有任何任何实现的,这只是Android为了我们能通过编译提供的一个Stub!
当APP运行在真实的Android系统的时候,由于类加载机制,会加载位于framework的具有真正实现的类。
由于我们的Local是直接在PC上运行的,所以调用这些系统API便会出错。
那么问题来了,我们既要使用Local测试,但测试过程又难免遇到调用系统API那怎么办?
其中一个方法就是mockobjects,比如借助Mockito,另外一种方式就是使用Robolectric,Robolectric就是为解决这个问题而生的。
它实现一套JVM能运行的Android代码,然后在unittest运行的时候去截取android相关的代码调用,然后转到他们的他们实现的Shadow代码去执行这个调用的过程
如何使用?
为项目添加依赖
testCompile"org.robolectric:
robolectric:
3.1.4"
Robolectric在第一次运行时,会下载一些sdk依赖包,每个sdk依赖包大概50M,下载速度比较慢,用户可以直接在网上下载相应依赖包,放置在本地maven仓库地址中,默认路径为:
C:
\Users\username\.m2\repository\org\robolectric
指定RobolectricTestRunner为运行器
为测试用例添加注解,指定测试运行器为RobolectricTestRunner。
注意,这里要通过Config指定constants=BuildConfig.class,Robolectric会通过constants推导出输出路径,如果不进行配置,Robolectric可能不能找到你的manifest、resources和assets资源
@RunWith(RobolectricTestRunner.class)
@Config(constants=BuildConfig.class)
publicclassMainActivityTest{
}
什么是Shadow类
Shadow是Robolectric的立足之本,如其名,作为影子,一定是变幻莫测,时有时无,且依存于本尊。
Robolectric定义了大量模拟Android系统类行为的Shadow类,当这些系统类被创建的时候,Robolectric会查找对应的Shadow类并创建一个Shadow类与原始类关联。
每当系统类的方法被调用的时候,Robolectric会保证Shadow对应的方法会调用。
这些Shadow对象,丰富了本尊的行为,能更方便的对Android相关的对象进行测试。
比如,我们可以借助ShadowActivity验证页面是否正确跳转了
/**
*验证点击事件是否触发了页面跳转,验证目标页面是否预期页面
*
*@throwsException
*/
@Test
publicvoidtestJump()throwsException{
//默认会调用Activity的生命周期:
onCreate->onStart->onResume
MainActivityactivity=Robolectric.setupActivity(MainActivity.class);
//触发按钮点击
activity.findViewById(R.id.activity_main_jump).performClick();
//获取对应的Shadow类
ShadowActivityshadowActivity=Shadows.shadowOf(activity);
//借助Shadow类获取启动下一Activity的Intent
IntentnextIntent=shadowActivity.getNextStartedActivity();
//校验Intent的正确性
assertEquals(nextIntent.getComponent().getClassName(),SecondActivity.class.getName());
}
@Config配置
可以通过@Config定制Robolectric的运行时的行为。
这个注解可以用来注释类和方法,如果类和方法同时使用了@Config,那么方法的设置会覆盖类的设置。
你可以创建一个基类,用@Config配置测试参数,这样,其他测试用例就可以共享这个配置了
配置SDK版本
Robolectric会根据manifest文件配置的targetSdkVersion选择运行测试代码的SDK版本,如果你想指定sdk来运行测试用例,可以通过下面的方式配置
@Config(sdk=Build.VERSION_CODES.JELLY_BEAN)
publicclassSandwichTest{
@Config(sdk=Build.VERSION_CODES.KITKAT)
publicvoidgetSandwich_shouldReturnHamSandwich(){
}
}
配置Application类
Robolectric会根据manifest文件配置的Application配置去实例化一个Application类,如果你想在测试用例中重新指定,可以通过下面的方式配置
@Config(application=CustomApplication.class)
publicclassSandwichTest{
@Config(application=CustomApplicationOverride.class)
publicvoidgetSandwich_shouldReturnHamSandwich(){
}
}
指定Resource路径
Robolectric可以让你配置manifest、resource和assets路径,可以通过下面的方式配置
@Config(manifest="some/build/path/AndroidManifest.xml",
assetDir="some/build/path/assetDir",
resourceDir="some/build/path/resourceDir")
publicclassSandwichTest{
@Config(manifest="other/build/path/AndroidManifest.xml")
publicvoidgetSandwich_shouldReturnHamSandwich(){
}
}
使用第三方LibraryResources
当Robolectric测试的时候,会尝试加载所有应用提供的资源,但如果你需要使用第三方库中提供的资源文件,你可能需要做一些特别的配置。
不过如果你使用gradle来构建Android应用,这些配置就不需要做了,因为GradlePlugin会在build的时候自动合并第三方库的资源,但如果你使用的是Maven,那么你需要配置libraries变量:
@RunWith(RobolectricTestRunner.class)
@Config(libraries={
"build/unpacked-libraries/library1",
"build/unpacked-libraries/library2"
})
publicclassSandwichTest{
}
使用限定的资源文件
Android会在运行时加载特定的资源文件,如根据设备屏幕加载不同分辨率的图片资源、根据系统语言加载不同的string.xml,在Robolectric测试当中,你也可以进行一个限定,让测试程序加载特定资源.多个限定条件可以用破折号拼接在在一起。
/**
*使用qualifiers加载对应的资源文件
*
*@throwsException
*/
@Config(qualifiers="zh-rCN")
@Test
publicvoidtestString()throwsException{
finalContextcontext=RuntimeEnvironment.application;
assertThat(context.getString(R.string.app_name),is("单元测试Demo"));
}
Properties文件
如果你嫌通过注解配置上面的东西麻烦,你也可以把以上配置放在一个Properties文件之中,然后通过@Config指定配置文件,比如,首先创建一个配置文件robolectric.properties:
#放置Robolectric的配置选项:
sdk=21
manifest=some/build/path/AndroidManifest.xml
assetDir=some/build/path/assetDir
resourceDir=some/build/path/resourceDir
然后把robolectric.properties文件放到src/test/resources目录下,运行的时候,会自动加载里面的配置
系统属性配置
robolectric.offline:
true代表关闭运行时获取jar包
robolectric.dependency.dir:
当处于offline模式的时候,指定运行时的依赖目录
robolectric.dependency.repo.id:
设置运行时获取依赖的Maven仓库ID,默认是sonatype
robolectric.dependency.repo.url:
设置运行时依赖的Maven仓库地址,默认是https:
//oss.sonatype.org/content/groups/public/
robolectric.logging.enabled:
设置是否打开调试开关
以上设置可以通过Gradle进行配置,如:
android{
testOptions{
unitTests.all{
systemProperty'robolectric.dependency.repo.url','https:
//local-mirror/repo'
systemProperty'robolectric.dependency.repo.id','local'
}
}
}
驱动Activity生命周期
利用ActivityController我们可以让Activity执行相应的生命周期方法,如:
@Test
publicvoidtestLifecycle()throwsException{
//创建Activity控制器
ActivityController
MainActivityactivity=controller.get();
assertNull(activity.getLifecycleState());
//调用Activity的performCreate方法
controller.create();
assertEquals("onCreate",activity.getLifecycleState());
//调用Activity的performStart方法
controller.start();
assertEquals("onStart",activity.getLifecycleState());
//调用Activity的performResume方法
controller.resume();
assertEquals("onResume",activity.getLifecycleState());
//调用Activity的performPause方法
controller.pause();
assertEquals("onPause",activity.getLifecycleState());
//调用Activity的performStop方法
controller.stop();
assertEquals("onStop",activity.getLifecycleState());
//调用Activity的performRestart方法
controller.restart();
//注意此处应该是onStart,因为performRestart不仅会调用restart,还会调用onStart
assertEquals("onStart",activity.getLifecycleState());
//调用Activity的performDestroy方法
controller.destroy();
assertEquals("onDestroy",activity.getLifecycleState());
}
通过ActivityController,我们可以模拟各种生命周期的变化。
但是要注意,我们虽然可以随意调用Activity的生命周期,但是Activity生命周期切换有自己的检测机制,我们要遵循Activity的生命周期规律。
比如,如果当前Activity并非处于stop状态,测试代码去调用了controller.restart方法,此时Activity是不会回调onRestart和onStart的。
除了控制生命周期,还可以在启动Activity的时候传递Intent:
/**
*启动Activity的时候传递Intent
*
*@throwsException
*/
@Test
publicvoidtestStartActivityWithIntent()throwsException{
Intentintent=newIntent();
intent.putExtra("test","HelloWorld");
Aityactivity=Robolectric.buildActivity(MainActivity.class).withIntent(intent).create().get();
assertEquals("HelloWorld",activity.getIntent().getExtras().getString("test"));
}
onRestoreInstanceState回调中传递Bundle:
/**
*savedInstanceState会在onRestoreInstanceState回调中传递给Activity
*
*@throwsException
*/
@Test
publicvoidtestSavedInstanceState()throwsException{
BundlesavedInstanceState=newBundle();
Robolectric.buildActivity(MainActivity.class).create().restoreInstanceState(savedInstanceState).get();
//verifysomething
}
在真实环境下,视图是在onCreate之后的某一时刻在attach到Window上的,在此之前,View是处于不可操作状态的,你不能点击它。
在Activity的onPostResume方法调用之后,View才会attach到Window之中。
但是,在Robolectric之中,我们可以用控制器的visible方法使得View变为可见,变为可见之后,就可以模拟点击事件了
@Test
publicvoidtestVisible()throwsException{
ActivityController
MainActivityactivity=controller.get();
//调用Activity的performCreate并且设置视图visible
controller.create().visible();
//触发点击
activity.findViewById(R.id.activity_main_button1).performClick();
//验证
assertEquals(shadowOf(activity).getNextStartedActivity().getComponent().getClassName(),SecondActivity.class.getName());
}
追加模块
为了减少依赖包的大小,Robolectric的shadows类成了好几部分:
SDKPackageRobolectricAdd-OnPackage
com.android.support.support-v4org.robolectric:
shadows-support-v4
com.android.support.multidexorg.robolectric:
shadows-multidex
com.google.android.gms:
play-servicesorg.robolectric:
shadows-play-services
com.google.android.maps:
mapsorg.robolectric:
shadows-maps
org.apache.httpcomponents:
httpclientorg.robolectric:
shadows-httpclient
用户可以根据自身需求添加以下依赖包,如
dependencies{
......
testCompile'org.robolectric:
robolectric:
3.1.4'
testCompile'org.robolectric:
shadows-support-v4:
3.1.4'
testCompile'org.robolectric:
shadows-multidex:
3.1.4'
testCompile'org.robolectric:
shadows-play-services:
3.1.4'
testCompile'org.robolectric:
shadows-maps:
3.1.4'
testCompile'org.robolectric:
shadows-httpclient:
3.1.4'
}
自定义Shadow类
Shadow类需要一个public的无参构造方法以方便Robolectric框架可以实例化它,通过@Implements注解与原始类关联在一起
若原始类有有参构造方法,在Shadow类中定义publicvoid类型的名为__constructor__的方法,且方法参数与原始类的构造方法参数一直
定义与原始类方法签名一致的方法,在里面重写实现,Shadow方法需用@Implementation进行注解
下面我们来创建RobolectricBean的Shadow类
原始类:
publicclassRobolectricBean{
Stringname;
intcolor;
publicRobolectricBean(Stringname){
this.name=name;
}
publicStringgetName(){
returnname;
}
publicintgetColor(){
returncolor;
}
publicvoidsetColor(intcolor){
this.color=color;
}
}
Shadow类:
/**
*创建{@linkRobolectricBean}的影子类
*
*@authorHansChen
*/
@Implements(RobolectricBean.class)
publicclassShadowRobolectricBean{
/**
*通过@RealObject注解可以访问原始对象,但注意,通过@RealObject注解的变量调用方法,依然会调用Shadow类的方法,而不是原始类的方法
*只能用来访问原始类的field
*/
@RealObject
RobolectricBeanrealBean;
/**
*需要一个无参构造方法
*/
publicShadowRobolectricBean(){
}
/**
*对应原始类的构造方法
*
*@paramname对应原始类构造方法的传入参数
*/
publicvoid__constructor__(Stringname){
realBean.name=name;
}
/**
*原始对象的方法被调用的时候,Robolectric会根据方法签名查找对应的Shadow方法并调用
*/
@Implementation
publicStringgetName(){
return"Hello,ImashadowofRobolectricBean:
"+realBean.name;
}
@Implementation
publicintgetColor(){
returnrealBean.color;
}
@Implementation
publicvoidsetColor(intcolor){
realBean.color=color;
}
}
Shadow类中访问原始类的field
Shadow类中可以定义一个原始类的成员变量,并用@RealObject注解,这样,Shadow类就能访问原始类的field了,但是注意,通过@RealObject注解的变量调用方法,依然会调用Sh
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Robolectric 使用 教程
![提示](https://static.bdocx.com/images/bang_tan.gif)