移动端仍可深度探索的领域有哪些?点击看业内代表性技术方案及案例>> 了解详情
写点什么

蘑菇街支付金融 Android 单元测试实践

  • 2016-05-19
  • 本文字数:10158 字

    阅读完需:约 1 分钟

本文为『移动前线』群在 4 月 23 日的分享总结整理而成,转载请注明来自『移动开发前线』公众号。

嘉宾介绍

邹勇(网名小创)蘑菇街支付金融资深安卓开发工程师。自毕业以来一直从事 Android 开发工作,先后工作于创新工厂、微策略以及蘑菇街。对单元测试和 TDD 情有独钟。

大家好,我是蘑菇街支付金融部门的邹勇,花名叫小创。今天很高兴跟大家分享一下安卓的单元测试在蘑菇街支付金融的实践。下面,我们从为什么开始。

为什么要写单元测试

首先要介绍为什么蘑菇街支付金融这边会采用单元测试的实践。说起来比较巧,刚开始的时候,只是我一个人会写单元测试。后来老板们知道了,觉得这是件很有价值的事情,于是就叫我负责我们组的单元测试这件事情。就这样慢慢的,单元测试这件事情就成了我们这边的正常实践了。再后来,在公司层面也开始有一定的推广。

要说为什么要写单元测试的话,我相信大部分人都能承认、也能理解单元测试在保证代码质量,防止 bug 或尽早发现 bug 这方面的作用,这可能是大家觉得单元测试最大的作用。然而我觉得,除了这方面的作用,单元测试还能在很大程度上改善代码的设计,同时还能节约时间,让人工作起来更自信、更开心,以及其他的一些好处。这些都是我的切身感受,我相信也是多数真正实践过单元测试的人的切身感受,而不是为了宣传这个东西而说的好听的大话。

说到节约时间,大家可能就会好奇了,写单元测试需要时间,维护单元测试代码也需要时间,应该更费时间才对啊?

这就是在开始分享之前,我想重点澄清的一点,那就是,单元测试本身其实不会占用多少时间,相反,还会节约时间。只是:

  1. 学习如何做单元测试需要时间;
  2. 在一个没有单元测试的项目中加入单元测试,需要一定的结构调整的时间,因为一个有单元测试跟没有单元测试的项目,结构上还是有较大不同的。

打个比方,开车这件事情,需要很多时间吗?我相信很少人会说开车这件事情需要很多时间,而是:

  1. 学习开车,需要一定的时间;
  2. 如果路面不平的话,那么修路需要一定的时间。单元测试也是类似的情况。

那为什么说单元测试可以节约时间呢?简单说几点:

  1. 如果没有单元测试的话,就只能把 app 运行起来测试,这比运行一次单元测试要慢多了。
  2. 尽早发现 bug,减少了 debug 和 fixbug 的时间。
  3. 重构的时候,大大减少手动验证重构正确性的时间。

所以,我希望大家能去掉"没时间写单元测试"这个印象,如果工作上安排太紧,没有时间学习如何做单元测试的话,可以自己私底下学,然后在慢慢应用到项目中。

单元测试简单介绍

接下来介绍我们这边是怎么做安卓单元测试的。首先澄清一下概念,在安卓上面写测试,有很多技术方案。有 JUnit、Instrumentation test、Espresso、UiAutomator 等等,还有第三方的 Appium、Robotium、Calabash 等等。我们现在讲的是使用 JUnit 和其他的一些框架,写可以在我们开发环境的 JVM 上面直接运行的单元测试,其他的几种其实都不属于单元测试,而是集成测试或者叫 Functional test 等等。这两者明显的不同是,前者可以直接在开发用的电脑,或者是 CI 上面的 JVM 上运行,而且可以只运行那么一小部分代码,速度非常快。而后者必须要有模拟器或真机,把整个 project 打包成一个 app,然后上传到模拟器或真机上,再运行相关的代码,速度相对来说慢很多。

单元测试的定义相信大家都知道,就是为我们写的某一个代码单元(比如一个方法)写的测试代码。一个单元测试大概可以分为三个部分:

  1. setup:即 new 出待测试的类,设置一些前提条件
  2. 执行动作:即调用被测类的被测方法,并获取返回结果
  3. 验证结果:验证获取的结果跟预期的结果是一样的

