robolectric完全解析

来源:互联网 发布:矩阵的奇异值分解方法 编辑:程序博客网 时间:2024/06/09 20:02

官网地址:http://robolectric.org

介绍:

在android设备上跑测试用例太慢了,编译、部署、启动app要花费很多时间。Robolectric是一个单元测试框架,测试是运行在jvm上的,只需要几秒钟。


使用:

在build.gradle中添加

testCompile "org.robolectric:robolectric:3.4"android {  testOptions {    unitTests {      includeAndroidResources = true    }  }}  

在测试用例中添加Robolectric test runner

@RunWith(RobolectricTestRunner.class)public class SandwichTest {}

对于Linux和mac使用的提示:

要配置一下moudle的工作区间:edit configurations -> defaults -> android junit -> working directory选择$MODULE_DIRS


demo:

点击登录按钮跳转到新的页面,下面是布局文件和actictiy

<?xml version="1.0" encoding="utf-8"?><LinearLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <Button        android:id="@+id/login"        android:text="Login"        android:layout_width="wrap_content"        android:layout_height="wrap_content"/></LinearLayout>
public class WelcomeActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.welcome_activity);        final View button = findViewById(R.id.login);        button.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                startActivity(new Intent(WelcomeActivity.this, LoginActivity.class));            }        });    }}

测试用例

