Robolectric 探索之路


layout: post
title: Roboletric探索之路,从抗拒到依赖
description: Roboletric Android Unit Testing
category: blog
---html

我为何之前抗拒Android Unit Testing

  • 一、懒,人类最大的天敌;java

  • 二、不是不知道什么是单元测试,只是需求太多了,哪有时间~;android

  • 三、须要学习单元测试的语言或者框架,不熟悉,因此从没尝试过;git

  • 四、没见到单元测试的好处,一想到要花时间就望而却步;github

  • 五、至少只是我我的以前的感觉,我相信有不少的程序猿同胞们都跟我有相似的感觉;web

既然抗拒,为何如今要尝试Android Unit Testing呢

大势所趋,bug量的增多不得不让咱们提升代码的质量,不是咱们完不成功能,只是咱们验证功能的成本实在过高,随着工程的复杂度的增长,run一次模拟器或者真机,在window上的花费至少是一分钟以上,甚至三四分钟,因此有些人偷懒,包括我,有时候把那些看上去“没有问题的代码”提交到了主干上,随之产生了bug,而后进入修复bug-》run-》修复bug->run;花费了更多的时间和资源;api

咱们的燃眉之急是要尽快改善这个问题,从根源着手,就是【加强自测】缓存

测试手段

如今是个讲究效率的时代,咱们但愿可以快速高效的验证咱们的代码逻辑是否有问题,咱们不但愿验证一个简单的逻辑或者一个方法是否有效,是经过run一次模拟器或者整个工程实现的,这样花费的时间太长了,下降了开发效率;app

  • 一、因此咱们要解决的第一个痛点是,如何快速验证;框架

咱们选择了Robolectric单元测试框架,缘由有好几个,最大的缘由是:

Robolectric
Test-Drive Your Android Code
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!?

它不须要Run你的模拟器,直接在jvm上运行你的测试代码,能在几秒钟以内快速验证,经过体验以后,它确实很是高效,编写测试代码反而加速了开发效率。
具体的原理描述可参见:

Robolectic官网

Robolectic介绍

Talk is cheap ,show me the code

环境配置

Android Stuido 1.5.1

junit:junit:4.12

Robolectric 3.0(不要用3.0-r3,有不少bug,踩了不少坑)

具体配置:

一、在app的build.gradle依赖添加以下:

testCompile 'junit:junit:4.12'
testCompile ’org.robolectric:robolectric:3.0’

二、在android studio左下角的Build Variants->TestArtifact,选择为Unit Tests;

如图

三、编译;

编译经过以后就已经集成了Robolectic单元测试框架了。

个人第一个单元测试

先描述如下踩过的坑:

1.使用3.0-r3版本,缘由是google搜到的例子是3.0-r3,傻傻的掉进了坑里;
找不到合并后的mainifest;ContextWraper为空,webview初始化异常,没法加载so库

2.使用了3.0版本后,依然没法加载so库,现象是启动application的时候若是调用so库会出现闪退,目前robolectic还不支持这个东西,做者在github的issue已经说明;

3.这一点致使没法robolectic没法直接集成到咱们的主工程;为了避免影响正常项目开发,咱们建了一个AppTest的空项目,集成了Robolectic框架;将全部用例分类写在里边,各个模块要测试的时候,把依赖写入build.gradle便可;

第一单元测试

建立测试类
日历分析页面测试demo
参数解释:

@RunWith(RobolectricGradleTestRunner.class);声明使用哪一个Runner,使用GradleTestRunner会自动帮咱们加载所须要的插件,通常咱们配这个就能够;

@Config(constants = BuildConfig.class,sdk=18);配置测试项目的BuildConfig和sdk版本,BuildConfig是编译器自动生成;@Config还能够配置不少其余的熟悉,好比Mainfest,Applciation,assert资源等等,具体了解可参见
http://robolectric.org/configuring/

extend TestCase:这个必须继承的类;

@Before是前置条件,也就是在执行@Test以前会执行的方法,这个很好理解,@After同理;