然而一个类的方法分两种,一种是有返回值的方法。一种是没有返回值的方法,即 void 方法。对于有返回值的方法,固然测试起来是很容易的,但是对于没有返回值的方法,该怎么测试呢?这里的关键是,怎么样获取这个方法的“返回结果”?

这里举一个例子来说明一下,顺便澄清一个十分常见的误解。比如说有一个 Activity,管他叫 DataActivity,它有一个 public void loadData() 方法, 会去调用底层的 DataModel 类,异步的执行一些网络请求。当网络请求返回以后,更新用户界面。

这里的 loadData() 方法是 void 的,它该怎么测试呢?一个最直接的反应可能是,调用 loadData() 方法 (当然,实际可能是通过其他事件触发),然后一段时间后,验证界面得到了更新。然而这种方法是错的,这种测试叫集成测试,而不是单元测试。因为它涉及到很多个方面,它涉及到 DataModel、网络服务器,以及网络返回正确时,DataActivity 内部的处理,等等。集成测试固然有它的必要性,但不是我们应该最关注的地方,也不是最有价值的地方。我们应该最关注的是单元测试。关于这一点,有一个 Test Pyramid 的理论:

Test Pyramid 理论基本大意是,单元测试是基础,是我们应该花绝大多数时间去写的部分,而集成测试等应该是冰山上面能看见的那一小部分。

那么对于这个 case,正确的单元测试方法,应该是去验证 loadData() 方法调用了 DataModel 的某个请求数据的方法,同时传递的参数是正确的。“调用了 DataModel 的方法,同时参数是。。。” 这个才是 loadData() 这个方法的“返回结果”。

Mock 的概念以及 Mockito 框架

要验证某个对象的某个方法得到调用了,就涉及到 mock 的使用。这里对 mock 的概念做个简单介绍,以免很多同学不熟悉,mock 就是创建一个虚假的、模拟的对象。在测试环境下,用来替换掉真实的对象。这样就能达到两个目的:

  1. 可以随时指定 mock 对象的某个方法返回什么样的值,或执行什么样的动作。
  2. 可以验证 mock 对象的某个方法有没有得到调用,或者是调用了多少次,参数是什么等等。

要使用 mock,一般需要使用 mock 框架,目前安卓最常用的有两个, Mockito JMockit 。两者的区别是,前者不能 mock static method 和 final class、final method,后者可以。我们依然采用的是 Mockito,原因说起来惭愧,是因为刚开始并不知道 JMockit 这个东西,后来查了一些资料,看过很多对比Mockito 和JMockit 的文章,貌似大部分还是很看好JMockit 的,只是有一个问题,那就是跟robolectric 的结合也有一些bug,同时使用姿势跟Mockito 有较大的不同,因此一直没有抽时间去实践过。这个希望以后能够做进一步的调查,到时候在给大家分享一下使用感受。

但是使用Mockito,就有一个问题,那就是static method 和final class、final method 没有办法mock,对于这点如何解决,我们稍后会介绍到。

在测试环境中使用mock:依赖注入

接下来的一个问题就是,如何在测试环境下,把DataModel 换成mock 的对象,而正式代码中,DataModel 又是正常的对象呢?

这个问题也有两种解决方案,一是使用专门的testing product flavor;二是使用依赖注入。第一种方案就是用一个专门的product flavor 来做testing,在这个testing flavor 里面,里面把需要mock 的类写一份mock 的implementation,然后通过factory 提供给client,这个factory 的接口在testing flavor 和正式的flavor 里面是一样的,在跑testing 的时候,专门使用这个testing flavor,这样通过factory 得到的就是mock 的类。这种情况看起来很简单,但其实很不灵活,因为只有一种mock 实现;此外,代码会变得很丑陋,因为你需要为每一个dependency 提供一个factory,会觉得很刻意;再者,多了一个flavor,很多gradle 任务都会变得很慢。关于这种方案,可以参考这个视频 https://www.youtube.com/watch?v=vdasFFfXKOY)。

因此,我们用的是第二种,依赖注入。先简单介绍一下依赖注入这个模式,他的基本理念是,某一个类(比如说 DataActivity),用到的内部对象(比如说 DataModel)的创建过程不在 DataActivity 内部去 new,而是由外部去创建好 DataModel 的实例,然后通过某种方式 set 给 DataActivity。这种模式应用是非常广泛的,尤其是在测试的时候。为了更方便的做依赖注入,如今有很多框架专门做这件事情,比如 RoboGuice Dagger Dagger2 等等。我们用的是 Dagger2,理由很简单,这是目前最好用的 DI 框架。

