使用 robolectric 做单元测试
简述上文android 如何开始测试提到了测试的基本知识,我也是初次接触测试,可能存在误解,如果各位发现,还请不吝指正。基本介绍如果你对Robolectric不够了解,可以先阅读以下链接:Robolectric 官网指南Robolectric GitHubRobolectric 2.4 升级 3.0对比WIKIRobolectric GitBook用Robolec
简述
上文android 如何开始测试提到了测试的基本知识,我也是初次接触测试,可能存在误解,如果各位发现,还请不吝指正。
基本介绍
如果你对Robolectric不够了解,可以先阅读以下链接:
常见问题
- 异步任务
- 如何使用内置的shadow对象
- 准备测试环境
- 测试时,Application 对象获取为 null。(见下方项目实践)
- 测试时,需要有选择的在Application中初始化代码。见下方项目实践)
后续遇到其他问题,再进行补充
项目实践
遇到的第一个问题:如何有选择的在 Application 初始化?
通常一段 App
的初始化代码如下。
public class App extends Application{
// ignore some code
@Override
public void onCreate () {
super.onCreate ();
if (isMainProcess ()) {
setupCrashReporting ();
setupUmeng ();
setupBasic ();
setupComponent ();
}
}
// ignore some code
}
但是:
- 如果使用
setupUmeng
友盟的一系列服务,可能会导致测试过程中衍伸出一些不必要的错误警告。 - 若在
App
中实现了Thread.UncaughtExceptionHandler
接口,来完成错误收集功能。则会导致单元测试时,无法正常执行。 - 亦或者,需要在单元测试时初始化一些正常运行时额外的一些服务。
但是通常,直接更改App
会导致代码中的职责界限不清晰。
我选择的做法是在 src/test/java
目录下扩展App
子类UnitTestApp
如下。
public class UnitTestApp extends App {
@Override
public void setupCrashReporting () {
}
@Override
public void setupUmeng () {
}
@Override
protected void setupComponent () {
CollectInfoUtil.init (this);
initVersionInfo ();
}
@Override
public boolean isMainProcess () {
return true;
}
}
这生产了一个仅测试使用的UnitTestApp
,但是如何测试时使用的是UnitTestApp
,而不是App
。你需要自定义TestRunner
,如下:
public class UnitTestGradleTestRunner extends RobolectricGradleTestRunner {
// This value should be changed as soon as Robolectric will support newer api.
private static final int SDK_EMULATE_LEVEL = 21;
public UnitTestGradleTestRunner (Class<?> klass) throws InitializationError {
super (klass);
}
@Override
public Config getConfig (Method method) {
final Config defaultConfig = super.getConfig (method);
return new Config.Implementation (
new int[]{SDK_EMULATE_LEVEL},
defaultConfig.manifest (),
defaultConfig.qualifiers (),
defaultConfig.packageName (),
defaultConfig.resourceDir (),
defaultConfig.assetDir (),
defaultConfig.shadows (),
UnitTestApp.class, // Here is the trick, we change application class to one with mocks.
defaultConfig.libraries (),
defaultConfig.constants () == Void.class ? BuildConfig.class : defaultConfig.constants ()
);
}
@NonNull
public static UnitTestApp getApp () {
return (UnitTestApp) RuntimeEnvironment.application;
}
}
在你需要使用到App
的测试类中指定如下:
@RunWith (UnitTestGradleTestRunner.class)
@Config (constants = BuildConfig.class)
public class LoginActivityTest
{
@Before
public void setUp () throws Exception {
UnitTestApp app = UnitTestGradleTestRunner.getApp ();
Assert.assertNotNull ("shadowApp not null.", app);
Assert.assertNotNull ("shadowApp context not null.", app.getApplicationContext ());
}
}
其他建议
在测试过程中,经常会需要对测试代码进行 set/get
操作。但是大部分时候,一些方法或者域是private
或者protect
,为了保证测试功能代码的唯一性,我选择使用reflect
方式,来确保测试工作的顺利。我使用的reflection-utils来完成反射工作。
结束语
对robolectric
的基本认识如上,但是具体在测试中很多问题需要逐一解决,后续再一一整理出来。
希望大家多拍砖,特别是有错误的地方重拍都没事~
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)