使用Robolectric对Android应用进行单元测试

       对于单元测试不清楚的地方可以参考我的第一篇文章  http://blog.csdn.net/u013772458/article/details/51958015

介绍Robolectric主要从框架简介、框架优点、框架可行性分析,框架环境配置、框架演示、框架参考资料方面进行介绍:

一、框架简介

官网的介绍 
        Running tests on an Android emulator or device is slow! Building, deploying, and launching the app often takes a minute or more. That’s no way to do TDD. There must be a better way.

          Wouldn’t it be nice to run your Android tests directly from inside your IDE? Perhaps you’ve tried, and been thwarted by the dreaded java.lang.RuntimeException: Stub!?

           Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds. With Robolectric you can write tests like this:

我的理解是

        对于Android app来说,写起单元测试来瞻前顾后,一方面单元测试需要运行在模拟器上或者真机上,麻烦而且缓慢,另一方面,一些依赖Android SDK的对象(如Activity,TextView等)的测试非常头疼,Robolectric可以解决此类问题,它的设计思路便是通过实现一套JVM能运行的Android代码,从而做到脱离Android环境进行测试。

     1、可以不运行于设备之上,可以想一想,一个项目小点还好,大一点的项目跑起来平时花费我们两三分钟,所以不错哦。
2、使用Android的例子测试大多抛出java.lang.RuntimeException: Stub!?这个出错信息,白了还是个运行的过程,所以如果你的unit test代码里面有android。

二、框架优点

测试驱动android开发

在安卓模拟器或者真机上跑测试用例速度很慢。构建、部署、启动app,通常需要花费一分钟或者更久。这不是TDD(测试驱动开发)模式.Robolectric提供一种更好的方式。

可能你一直尝试在java IDE中使用junit或者testng直接跑测试用例,但是一直报java.lang.RuntimeException: Stub!异常。

这个异常是因为在jdk中没有android运行环境。而现在Robolectric这款android单元测试工具,模拟了android sdk中的jar包,可以直接在jvm中运行测试用例,这样就大大节省了时间。

SDK,Resource和Native Method模拟

Robolectric可以处理控件展示、资源加载和很多使用native C实现的真机上的其他功能。所以我们可以使用Robolectric来模拟真机上的大部分操作。我们可以很方便地获取Robolectric的源码,直接查看它的模拟机制,所以使用Robolectric,我们也可以模拟错误条件和一些真实的传感器信号。

脱离模拟器执行测试用例

Robolectric允许我们在项目工程中或者持续集成(CI如hudson、jenkins)中使用jvm来执行测试用例,所以就省掉了打包、安装的过程,将测试用例的执行时间由分钟级降到秒级。

对比:


三、可行性分析

(1)环境配置相对简单(根据后面介绍配置即可)
(2)可以直接在jvm中运行测试用例(包含Android相关类,打印日志等),这样就大大节省了时间。
(3)Robolectric可以在持续集成(jenkins)中使用jvm来执行测试用例。
(4)Robolectric可以对(OrmLite,retrofit)网络请求,数据库进行测试。
(5)Robolectric相对比较成熟,使用起来简单,官网有详细的API文档,一些难点与资料比较容易查找。
但是存在一些问题,

(1)只支持22版本以及以下,我测试了23,22,21,20,19。其中20,23不支持。(第一次运行测试自动下载一些Jar包)


(2)异步测试出现一些问题(比如改变一些编码习惯,比如回调函数不能写成匿名内部类对象,需要定义一个全局变量,并破坏其封装性,即提供一个get方法,供UT调用),解决方案使用Mockito来结合进行测试,将异步转为同步,后面参考资料有。
(3)遇到问题,作者很积极回答,详情https://github.com/robolectric/robolectric/issues

四、框架环境配置

一、Gradle配置
在build.gradle中配置如下依赖关系:


二、在test目录(如木有自己创建)
建立一个JustTest测试类

三、通过注解配置JustTest(测试类)

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class,sdk = 19)
public class JustTest {
请注意:sdk目前只适配(API21以及以下,可选择21,19,16)
四、Android Studio的配置(2.0版本以上跳过这个步骤)
1、在Build Variants面板中,将Test Artifact切换成Unit Tests模式(目录绿色表示成功),如下图:



2、working directory 设置为$MODULE_DIR$
如果在测试过程遇见如下问题,解决的方式就是设置working directory的值:
java.io.FileNotFoundException: build\intermediates\bundles\debug\AndroidManifest.xml (系统找不到指定的路径。)设置方法如下图所示:

configurations


Working directory的配置

五、框架演示

Android 打印的日志轻松可以在控制台查看

@Before
public void setUp() throws URISyntaxException {
    //输出日志
    ShadowLog.stream = System.out;
}


写了一个创建Activity实例、Activity的跳转,生命周期的测试,UI组件状态,Toast信息验证的测试用例

