Robolectric 框架是一款可以在JVM 上运行 Android 相关代码的框架,在 Android 单元测试总起到至关重要的作用。它既有减少编译、测试用例运行的时间等优点。
Robolectric 框架简介
Android 测试用例编写的过程中,和 Android 框架进行交互的单元是测试难点所在,也是主要耗费时间的地方,针对这类问题,在 Robolectric 框架出现之前主要有两种解法:
- 编写 Android JUnit Test,这类测试用例的优点是不用对框架相关的代码进行特殊处理,缺点是每次运行都需要编译被测试代码 apk 和测试代码 apk,比较耗时;
- 对 Android 框架相关的类、方法等进行 mock,此方法可以减少编译及运行时间,但是对大量框架相关代码的mock 对测试用例编写带来了一定的难度及复杂度;
幸运的是 Robolectric 框架的出现解决了这一系列问题。Robolectric可以在 JVM 上运行 Android 相关的代码,不需要借助手机就可以对 Activity、Service 等四大组件、资源等进行测试,给 Android 测试带来了很大的便利。
Robolectric 框架主要有以下优点:
-
框架对 Android.jar 包中几乎所有的类都进行了映射(Shadow),即对于一个类
X
,则有一个其影子ShadowX
,比如,Log
类对应的影子为ShadowLog
,测试用例过程中可以想操作原始类一样操作其映射。同时Robolectric 还提供了自定义影子的方法,这点后面会说到。除此之外,Robolectric 框架还对资源、Native 方法都进行了特殊的处理,使之能在 JVM 上运行。
-
在虚拟机和真实设备之外运行测试用例,正如前面说到的一样,Robolectric 对框架的代码做了重新实现,使之可以脱离手机或者虚拟机运行。
-
不需要去 mock 框架相关代码,当然,必要的情况下可以配合 mock 或者 powermock 等 mock 框架进行更全面的测试(后续文章会降到 Robolectric 和 PowerMock 框架搭配进行单元测试)。
Gralde 配置
使用 Robolectric 框架需要引入其对应的依赖包,并做其它的一些简单配置,如下:
android { |
Robolectric 配置
Robolectric 框架的配置主要分为两个:
-
Runner 指定:
(RobolectricTestRunner.class)
public class RobolectricDemoTest{
} -
利用
@Config
指定一些其它的配置,可以指定的配置主要有以下一些:配置属性 描述 sdk
/minSdk
/maxSdk
指定测试用例所工作 / 最小 / 最大的 sdk 版本 manifest
指定 Android manifest 文件,该文件中的资源、assert 文件都将被加载。 constants
指定由 Gradle 编译生成的 BuildConfig
文件,默认为Void.class
packageName
R.class
文件所在的包名,如果在 gradle 文件的 productflavor 对包名进行了改变,则需要指定该设置application
指定测试用例所使用的 Application
类,次指定将会覆盖 AndroidManifest中指定的 Application 类qualifiers
指定资源文件所使用的语言、横竖屏等,在多语言测试时很有用,方法和类级别注解都可以 resourceDir
指定加载资源所使用的文件夹,默认为 res
assetDir
指定加载 assert 文件所使用的文件夹,默认为 assets
shadows
指定自定义 shadow 文件,可以同时指定多个 instrumentedPackages
已经设备化的包名列表 libraries
工程所依赖的 library 文件夹,可以同时指定多个,默认为 {}
例如:
"AndroidManifest.xml", (manifest =
// sdk = 27,
minSdk = 26,
maxSdk = 28,
shadows = CustomShadow.class,
libraries = {
"path/to/library1",
"path/to/library2"
})
(RobolectricTestRunner.class)
public class RobolectricTest {
"zh-CN", (qualifiers =
application = CustomApplication.class)
public void name() {
}
}minSdk
或maxSdk
不能同时和sdk
同时出现 -
gradle 文件中配置系统属性
android {
testOptions {
unitTests.all {
systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'
systemProperty 'robolectric.dependency.repo.id', 'local'
}
}
}
自定义 Runner 加快下载 jar 包的速度
以 RobolectricTestRunner 作为注解的测试用例在运行之前会下载 android-all-x.x.x 相关文件,但是这个网站下载速度是能是龟速,有时候还会出现下载失败的问题。这种情况可以通过自定义 Runner,添加自定义的 maven 地址,例如阿里云等。当然也可以离线下载到<USER_HOME>/.m2/repository/org/robolectric/android-all/xxx/
目录下,xxx
为 jar 包的版本号。
RoboSettings
类中定义两个静态变量mavenRepositoryId
和mavenRepositoryUrl
,分别为 maven 仓库的 id 和 url,Robolectric 是通过读取这两个的值来获取 maven 仓库地址,因为只需要在自定义 Runner 中分别制定这两个变量值:
public class CustomRobolectricRunner extends RobolectricTestRunner { |
如果要修改全局配置,则需要在自定义 Runner 中重写buildGlobalConfig
方法。
测试 Android 组件
Robolectric 框架不仅可以测试 Activity 等 Android 四大组件,也可以测试例如 Dialog、Toast等,也可以测试控件的状态。
Activity测试
Activity 测试主要包括 Activity 的创建、生命周期以及 Activity 的跳转等,下面分别进行举例。
Activity 创建测试
Robolectric 框架提供了ActivityController
类来操作 Activity,不仅可以创建 Activity,还可以对 Activity 的周期函数进行操作。
|
Activity 生命周期测试
|
Activity 跳转测试
下面是测试点击一个按钮起动 Activity 的用例
|
Dialog 测试
测试点击按钮弹出 Dialog 的测试代码如下:
|
Toast 测试
|
控件状态测试
在 Activity 起动获取到控件之后,可以对控件的状态进行验证,以下是一个验证是否可用的例子:
|
BroadCastReceiver 测试
广播接收器的测试主要分为两类:
- 是否注册该广播;
- 接收到广播之后逻辑是否正常;
以下是一个接收弹出 Toast 的一个广播接收器的测试用例:
|
Service 测试
以自定义 IntentService 为例,service 主体代码及测试代码如下:
public class MyService extends IntentService { |
资源测试
资源测是通过@Config(qualifiers = "")
知道不同语言、分辨率或者横竖屏,对不同的资源进行测试:
/** |
Looper 测试
Looper 测试主要是测试代码中使用 handler处理消息产生延迟的情况。假如使用 hanler 发送一个延时的消息到消息队列,但是在测试用例验证的过程中次消息不一定被执行到,因此后续的验证会产生一定的错误,这种情况下需要利用Looper 单独去测试 handler 所发消息的逻辑。
// handle 代码如下: |
当测试代码执行useHandler
方法时,run 内部的逻辑不会执行,Log 不会打印,因此测试用例代码需要单独运行消息队列中的消息:
|
此时 run 方法将会得到执行。
自定义 Shadow
Shadow 作为 Robolectric 框架的核心所在,是该框架的重中之重。Shadow 意为影子,Robolectric 框架对 android.jar
包中大部分类都做了影子,例如Activity
的影子为ShadowActivity
、View
的影子为ShadowView
。虽然 Robolectric 做了很多影子,但是不一定满足我们项目测试所需。Robolectric 还支持自定义影子,并用@config
注解指定即可。
假如有类文件如下:
public class Demo { |
自定义 Shadow 的过程如下:
- 以
@Implements(Demo.class)
注解自定义的 Shadow 类 - 为自定义 Shadow 提供了一个 public 的构造函数;
- 对原始类的方法以
@Implementation
,并做自定义实现; - 在使用的测试类或者测试方法上以
@Config(shadows = ShadowDemo.class)
;
接下来在执行本体类方法的时候就会由 Robolectric 框架转到执行自定义 Shadow 内部的方法。另外,自定义 Shadow 不仅可以实现本体的方法,还可以添加自定义方法作为本体的一种扩展。上述类的自定义 Shadow 如下:
(Demo.class) |
测试代码如下:
(shadows = ShadowDemo.class) |
在知道本体对象的情况下使用如下方法去获取对应的影子对象:
Demo demo = new Demo(); |
小结
Robolectric 框架在 Android 测试中有着很大的便利性,可以快速编写并运行测试用例。在我写测试用例的过程中,Robolectric 框架相关的占了很大的比例。上述所讲到的只是 Robolectric 框架很小的一部分,其它用法还需要阅读文档或者看源代码进行挖掘。
本文所有的示示例代码:Robolectric Demo