简述

上文android 如何开始测试提到了测试的基本知识,我也是初次接触测试,可能存在误解,如果各位发现,还请不吝指正。

基本介绍

如果你对Robolectric不够了解,可以先阅读以下链接:

  1. Robolectric 官网指南
  2. Robolectric GitHub
  3. Robolectric 2.4 升级 3.0对比WIKI
  4. Robolectric GitBook
  5. 用Robolectric来做Android unit testing
  6. Robolectric Github 官方示例
  7. Robolectric Github 其他开发者提供的示例
  8. Robolectric Github 在开源项目Android-Boilerplate中运用

常见问题

  1. 异步任务
  2. 如何使用内置的shadow对象
  3. 准备测试环境
  4. 测试时,Application 对象获取为 null。(见下方项目实践
  5. 测试时,需要有选择的在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的基本认识如上,但是具体在测试中很多问题需要逐一解决,后续再一一整理出来。

希望大家多拍砖,特别是有错误的地方重拍都没事~

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