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.classpackageNameR.class文件所在的包名,如果在 gradle 文件的 productflavor 对包名进行了改变,则需要指定该设置application指定测试用例所使用的 Application类,次指定将会覆盖 AndroidManifest中指定的 Application 类qualifiers指定资源文件所使用的语言、横竖屏等,在多语言测试时很有用,方法和类级别注解都可以 resourceDir指定加载资源所使用的文件夹,默认为 resassetDir指定加载 assert 文件所使用的文件夹,默认为 assetsshadows指定自定义 shadow 文件,可以同时指定多个 instrumentedPackages已经设备化的包名列表 libraries工程所依赖的 library 文件夹,可以同时指定多个,默认为 {}例如:
(manifest = "AndroidManifest.xml",
// sdk = 27,
minSdk = 26,
maxSdk = 28,
shadows = CustomShadow.class,
libraries = {
"path/to/library1",
"path/to/library2"
})
(RobolectricTestRunner.class)
public class RobolectricTest {
(qualifiers = "zh-CN",
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