 //创建Activity实例
 @Test
 public void testActivity() {
     MainActivity sampleActivity = Robolectric.setupActivity(MainActivity.class);//创建Activity
     Assert.assertNotNull(sampleActivity);//判断是否为空
     Assert.assertEquals(sampleActivity.getTitle(), "MainActivity");//获取Activity的标题
 }

/* @Test
 public void testMainActivity() {
     MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);//创建Activity
     mainActivity.findViewById(R.id.tv_test).performClick();//自动点击按钮

     ShadowActivity shadowActivity = Shadows.shadowOf(mainActivity);//ShodawRobolectric的本质是在Java运行环境下,
     // 采用Shadow的方式对Android中的组件进行模拟测试,从而实现Android单元测试。对于一些Robolectirc暂不支持的组件,可以采用自定义Shadow的方式扩展Robolectric的功能。

     Intent expectedIntent = new Intent(mainActivity, TestActivity.class);
     Intent actualIntent = shadowActivity.getNextStartedActivity();
     Assert.assertEquals(expectedIntent, actualIntent);

     String hello = mainActivity.getResources().getString(R.string.app_name);
     Assert.assertEquals("My Application",hello);
 }*///这个网上很多人写这个,我验证是错误的。

 /**
  * 测试下一个Activity
  */
 @Test
 public void testNextActivity(){
     MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);
     mainActivity.findViewById(R.id.tv_test).performClick();
     ShadowActivity shadowActivity = Shadows.shadowOf(mainActivity);
     Intent nextStartedActivity = shadowActivity.getNextStartedActivity();
     ShadowIntent shadowIntent = Shadows.shadowOf(nextStartedActivity);
     Assert.assertEquals( shadowIntent.getIntentClass(), (TestActivity.class));
 }


 //测试生命周期
 @Test
 public void testLifecycle() {
     ActivityController<MainActivity> activityController = Robolectric.buildActivity(MainActivity.class).create().start();
     Activity activity = activityController.get();
     TextView textview = (TextView) activity.findViewById(R.id.tv_test);
     Assert.assertEquals("onCreate",textview.getText().toString());
     activityController.resume();
     Assert.assertEquals("onResume", textview.getText().toString());
     activityController.destroy();
     Assert.assertEquals("onDestroy", textview.getText().toString());
 }
 //UI组件状态
 @Test
 public void testViewState(){
     MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);
     CheckBox checkBox = (CheckBox) mainActivity.findViewById(R.id.box_test);
     Button testBt = (Button) mainActivity.findViewById(R.id.bt_test);
     Assert.assertTrue(testBt.isEnabled());

     checkBox.setChecked(true);
     //点击按钮,CheckBox反选
     testBt.performClick();
     Assert.assertTrue(!checkBox.isChecked());
     testBt.performClick();
     Assert.assertTrue(checkBox.isChecked());
 }

 //Toast
 @Test
 public void testToast(){
     //点击按钮,出现吐司
     MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);
     Button toastBt = (Button) mainActivity.findViewById(R.id.bt_toast);
     toastBt.performClick();
     Assert.assertEquals(ShadowToast.getTextOfLatestToast(),"we love UT");
 }
 点击右键,运行,注意第一次运行需要下载一些东西,所以很慢,最好翻墙或者设置代理


运行结果(成功)如下:


弄个失败的例子,改了下一个Activity的跳转,将TestActivity改成SecondActivity.代码与运行结果如下:

运行结果显示有错误,具体到行数与差异之处。


代码地址:http://pan.baidu.com/s/1c2E4BUo


六、参考资料

 关于代码的演示只是肤浅的一部分,详细可以参考如下文章
1、(包括四大组件的测试以及Shadow的使用)
Shadow是Robolectric的立足之本,提供了很多影子对象(如Activity和ShadowActivity、TextView和ShadowTextView等),这些影子对象,丰富了本尊的行为,能更方便的对Android相关的对象进行测试。
http://www.jianshu.com/p/9d988a2f8ff7

2、(网络请求与数据库测试用例的测试,包括对OrmLite,retrofit)
http://www.jianshu.com/p/3aa0e4efcfd3

3、(解决框架异步测试问题结合Mockito解决)
http://www.jianshu.com/p/b00586534fc1
4、官网资料
http://robolectric.org/getting-started/
5、Robolectric与Mockito结合使用
http://blog.csdn.net/ruyi366/article/details/51693690

    Mock的概念,其实很简单,我们前面也介绍过:所谓的mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,以达到两大目的:
    验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
   要使用Mock,一般需要用到mock框架,这篇文章我们使用Mockito这个框架,这个是Java界使用最广泛的一个mock框架。
    Mockito 是一个基于MIT协议的开源java测试框架。 
Mockito区别于其他模拟框架的地方主要是允许开发者在没有建立“预期”时验证被测系统的行为。对于mock对象的一个评价是测试系统的测试代码是一个高耦合的,由于 Mockito试图通过移除“期望规范”去除expect-run-verify(期望验证模式)的模式,因此在耦合度上有所降低。这样的结果是简化了测试代码,使他更易读和修改了。
资料:
http://www.oschina.net/translate/mockito-a-great-mock-framework-for-java-development
http://android.jobbole.com/83051/

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值