@Test具体的单元测试方法;

例子解释:

@Before

@Before
public void setUp() {
     //获取当前运行环境的Context;
    Context context =  RuntimeEnvironment.application.getApplicationContext();
    //初始化BeanManager
    BeanManager.getUtilSaver().setContext(context);
    //初始化日历模块
    CalendarController.getInstance().init(context, new OnCalendarListener(){});
}

每次执行test的时候,Robolectic执行顺序是:
模拟启动执行你的application,
执行@Before
执行@Test,
执行@After
因此若是没有没有执行初始化逻辑,@test颇有可能会失败;
或者appliation里调用了so或者初始化了webview,也会失败;
通常在@Before咱们作的是初始化的工做和构造一些模拟数据的操做;

@Test

@Test
public void doTestMainUI(){
     //启动AnalysisMainActivity,并获取activity对象
    Activity activity = Robolectric.setupActivity(AnalysisMainActivity.class);
    //获取里边的控件
    RelativeLayout linearLayout = (RelativeLayout)activity.findViewById(R.id.calendarNodataLayout);
    int visible = linearLayout.getVisibility();
    //判断是否可见
    assertEquals(visible, View.VISIBLE);
}

@Test
public void doTestCurrentIdentify(){
      //获取当前身份,能够在setup设置身份
    int value = CalendarController.getInstance().getIdentifyManager().getIdentifyModelValue();
    //验证身份
    assertEquals(value, IdentifyModel. NORMAL);
}

@Test
public void testDomain(){
            List<HttpDnsModel> list = mHttpDnsCacheManager.getHttpDnsModels();
            assertEquals(list.size(), 2);
            //验证解析格式
            String domain = mHttpDnsCacheManager.getDomian("http://api.myms.meiyou.com/configs");
            assertEquals(domain,"api.myms.meiyou.com");
            //验证解析格式
            domain = mHttpDnsCacheManager.getDomian("https://api.myms.meiyou.com/configs");
            assertEquals(domain, "api.myms.meiyou.com");
            //验证命中缓存
            HttpDnsModel model =  mHttpDnsCacheManager.getFromCache("api.myms.meiyou.com/configs");
            assertEquals(model!=null,true);
            assertEquals(model.getIp(), "211.151.209.71");
            //验证替换
            String result = mHttpDnsCacheManager.replaceDomianToIp("http://api.myms.meiyou.com/configs", domain, model.getIp());
            assertEquals("http://211.151.209.71/configs",result);

    }
          
@Test
public void testGetPhoto(){
                //记录器
            final Transcript transcript = new Transcript();
            //建立一个activity
            Activity activity = new Activity() {
                    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                            //记录收到的结果
                            transcript.add("onActivityResult called with requestCode " + requestCode + ", resultCode " + resultCode + ", intent data " + data.getData());
                    }
            };
            //启动去相册选择相册
            activity.startActivity(new Intent().setType("image/*"));
               //获取影子类,模拟设置ActivityResult结果
            Shadows.shadowOf(activity).receiveResult(new Intent().setType("image/*"), Activity.RESULT_OK,
                    new Intent().setData(Uri.parse("content:foo")));
            //验证收到的结果       
            transcript.assertEventsSoFar("onActivityResult called with requestCode -1, resultCode -1, intent data content:foo");
    }

强大的影子类:

影子类的详细了解
影子类是对安卓原生api类的一种拓展,通俗的解释是增长了一些用于测试的很方便的方法;3.0有不少影子类,获取方法是:Shadows.shadowOf();
上边的 Shadows.shadowOf(activity).receiveResult的方法;
影子类还能够自定义,遇到比较复杂的功能或者须要的功能可能会颇有用;

单元测试的例子

大量的测试例子都在github的源码里边,能够详细参照;
Robolectric Github 源码地址


Done

----------

QQ:452825089


mail:452825089@qq.com


wechat:ice3897315


blog:http://iceAnson.github.io

相关文章
相关标签/搜索