关于 Dagger2 的文章,之前我们群里也分享了不少,但是好像我并没有看到讲述没有关于如何在测试环境下使用 Dagger2 的文章,这个还是略感遗憾的。离开单元测试,使用依赖注入就少了很有说服力的一个理由。

那么这里我就介绍一下,怎么样把 Dagger2 应用到单元测试中。熟悉 dagger2 的童靴可能知道,Dagger2 里面最关键的有两个概念,ModuleComponent。Module 是负责生成诸如 DataModel 这样被别人(比如 DataActivity)使用的类的地方。用术语的话,被别人使用的类 DataModel 叫 Dependency,使用到了别的类的类 DataActivity 叫 Client。而 Component 则是供 Client 使用 Dependency 的统一接口。也就是说,DataActivity 通过 Component,来得到一份 DataModel 的实例。

现在,关键的地方来了,Component 本身是不生产 dependency 的,它只是搬运工而已,真正生产 dependency 的地方在 Module。所以,创建 Component 需要用到 Module,不同的 Module 生产出不同的 dependency。在正式代码里面,我们使用正常的 Module,生产正常的 DataModel。而在测试环境中,我们写一个 TestingModule,让它继承正常的 Module,然后 override 掉生产 DataModel 的方法,让它生产 mock 的 DataModel。在跑单元测试的时候,使用这个 TestingModule 来创建 Component,这样的话,DataActivity 通过 Component 得到的 DataModel 对象就是 mock 出来的 DataModel 对象。

使用这种方式,所有 production code 都不用专门为 testing 增加任何多余的代码,同时还能得到依赖注入的其他好处。

Robolectric:解决 Android 单元测试最大的痛点

接下来讲讲 Android 单元测试最大的痛点,那就是 JVM 上面运行纯 JUnit 单元测试时是不能使用 Android 相关的类的,因为我们开发用到的安卓环境是没有实现的,里面只定义了一些接口,所有方法的实现都是 throw new RuntimeException(“stub”);,如果我们单元测试代码里面用到了安卓相关的代码的话,那么运行时就会遇到 RuntimeException(“Stub”)。

要解决这个问题,一般来说有三种方案:

  1. 使用 Android 提供的 Instrumentation 系统,将单元测试代码运行在模拟器或者是真机上。
  2. 用一定的架构,比如 MVP 等等,将安卓相关的代码隔离开了,中间的 Presenter 或 Model 是存 java 实现的,可以在 JVM 上面测试。View 或其他 android 相关的代码则不测。
  3. 使用 Robolectric 框架,这个框架基本可以理解为在 JVM 上面实现了一套安卓的模拟环境,同时给安卓相关的类增加了其他一些增强的功能,以方便做单元测试,使用这个框架,我们就可以在 JVM 上面跑单元测试的时候,就可以使用安卓相关的类了。

第一种方案能 work,但是速度非常慢,因为每次运行一次单元测试,都需要将整个项目打包成 apk,上传到模拟器或真机上,就跟运行了一次 app 似得,这个显然不是单元测试该有的速度,更无法做 TDD。这种方案首先被否决。

刚开始,我们采用的是 Robolectric,原因有两个:

  1. 我们项目当时还没有比较清楚的架构,android 跟纯 java 代码的隔离没有做好;
  2. 很多安卓相关的代码,还是需要测试的,比如说自定义 View 等等。

然而慢慢的,我们的态度从拥抱 Robolectric,到尽量不用它,尽量使用纯 java 代码去实现。可能大家觉得安卓相关的代码会很多,而纯 java 的很少,然而慢慢的你会发现,其实不是这样的,纯 java 的代码其实真不少,而且往往是核心的逻辑所在。之所以尽量不用 Robolectric,是因为 Robolectric 虽然相对于 Instrumentation testing 来说快多了。但毕竟他也需要 merge 一些资源,build 出来一个模拟的 app,因此相对于纯 java 和 JUnit 来说,这个速度依然是很慢的。

用具体的数字来对比说明:

  • 运行 Instrumentation testing:几十秒,取决于 app 的大小
  • Robolectric:10 秒左右
  • JUnit:几秒钟之内

