0. 环境
- Mac OS 10.13.4
- Android Studio 3.1.2
- Junit 4.12
- Robolectric 3.7.1
- Mockito 2.13.0
- PowerMock 2.0.0-beta.5
1. 预备知识
1.1 JUnit4
- 什么是 JUnit4
1.2 Robolectric
- 什么是 Robolectric
1.3 Mockito
- 什么是 Mockito
1.4 PowerMock
- 什么是 PowerMock
- PowerMock 简介
2. 配置 Robolectric
单元测试主要基于 JUnit 和 Robolectric 进行。Android Studio 默认集成好了 JUnit,而 Robolectric 则需要稍稍配置一下,这里提供两种方式进行配置。
2.1 给每个测试类单独配置
在每个测试类上都加上注解
@RunWith(RobolectricTestRunner.class)
@Config(application = TestMyApplication.class, constants = BuildConfig.class, manifest = "./AndroidManifest.xml",packageName = "com.your.package")
复制代码
其中,TestMyApplication
是测试代码的初始化Application,可以把App的Application的逻辑放在这里面
2.2 在 TestMyRobolectricRunner
中集中配置
- 自定义 TestMyRobolectricRunner,继承自 RobolectricTestRunner
- 自定义 TestMyApplication,这个 TestMyApplication 承担的作用是初始化一些东西,比如 SDK 等,和 App 中的 Application 作用是一样的
- 在 TestMyRobolectricRunner 中重写
buildGlobalConfig
方法 - 在测试类中,就可以不再使用
@RunWith(RobolectricTestRunner.class)
注解,转而使用@RunWith(TestMyRobolectricRunner.class)
注解
public class TestMyRobolectricRunner extends RobolectricTestRunner {
/**
* Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
* and res directory by default. Use the {@link Config} annotation to configure.
*
* @param testClass the test class to be run
* @throws InitializationError if junit says so
*/
public TestMyRobolectricRunner(Class> testClass) throws InitializationError {
super(testClass);
}
@Override
protected Config buildGlobalConfig() {
return new Config.Builder()
.setApplication(TestMyApplication.class)
.setConstants(BuildConfig.class)
.setManifest("AndroidManifest.xml")
.setPackageName("com.yongf.mypackagename")
.build();
}
}
复制代码
3. Android 单测中的一些 case
3.1 如何测试 SharedPreference
SharedPreferences preferences = RuntimeEnvironment.application.getSharedPreferences(PREFERENCE_KEY, Context.MODE_PRIVATE);
复制代码
3.2 如何测试Intent跳转
比如,点击一个按钮以后在满足条件的情况下,会跳转到一个新页面。怎么测呢,可以先满足各种设置条件,然后模拟按钮点击,然后获取系统跳转的下一个 activity 是否指定的 activity。
//按钮点击后跳转到下一个Activity
forwardBtn.performClick();
Intent expectedIntent = new Intent(sampleActivity, LoginActivity.class);
Intent actualIntent = ShadowApplication.getInstance().getNextStartedActivity();
assertEquals(expectedIntent, actualIntent);
复制代码
3.3 如何测试Dialog的显示
某些情况下,弹出Dialog。
ShadowDialog latestDialog = ShadowApplication.getInstance().getLatestDialog();
复制代码
遗憾的是,目前只能测试是否有对话框弹出,并不能测试对话框的内容(PS: 如果观众朋友你会的话,千万别吝啬你的才华,请在评论中或者 issue 中赐教~)
3.4 如何测试Toast的显示
某些情况下,弹出 Toast
Toast latestToast = ShadowToast.getLatestToast();
String textOfLatestToast = ShadowToast.getTextOfLatestToast();
复制代码
可以测试 Toast 的显示,以及 Toast 的内容
3.5 如何设置测试用例的Application
参考上述 配置 Robolectric
章节的示例代码
3.6 如何指定单个测试[类|方法]的sdk版本
单个测试类,在[类|方法]上面添加 @Config(sdk = 21)
即可,具体的 sdk 版本自己指定
3.7 需要用户权限授权的场景如何测试
目前我的做法是指定 sdk 版本为21以下,因为没有动态权限,这样需要用户授权的部分就能走。但这样带来的问题就是,权限未授权的分支部分代码没有被覆盖到。
如果你有更好的做法,赶紧来告诉我吧,谢谢啦~
3.8 如何测试 onActivityResult
- 使用 Robolectric 构造一个 activity
- 然后直接调用 onActivityResult 方法
3.9 如果出现 not mocked,怎么办?
在运行的测试用例的时候,你可能会遇到如下错误:
java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked. See http://g.co/androidstudio/not-mocked for details.
at android.text.TextUtils.isEmpty(TextUtils.java)
at com.example.robolectric.TextUtilsTest.testIsEmpty(TextUtilsTest.java:14)
复制代码
如何解决呢?很简单,把 Android 源码中这个类搬到测试代码目录下即可(注意要保持包名一致)
4. 单元测试编写实战
TODO
4.1 搭建环境
- 新建 demo 项目
- 引入相关依赖
- TODO