@RunWith(RobolectricTestRunner.class)public class WelcomeActivityTest {    @Test    public void clickingLogin_shouldStartLoginActivity() {        // 获取activity        WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);        // 获取到登录按钮,模拟点击事件        activity.findViewById(R.id.login).performClick();        // 判断启动的activity是否是登录页面        Intent expectedIntent = new Intent(activity, LoginActivity.class);        assertThat(shadowOf(activity).getNextStartedActivity()).isEqualTo(expectedIntent);    }}

配置:

1、 @config注解,可以用在类或者方法上,方法的会覆盖掉类上的值,可以在一个base类上使用。

@Config(sdk=JELLYBEAN_MR1,      manifest="some/build/path/AndroidManifest.xml",      shadows={ShadowFoo.class, ShadowBar.class})  public class SandwichTest {}

2、robolectric.properties文件,可以在一个包或者一组包中配置,通常配置文件放在src/test/resources里,Robolectric会在层级目录中寻找各个配置文件。@config中配置会覆盖文件中的配置。

# src/test/resources/com/mycompany/app/robolectric.propertiessdk=18manifest=some/build/path/AndroidManifest.xmlshadows=my.package.ShadowFoo,my.package.ShadowBar

3、全局配置

如果想要改变所有测试用力配置,你可以自定义test runner继承RobolectricTestRunner,然后重写buildGlobalConfig()方法,@RunWith注解中填写自定义的Runner


具体的配置:

1、配置sdk版本,如果不配置默认使用targetSdkVersion的版本

@Config(sdk = { JELLY_BEAN, JELLY_BEAN_MR1 })public class SandwichTest {    public void getSandwich_shouldReturnHamSandwich() {      // will run on JELLY_BEAN and JELLY_BEAN_MR1    }    @Config(sdk = KITKAT)    public void onKitKat_getSandwich_shouldReturnChocolateWaferSandwich() {      // will run on KITKAT    }    @Config(minSdk=LOLLIPOP)    public void fromLollipopOn_getSandwich_shouldReturnTunaSandwich() {      // will run on LOLLIPOP, M, etc.    }}

2、配置Application实例

@Config(application = CustomApplication.class)public class SandwichTest {    @Config(application = CustomApplicationOverride.class)    public void getSandwich_shouldReturnHamSandwich() {    }}

3、配置资源文件和Asset路径,如果不配置则使用默认路径

@Config(resourceDir = "some/build/path/res")public class SandwichTest {    @Config(resourceDir = "other/build/path/ham-sandwich/res")    public void getSandwich_shouldReturnHamSandwich() {    }}

4、配置限定符

public class SandwichTest {    @Config(qualifiers = "fr-xlarge")    public void getSandwichName() {      assertThat(sandwich.getName()).isEqualTo("Grande Croque Monégasque");    }}

5、系统配置

robolectric.enabledSdks — Comma-separated list of SDK levels or names (e.g. 19, 21 or KITKAT, LOLLIPOP) which are enabled for this process. Only tests targetted at the listed SDKs will be run. By default, all SDKs are enabled.robolectric.offline — Set to true to disable runtime fetching of jars.robolectric.dependency.dir — When in offline mode, specifies a folder containing runtime dependencies.robolectric.dependency.repo.id — Set the ID of the Maven repository to use for the runtime dependencies (default sonatype).robolectric.dependency.repo.url — Set the URL of the Maven repository to use for the runtime dependencies (default https://oss.sonatype.org/content/groups/public/).robolectric.logging.enabled — Set to true to enable debug logging.

也可以在gradle中配置

android {  testOptions {    unitTests.all {      systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'      systemProperty 'robolectric.dependency.repo.id', 'local'    }  }}

使用限定的资源:

资源限定符可以使你改变你的资源加载,比如语言、屏幕大熊啊或者是黑天白天。而这些变化往往是乏味的严格测试(每个字符串都有支持的所有语言的翻译),你可能会发现自己希望在不同的资源限定的环境中运行测试。

指定资源限定非常简单,例如可以指定当前的string值为en-port中的值

// values/strings.xml<string name="overridden_twice">Unqualified value</string>// values-en/strings.xml<string name="overridden_twice">English qualified value</string>// values-en-port/strings.xml<string name="overridden_twice">English portrait qualified value</string>@Test@Config(qualifiers="en-port")public void shouldUseEnglishAndPortraitResources() {  final Context context = RuntimeEnvironment.application;  assertThat(context.getString(R.id.not_overridden)).isEqualTo("Not Overridden");  assertThat(context.getString(R.id.overridden)).isEqualTo("English qualified value");  assertThat(context.getString(R.id.overridden_twice)).isEqualTo("English portrait qualified value");}

操纵activity的生命周期

新建一个MyAwesomeActivity实例,并且调用onCreate()方法

Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().get();

如果想在onResume()方法中执行一些测试

ActivityController controller = Robolectric.buildActivity(MyAwesomeActivity.class).create().start();Activity activity = controller.get();// assert that something hasn't happenedactivityController.resume();// assert it happened!

相似的方法还有start(), pause(), stop(), destroy(),如果你想测试创建的生命周期,可以这么写

Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().start().resume().visible().get();

你可以模仿通过intent启动activity

Intent intent = new Intent(Intent.ACTION_VIEW);Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).withIntent(intent).create().get();

或者恢复instance中的状态:

Bundle savedInstanceState = new Bundle();Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class)    .create()    .restoreInstanceState(savedInstanceState)    .get();

你应该在create()方法之后调用visible(),然后再调用Robolectric.clickOn()等方法。


使用附加组件的Modules

为了减少测试依赖的数量,Robolectric’s shadows被分成了几个包。Robolectric模块只提供有基础的SDK中的shadows,appcompat或者support库中要添加额外的shadows组件,下面是对应的列表

SDK Package Robolectric Add-On Package com.android.support.support-v4 org.robolectric:shadows-support-v4 com.android.support.multidex org.robolectric:shadows-multidex com.google.android.gms:play-services org.robolectric:shadows-play-services com.google.android.maps:maps org.robolectric:shadows-maps org.apache.httpcomponents:httpclient org.robolectric:shadows-httpclient

上面这些附加包需要加build.gradle声明


使用Library Resources

当Roboletric跑测试用例的时候,他会试图加载索引应用中的所有资源,当你调用AssetManager时会返回这些资源。对于第三方资源库,需要添加额外的配置。但是如果使用Gradle编译,使用RobolectricTestRunner运行测试用例的时候,就不用添加了,因为gralde在编译时会将第三方库中的资源合并到你的应用中。


Robolectric 扩展

1、shadow类

Robolectric定义了很多的shadow类,修改、扩展了android系统中类的行为。当一个类实例化的时,Robolectric会找相关的shadow类,如果找到了,会创建一个shadow对象和他相关联。当方法被调用时,Robolectric会保证shadow类关联的方法先执行。这个适用于所有的方法,甚至包括static、final的方法。

2、为什么叫这个名字?

为什么叫 “Shadow?” shadow和Proxies不太一样,和Fakes不太一样,和Mocks、Stubs不太一样。Shadows有时隐藏,有时可见,可以使你看到真正的对象。

3、添加方法

如果Robolectric提供的shadow类没有做你想做的事情,有可能是在一个测试、一组测试、或者全部测试中改变了以他们的行为。简单的声明一个类(ShadowFoo)并且添加注释(@Implements(Foo.class))。你的shadow类可以继承Robolectric中存储的任何一个shadow类。为了让Roboletric知道你的shadow类,通过@Config(shadows=ShadowFoo.class)来注解你的类、方法,或者创建一个名为org.robolectric.Config.properties的文件,包含shadows=my.package.ShadowFoo。

4、shadow classes

Shadow类需要一个public的无参构造函数,Robolectric可以实例化他们。通过@Implements注解关联class。通常,他们的实现如果是从零开始的,他们shadow的设备classes通常被移除了,并且成员数据很难被访问到。shadow类中的方法通常不是shadow原生的方法,就是通过设置返回值或者提供内部状态或记录方法调用来完成这个测试。

shadow类会模仿类的继承结构,例如

@Implements(ViewGroup.class)public class ShadowViewGroup extends ShadowView {    // 需要继承ViewGroup的shadow父类ShadowView.}

5、shadow method

应用中的代码:

 this.imageView.setImageResource(R.drawable.pivotallabs_logo);

测试代码,shadow实例中这个ShadowImageView#setImageResource(int resId)方法将会被调用,记得一定要注释@Implementation,Robolectric中会包含lint测试来确保正确执行

@Implements(ImageView.class)public class ShadowImageView extends ShadowView {  ...     @Implementation  public void setImageResource(int resId) {    // implementation here.  }}

6、shadowing constructors

一旦Shadow实例实例化后,Robolectric会找constructor方法,它是在真正程序中调用和构造函数有这个一样的参数的方法。

代码中,实例化TextView时,参数为context

new TextView(context);

Robolectric会执行参数为context的constructor方法

@Implements(TextView.class)public class ShadowTextView {  public void __constructor__(Context context) {    this.context = context;  }

7、获取正在的实例

有时候Shadow想要参考shadowing的对象,可以通过@RealObject注解获取,然后进行操作

@Implements(Point.class)public class ShadowPoint {  @RealObject private Point realPoint;  public void __constructor__(int x, int y) {    realPoint.x = x;    realPoint.y = y;  }}

Robolectric在调用其他方式时,会设置realPoint来替代Point实例

需要注意的是,调用真正方法的时候会被Robolectric拦截和重定向,这对于测试代码没有问题,但是对于shadow的实现者有提示作用,由于shadow类的继承结构并不总是反映他们的Android相关类。有时候需要通过他们真正的对象来调用,以便Robolectric运行时将有机会基于实际的类的对象来路由到正确的shadow类,否则base shadow类将无法访问到他们的子类。

原创粉丝点击