当然,虽然运行一次 Robolectric 在 10 秒左右,但是对比运行一次 app,还是要快太多。因此,刚开始的时候,从 Robolectric 开始完全是 OK 的。

以上就是现在我们这边单元测试用到的几个基本技术:JUnit4 + Mockito + Dagger2 + Robolectric。基本来说,并没有什么黑科技,都是业界标准。

一个具体的案例

接下来,我通过一个具体的案例,跟大家介绍一下,我们这边的一个 app,具体是怎么单测的。

这里是我们收银台界面的样子:

假设 Activity 名字为 CheckoutActivity,当它启动的时候,CheckoutActivity 会去调一个 CheckoutModel 的 loadCheckoutData() 方法,这个方法又会去调更底层的一个封装了用户认证等信息的网络请求 Api 类 (mApi) 的 get 方法,同时传给这个 Api 类一个 callback。这个 callback 的做的事情是将结果通过 Otto Bus (mBus) post 出去。CheckoutActivity 里面 Subscribe 了这个 Event(方法名是 onCheckoutDataLoaded()),然后根据 Event 的值相应的显示数据或错误信息。

代码简写如下:

这里,CheckoutActivity 里面的 mCheckoutModel、CheckoutModel 里面的 mApi、CheckoutModel 里面的 mBus,都是通过 Dagger2 注入进去的。在做单元测试的时候,这些都是 mock。

对于这个流程,我们做了如下的单元测试:

  • CheckoutActivity 启动单元测试:通过 Robolectric 提供的方法,启动一个 Activity。验证里面的 mCheckoutModel 的 loadCheckoutData() 方法得到了调用,同时参数(订单 ID 等)是对的。
  • CheckoutModel 的 loadCheckoutData 单元测试 1:调用 CheckoutModel 的 loadCheckoutData() 方法,验证里面的 mApi 对应的 get 方法得到了调用,同时参数是对的。
  • CheckoutModel 的 loadCheckoutData 单元测试 2:mock Api 类,指定当它的 get 方法在收到某些调用的时候,直接调用传入的 callback 的 onSuccess 方法,然后调用 CheckoutModel 的 loadCheckoutData() 方法,验证 Otto bus 的 post 方法得到了调用,并且参数是对的。
  • CheckoutModel 的 loadCheckoutData 单元测试 3:mock api 类,指定当它的 get 方法在收到某些调用的时候,直接调用传入的 callback 的 onFailure 方法,然后调用 CheckoutModel 的 loadCheckoutData() 方法,验证 Otto bus 的 post 方法得到了调用,并且参数是对的。
  • CheckoutActivity 的 onCheckoutDataLoaded 单元测试 1:启动一个 CheckoutActivity,调用他的 onCheckoutDataLoaded(),传入含有正确数据的 Event,验证相应的数据 view 显示出来了
  • CheckoutActivity 的 onCheckoutDataLoaded 单元测试 2:启动一个 CheckoutActivity,调用他的 onCheckoutDataLoaded(),传入含有错误信息的 Event,验证相应的错误提示 view 显示出来了。

这里需要说明的一点是,上面的每一个测试,都是独立进行的,不是说下面的单元测试依赖于上面的。或者说必须先做上面的,再做下面的。

这部分较为详细的代码放在 github https://github.com/ChrisZou/android-unit-testing-tutorial)上,groupshare 这个 package 里面。

其他的问题

以上就是我们这边做单元测试用到的技术,以及一个基本流程,下面聊聊其他的几个问题。

哪些东西需要测试呢?

  1. 所有的 Model、Presenter/ViewModel、Api、Utils 等类的 public 方法
  2. Data 类除了 getter、setter、toString、hashCode 等一般自动生成的方法之外的逻辑部分
  3. 自定义 View 的功能:比如 set data 以后,text 有没有显示出来等等,简单的交互,比如 click 事件,负责的交互一般不测,比如 touch、滑动事件等等。
  4. Activity 的主要功能:比如 view 是不是存在、显示数据、错误信息、简单的点击事件等。比较复杂的用户交互比如 onTouch,以及 view 的样式、位置等等可以不测。因为不好测。

CI 和 code coverage: Jacoco

要把单元测试正式化,CI 是非常重要的一步,我们有一个运行 Jenkins 的 CI server,每次开发者 push 代码到 master branch 的时候,会运行一次单元测试的 gradle task,同时使用 Jacoco 做 code coverage。

