12

如果我为我的 Activity 编写自定义 Shadow,并将其注册到 RobolectricTestRunner,框架是否会在启动时使用我的自定义 Shadow 拦截 Activity?

谢谢。

4

5 回答 5

11

最简洁的答案是不。

Robolectric 对拦截的类别和仪器有选择性。在撰写本文时,唯一将被检测的类必须具有与以下选择器之一匹配的完全限定类名:

android.* 
com.google.android.maps.* 
org.apache.http.impl.client.DefaultRequestDirector

Robolectric 存在的全部原因是 Android SDK jar 中提供的类在 JVM 中调用时会抛出异常(即不在模拟器或设备上)。您的应用程序的 Activity 具有非“敌对”的源(在调用方法或构造函数时它可能不会引发异常)。Robolectric 的预期目的是允许您对应用程序的代码进行测试,否则由于 SDK 的编写方式,这是不可能的。创建 Robolectric 的其他一些原因是:

  • SDK 并不总是具有允许您查询由应用程序代码操作的 Android 对象状态的方法。可以编写阴影来提供对此状态的访问。
  • Android SDK 中的许多类和方法都是最终的和/或私有的或受保护的,因此很难创建应用程序代码所需的依赖项,否则这些依赖项对应用程序代码可用。

可以清楚地更改代码以隐藏任何类。过去曾讨论过将阴影特征提取到独立库中,以帮助使用其他一些不利于测试的 api 编写测试。

为什么要隐藏您的活动?

于 2011-08-31T04:53:32.687 回答
9

这在 Robolectric 2 中发生了显着变化。您可以在配置中指定自定义阴影,而不是编写自己的 TestRunner。

例如:

@Config(shadows = {ShadowAudioManager.class, ShadowContextWrapper.class})
于 2013-07-25T01:45:09.863 回答
3

是的,如果您将 RobolectricTestRunner 子类化,则将自定义包添加到构造函数并在 bindShadowClasses 方法中加载您的 Shadow 类。无需使用 android.* 包技巧。

(注意:这是使用 robolectric-1.1)

RobolectricTestRunner#setupApplicationState 中提供了许多可以覆盖的钩子。

这是我对 RobolectricTestRunner 的实现。

import org.junit.runners.model.InitializationError;

import com.android.testFramework.shadows.ShadowLoggerConfig;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.RobolectricTestRunner;

public class RoboRunner extends RobolectricTestRunner {

public RoboRunner(Class<?> clazz) throws InitializationError {
    super(clazz);
    addClassOrPackageToInstrument("package.you're.creating.shadows.of");
}

@Override
protected void bindShadowClasses() {
    super.bindShadowClasses(); // as you can see below, you really don't need this
    Robolectric.bindShadowClass(ShadowClass.class);
}

}

您可以子类化的更多方法(来自 RobolectricTestRunner.class)

/**
 * Override this method to bind your own shadow classes
 */
protected void bindShadowClasses() {
}

/**
 * Override this method to reset the state of static members before each test.
 */
protected void resetStaticState() {
}

   /**
 * Override this method if you want to provide your own implementation of Application.
 * <p/>
 * This method attempts to instantiate an application instance as specified by the AndroidManifest.xml.
 *
 * @return An instance of the Application class specified by the ApplicationManifest.xml or an instance of
 *         Application if not specified.
 */
protected Application createApplication() {
    return new ApplicationResolver(robolectricConfig).resolveApplication();
}

这是在 Robolectric TestRunner 中调用它们的地方:

 public void setupApplicationState(final RobolectricConfig robolectricConfig) {
    setupLogging();
    ResourceLoader resourceLoader = createResourceLoader(robolectricConfig);

    Robolectric.bindDefaultShadowClasses();
    bindShadowClasses();

    resourceLoader.setLayoutQualifierSearchPath();
    Robolectric.resetStaticState();
    resetStaticState();

    DatabaseConfig.setDatabaseMap(this.databaseMap);//Set static DatabaseMap in DBConfig

    Robolectric.application = ShadowApplication.bind(createApplication(), resourceLoader);
}
于 2012-12-20T17:44:58.243 回答
2

作为更新,我已经能够创建自己的类的影子,只要在任何可能的加载器作用于该类之前小心绑定影子类。因此,按照说明,在 RoboRunner 中我做了:

@Override protected void bindShadowClasses() {
    Robolectric.bindShadowClass(ShadowLog.class);
    Robolectric.bindShadowClass(ShadowFlashPlayerFinder.class);
}

我有没有提到我有点作弊?上面的原始答案(当然)是正确的。所以我将它用于我的真实课程:

package android.niftyco;

public class FlashPlayerFinder {
  .. . 

正如人们所预料的那样,我的模拟(影子)回到了我的测试包中:

package com.niftyco.android.test;

@Implements(FlashPlayerFinder.class)
public class ShadowFlashPlayerFinder {
    @RealObject private FlashPlayerFinder realFPF;

    public void __constructor(Context c) {
        //note the construction
    }

    @Implementation
    public boolean isFlashInstalled() {
        System.out.print("Let's pretend that Flash is installed\n");
        return(true);
    }
}
于 2012-06-05T14:07:08.323 回答
2

可能会迟到,但从这里:org.robolectric.bytecode.Setup,您可能会找到有关检测哪些类的更多详细信息。

  public boolean shouldInstrument(ClassInfo classInfo) {
    if (classInfo.isInterface() || classInfo.isAnnotation() || classInfo.hasAnnotation(DoNotInstrument.class)) {
      return false;
    }

    // allow explicit control with @Instrument, mostly for tests
    return classInfo.hasAnnotation(Instrument.class) || isFromAndroidSdk(classInfo);
  }

  public boolean isFromAndroidSdk(ClassInfo classInfo) {
    String className = classInfo.getName();
    return className.startsWith("android.")
        || className.startsWith("libcore.")
        || className.startsWith("dalvik.")
        || className.startsWith("com.android.internal.")
        || className.startsWith("com.google.android.maps.")
        || className.startsWith("com.google.android.gms.")
        || className.startsWith("dalvik.system.")
        || className.startsWith("org.apache.http.impl.client.DefaultRequestDirector");
  }
于 2014-10-27T21:04:50.840 回答