layout: post

title: Roboletric探索之路,从抗拒到依赖

description: Roboletric Android Unit Testing

category: blog

我为什么以前抗拒Android Unit Testing

  • 1、懒,人类最大的天敌;
  • 2、不是不知道什么是单元测试,只是需求太多了,哪有时间~;
  • 3、需要学习单元测试的语言或者框架,不熟悉,所以从没尝试过;
  • 4、没见到单元测试的好处,一想到要花时间就望而却步;
  • 5、至少只是我个人之前的感受,我相信有很多的程序猿同胞们都跟我有类似的感受;

既然抗拒,为什么现在要尝试Android Unit Testing呢

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

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

测试手段

现在是个讲究效率的时代,我们希望能够快速高效的验证我们的代码逻辑是否有问题,我们不希望验证一个简单的逻辑或者一个方法是否有效,是通过run一次模拟器或者整个工程实现的,这样花费的时间太长了,降低了开发效率;

  • 1、所以我们要解决的第一个痛点是,如何快速验证;

我们选择了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,踩了很多坑)

具体配置:

1、在app的build.gradle依赖添加如下:

testCompile 'junit:junit:4.12'

testCompile ’org.robolectric:robolectric:3.0’

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

3、编译;

编译通过之后就已经集成了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即可;

第一单元测试

创建测试类



参数解释:

@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

Robolectric 探索之路的更多相关文章

  1. Android 单元测试(junit、mockito、robolectric)

    1.运用JUnit4 进行单元测试 首先在工程的 src 文件夹内创建 test 和 test/java 文件夹. 打开工程的 build.gradle(Module:app)文件,添加JUnit4依 ...

  2. Android studio下gradle Robolectric单元测试配置

    android studio下gradle Robolectric单元测试配置 1.Robolectric Robolectric是一个基于junit之上的单元测试框架.它并不依赖于Android提供 ...

  3. Robolectric 配置

    费了些工夫,已配好,按记录留记录 按官网操作http://robolectric.org/getting-started/ 1引包 testCompile "org.robolectric: ...

  4. 在Android Studio中用Gradle添加Robolectric

    我们用Robolectric测试的话需要在gradle中进行配置,国内的详细教程太过简易,而且很多是低版本下的配置方案.所以经过本人的仔细摸索,找到了现在高版本中的配置方案,主要还是参考了官网的配置教 ...

  5. robolectric环境的搭建

    最近在学习测试驱动开发(Test-Driven Development),测试驱动开始是极限编程的一种方式,提倡在真正编写代码之前先根据需求编写测试代码(当然这个测试代码是不可能通过的),然后根据测试 ...

  6. Extending Robolectric

    Robolectric is a work in progress, and we welcome contributions from the community. We encourage dev ...

  7. Configuring Robolectric

    There are numerous ways to customize how Robolectric behaves at runtime. Config Annotation The prima ...

  8. Robolectric Test-Drive Your Android Code

    Running tests on an Android emulator or device is slow! Building, deploying, and launching the app o ...

  9. OpenCV探索之路(二十四)图像拼接和图像融合技术

    图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要. 再举一个身边的例子吧,你用你的手 ...

随机推荐

  1. Oracle EBS-SQL (SYS-13):查询DBA在系统中的打Patch的信息.SQL

    查询DBA在系统中的打补丁信息 1. select * from ad_patch_drivers          /*查看已经打了哪些Patch*/ 2. select * from ad_pat ...

  2. 官方原版Adobe Acrobat XI Pro v11.0.0 序列号激活 可升级更新

    使用本人制作的正版激活向导,可以直接使用 注册机算号激活Adobe Acrobat XI Pro11 激活后可以直接官网升级更新软件,永久使用.下面是激活程序界面.简单易懂,傻瓜式操作.带有详细的安装 ...

  3. kafka学习(一)-背景及架构设计

    概念和术语 消息,全称为Message,是指在生产者.服务端和消费者之间传输数据. 消息代理:全称为Message Broker,通俗来讲就是指该MQ的服务端或者说服务器. 消息生产者:全称为Mess ...

  4. 链表-Reverse Linked List II

    [题目要求直接翻转链表,而非申请新的空间] 这道题的一个关键在于,当m=1时,需要翻转的链表段前没有其他的结点(leetcode的测试用例不含头结点),这个特例给解题带来了一点小小的困难.一个比较直观 ...

  5. C++一维数组和指针的关系总结

    对于数组int a[10]; a表示数组的第一个元素的地址,即&a[0]; 如果使指针p,指向数组的首元素,可以进行操作: int * p=a; 或者 int *p=&a[0]; 那么 ...

  6. 应用java多线程实现server端与多client之间的通信

    应用多线程来实现server与多线程之间的通信的基本步骤 1.server端创建ServerSocket,循环调用accept()等待client链接 2.client创建一个Socket并请求和se ...

  7. JQuery打造下拉框联动效果

    做联动效果,若是用纯JavaScript来做,往往须要辅助页面保存须要刷新的结果集,然后渲染到原页面.考虑将须要动态刷新的内容自己主动拼接到前一个下拉框之后,当前一个下拉框onchange后,同级的后 ...

  8. 【我所認知的BIOS】—&gt;ADU.exe

    [我所認知的BIOS]—>ADU.exe By LightSeed 2009-5-12 1.概要 在學習的過程中,肯定會要用不少的工具,作為底層的engineer那麼用的工具大多是DOS下.在D ...

  9. oracle超过最大游标数异常分析(转贴)

    问题描述 Oracle 使用 OPEN_CURSORS 参数指定一个会话一次最多可以打开的游标的数量.超过此数量时,Oracle 将报告 ORA-01000 错误.当此错误传播到 WebLogic S ...

  10. winfrom运用webservice上传文件到服务器

    winfrom做文件上传的功能显然没有BS的简单,本实例是运用了webservice获取二进制流转换的字符串.然后,解析字符串,把流文件再转成pdf. webservice 里面的代码为下: [Web ...