这里有个坑要特别注意,那就是项目里面的 gradle Jacoco 插件和 Jenkins 的 Jacoco 插件的兼容性问题。我们用的 gradle Jacoco 插件是 7.1,更高版本的好像有问题。然后对应的 Jenkins 的 Jacoco 插件需要 1.0.19 或更低版本的,更高版本的 jenkins plugin 不支持低版本的 gradle Jacoco 项目版本。实际上,这点在 Jenkins 的 Jacoco 插件首页就有说明:

(点击放大图像)

但是我当时没注意,所以覆盖率数据一直出不来,折腾了好一会,最后还是在同事的帮助下找到问题了。

遇到的坑,以及好的practice 建议

接下来讲讲我们遇到的一些坑,以及一些好的practice 建议。

1. Native libary

无论是纯 JUnit 还是 Robolectric,都不支持 load native library,会报 UnsatisfiedLinkError 的错。所以如果你的被测代码里面用到了 native lib,那么可能需要给 System.loadLibrary 加上 try catch。

如果是被测代码用到的第三方 lib,而里面用到了 native lib 的话,一般有两种解决办法,一种是将用到 native lib 的第三方类外面自己在包一层,然后在测试的情况下 mock 掉。第二种是用 Robolectric,给那个类创建一个 shadow class。

第一种方法的好处是可以在测试的时候随时改变这个类的返回值或行为,缺点是需要另外创建一个 wrapper 类,会有点繁琐。第二种方式不能随时改变这个类的行为,但是写起来非常简单。所以,看自己的需要,选择相应的方法。

这两种方法,也是解决 static method, final class/method 不能 mock 的主要方式。

2. 尽量写出易于测试的代码

static method、直接 new object、singleton、Global state 等等这些都是一些不利于测试的代码方式,应该尽量避免,用依赖注入来代替这些方式。

3. 不要重复你的 unit test

比如说你使用了一个 builder 模式来创建了一个类,这个 builder 有一个 validator,来 validate 一些参数情况。那么这种情况,builder 跟 validator 分开测,用各种正确的错误的参数情况去测试 validator,然后测 builder 的时候,就不用遍历各种有效的跟无效的参数去测试了。

因为如果这样的话,到时候 Validator 的逻辑改了,那么针对 Validator 的测试跟针对 Builder 的测试都要修改,这个其实是重复的。这里只需要测试这个 builder 里面有一个 Validator 就好了。

4. 公共的单元测试 library

如果你们公司也是组件化开发的话,抽出一个公共的单元测试类库来做单元测试,里面可以放一些公共的 helper、utils、rules 等等,这个可以极大的提高写单元测试的速度。

5. 把安卓里面的“纯 java”代码 copy 一份到自己的项目里面

安卓里面有些类其实跟安卓没太大关系的,比如说 TextUtils、Color 等等,这些类完全可以把代码 copy 出来,放到自己的项目里面,然后其他地方就用这个类,这样也能部分摆脱 android 的依赖,使用 JUnit 而不是 Robolectric,提高运行 test 的速度。

6. 充分发挥 JUnit Rule 的作用

JUnit Rule 是个很强大的工具,然而知道的人却不多。它的基本作用是,让你在执行某个测试方法前后,可以做一些事情。如果你的好几个测试类里面有很多的共同的 setup、teardown 工作,你可能会倾向于使用继承,结合 @Before、@After 来减少 duplication,这里更建议大家使用 JUnit Rule 来实现这个目的,而不是用继承,这样可以有更大的灵活性。

比如,为了方便测试 Activity 的 method,我们有一个 ActivityRule,在跑一个测试方法之会启动 target Activity,然后跑完以后自动 finish 这个 activity。

其中一个比较有趣的用 JUnit Rule 实现的功能,是实现类似于 BDD 测试框架的命名方式。做单元测试的时候,你经常需要为同一个方法写好几个测试方法,每个测试方法测试不同的点。为了让命名更具可读性,我们往往会把名字写的很长,在这种情况下,如果用驼峰命名的话,需要不断切换大小写,写起来麻烦,可读性也不高。如果用下划线的话,写起来也很麻烦。如果你使用过 BDD 的一些框架(比如 RSpec Cucumber Jasmine 等),你就会异常怀念那种“命名”方式。如果你没用过的话,那种“命名”方式大概是这样的:

这里的关键是,当测试方法失败的时候,这个字符串是要能被加到错误信息里面的。我们做了个 JUnit Rule 来达到这个效果。做法是结合一个自定义的 annotation,这个 annotation 接收一个 String,来描述这个测试方法的测试目的。在 Rule 里面将这个 annotation 读出来,如果测试没通过的话,把这个描述性的 String 加到输出的 error message 里面。这样在批量运行的时候,一看就知道没通过的测试是测什么东西的。而测试方法的命名则可以比较随意。
达到的效果如下:

如果运行失败,得到如下的结果

关于 JUnit Rule 的使用,大家可以自行 google 一下,也不难。

7. 善于利用 AndroidStudio 来加快你写测试的速度

AndroidStudio 有很多 feature 可以帮助我们更快的写代码,比如 code generation 和 live template 。这点对于写正式代码也适用,不过对于写测试代码来说,效果更为突出。因为大部分测试代码的结构、风格都是类似的,在这里 live template 能起非常大的作用。此外,如果你先写测试,可以直接写一些还不存在的 Class 或 method,然后 alt+enter 让 AndroidStudio 自动帮你生成。

8. 不要追求完美

刚开始的时候,不用追求测试代码的质量,也不用追求完美,如果有些地方不好写测试,可以先放放,以后再来补,有部分测试总比没有测试好。 Martin Fowler 说过

Imperfect tests, run frequently, are much better than perfect tests that are never written at all.

然而等你熟悉写测试的方法以后,强烈建议先写测试!因为如果你先写了正式代码,那你对这写代码是如何 work 的已经有一个印象了,因此你往往会写出能顺利通过的测试,而忽略一些会让测试不通过的情况。如果先写测试,则能考虑得更全面。

9. 未来的打算

使用 Groovy 和 RoboSpock 或者是 Kotlin 和 Spek ,真正实现 BDD,这是很可能的事情,只是目前我们这边还没太多那方面的实践,因此就不说太多了。以后有一定实践了,到时候可以再更大家交流。

文中部分代码: https://github.com/ChrisZou/android-unit-testing-tutorial

QA 环节

Q:如何测试界面交互?如点击拖动等。

A:Robolectric 提供了非常丰富的测试交互的方式,比如 findViewById(id).performClick()。基本上,使用 Robolectric,你可以像正常写安卓代码那样写测试代码。甚至正常情况下没有的方法,Robolectric 也提供了。

Q:我也是后来才接触代码测试的,然后开始喜欢上写代码测试,但当尝试为以前的代码写代码测试的时候,发现以前的结构很难写代码测试,请问你们也有遇到这种情况么?如何解决。

A:这的确是比较头疼的问题,建议可以看看《Working Effective With Legacy Code》一般来说就是挑选一个比较好下手的地方,做好隔离写好测试,在重构。那本书里面提出了很多简单除暴的方式,比如把一个方法或变量从 private 改成 public 等等。

Q:自绘控件一般怎么去做自动化测试?

A: 自定义控件一般只测他的功能性的部分,样式、动画这样的一般不测。

测试方式基本就是把这个控件 new 出来,然后调用它的 public 方法,验证它的 text 是不是正确等等。或者是相应的事件有没有触发,这个借助 Robolectric 可以做到。

Q:业务测试数据,是自己本地写的逻辑,还是结合服务器的真实逻辑?

A:对于单元测试来说,一般是自己 mock 服务器的返回结果,因为服务器返回结果是不是正确的,其实不是我们应该测的情况,而是服务器应该测的情况,我们要测的,是服务器返回正确的结果我们就显示正确的结果,服务器返回错误的结果,我们就显示错误的返回信息。

Q:MVP 的情况下 view 和 presenter 的回调函数需要做测试吗,如果需要怎么做?

A:要测,把 presenter new 出来,直接调用它的那个方法就是了。

Q:对于依赖环境的测试,比如有无网络,不同的网络测试类型,不同的网络类型,网络超时等,这种怎么去做单元测试比较好?在比如测试试写文件的方法,怎么去构造剩余空间不足、空间足够的环境?

A:这些情况需要借助系统的 api(比如 NetworkManager), 去判断情况,这种情况可以把这些系统的 api mock 掉,指定让他返回你想要指定的结果。

Q:为啥方法名不是驼峰命名法?

A:因为常常需要为同一个方法写好几个测试方法,每个方法测试的目的可能不一样,这个时候往往会把测试方法写的很长,需要一段的驼峰大小写切换,这样可读性不高,写起来又麻烦,或者是用下划线去分隔,这样写起来又很麻烦。因此我们特别写了那个 annotation 和 junit rule,目的就是可以让单元测试的方法命名可以随意一点。

Q:robolectric 一般只能模拟点击到一个子控件,但是自绘的控件可能不满足,自绘控件一般是为了较少 layout 的嵌套,而实现自绘的,点击控件的不同区域可能会触发不同的事件,以前我们的做法非常拿到,需要专门去根据这个自绘控件去这一大堆的测试代码,不知道有没有什么好的方法?

A:这种情况的确没碰到过,我觉得可以看看 Robolectric 有没有指定点击这个 view 的某个坐标的方法,以我的经验,要实现你说的那种功能,应该是在这个 view 的 ontouch 时间里面去处理把,可以看看 Robolectric 有没有类似的模拟 ontouch 的方法,估计应该是有的。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-05-19 17:3313489

评论 1 条评论

发布
用户头像
为什么robolectric需要10s耗时?主要耗时在哪里?
2020-08-14 00:17
回复
没有更多了
发现更多内容

Week 1 学习总结

J

极客大学架构师训练营

一张图彻底理解Spring如何解决循环依赖!!

冰河

spring aop ioc 源码解析 循环依赖

深入浅出Spark

大数志

大数据 spark 数据科学

讲真,你知道Python咋来的吗?

华为云开发者联盟

Java Python 编程语言 C语言 代码

架构师训练营第 1 期 - 第十周总结

Todd-Lee

极客大学架构师训练营

WSL2:我在原生的Win10玩转Linux系统

梁桂钊

tcp/ip协议栈——epoll的内部实现原理

Linux服务器开发

后端 TCP/IP epoll 网络协议栈 服务器开发

和同事交流不会kafka怎么行,API奉上,不是大神也能编

小Q

Java 学习 架构 面试

架构师训练营第 10 周学习总结

netspecial

极客大学架构师训练营

我是如何在五年阿里面试官的连珠炮问下三面斩获Java岗offer,最后定级P6的呢?

Java~~~

Redis面试受阻?阿里P8架构师整理出的核心笔记+实战+面试题+脑图送你

比伯

Java 编程 程序员 面试 计算机

5G矿山,工业真金,以及智能体矿井

脑极体

清华大佬马士兵告诉你从阿里P5级一直学到P8架构师的成长路线+视频教程!

比伯

Java 编程 架构 面试 计算机

精心整理MySQL基本使用(数据库的操作、数据类型、MySQL的常用命令)

ShenDu_Linux

c++ MySQL 程序员 数据类型

数仓搬迁:从方法到实践,带你解决数据一致性对比

华为云开发者联盟

数据仓库 数据 存储 数据校验 搬迁

数字时代,如何跟上互联网医院的建设潮?

CECBC

数字化医疗

学习工作即游戏:游戏化生存的现实物语

脑极体

《具有算法和程序的离散数学基础》PDF免费下载

计算机与AI

算法 离散数学

架构师训练营第 1 期 - 第十周作业

Todd-Lee

极客大学架构师训练营

白皮书丨关于工业互联网,你想知道的都在这儿

华为云开发者联盟

工业互联网 华为云 白皮书 ICT 智能

Week 10 作业

黄立

阿里要求其内部程序员必须精通的并发编程笔记:原理+模式+应用

Java架构追梦

阿里巴巴 编程 面试 并发 java架构

多线程源码明白了吗?不明白的话来看腾讯大牛给你画的面试重点

小Q

Java 学习 架构 面试 线程

架构方法

raox

极客大学架构师训练营

跨国区块链投资 花式“割韭菜”骗光你的钱

CECBC

区块链

五周 - 总结

水浴清风

Python进阶——什么是上下文管理器?

Kaito

Python

iOS 项目避坑:多个分类中方法重复实现检测

iOSer

ios 项目管理 编程语言 iOS Document

“通证经济”实质是生产关系的变革

CECBC

通证经济

我就不信2W字把源码拆的这么碎,你还不明白mybatis缓存

996小迁

Java 源码 架构 面试 mybatis

go-zero 如何扛住流量冲击(二)

万俊峰Kevin

microservice Go 语言

蘑菇街支付金融Android单元测试实践_最佳实践_邹勇_InfoQ精选文章