Как именно пользовательские объекты Shadow работают в Robolectric? - PullRequest
12 голосов
/ 31 августа 2011

Если я напишу пользовательскую тень для своей деятельности и зарегистрирую ее в RobolectricTestRunner, будет ли каркас перехватывать операцию с моей собственной тенью при ее запуске?

Спасибо.

Ответы [ 5 ]

11 голосов
/ 31 августа 2011

Короткий ответ - нет.

Robolectric избирательно относится к тем классам, которые он перехватывает, и к инструментам. На момент написания этой статьи единственные классы, которые будут оснащены, должны иметь полное имя класса, совпадающее с одним из следующих селекторов:

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

Вся причина существования Robolectric заключается в том, что классы, представленные в jar Android SDK, генерируют исключения при вызове в JVM (т. Е. Не на эмуляторе или устройстве). У Activity вашего приложения есть источник, который не является «враждебным» (вероятно, он не генерирует исключения при вызове методов или конструкторов). Назначение Robolectric - дать вам возможность протестировать код вашего приложения, что в противном случае было бы невозможно из-за способа написания SDK. Некоторые другие причины, по которым был создан Robolectric:

  • В SDK не всегда есть методы, позволяющие запрашивать состояние объектов Android, которыми манипулирует код вашего приложения. Тени могут быть написаны для обеспечения доступа к этому состоянию.
  • Многие классы и методы в Android SDK являются окончательными и / или частными или защищенными, что затрудняет создание зависимостей, необходимых для кода вашего приложения, которые в противном случае были бы доступны для кода вашего приложения.

Код может быть явно изменен, чтобы скрыть любой класс. В прошлом говорили о выделении функций теневого копирования в автономную библиотеку, чтобы помочь в написании тестов с использованием некоторых других API, враждебных по отношению к тестам.

Почему вы хотите скрывать свою активность?

9 голосов
/ 25 июля 2013

Это значительно изменилось в Robolectric 2. Вы можете указать пользовательских теней в конфигурации вместо написания собственного TestRunner.

Например:

@Config(shadows = {ShadowAudioManager.class, ShadowContextWrapper.class})
3 голосов
/ 20 декабря 2012

Да, если вы создаете подкласс RobolectricTestRunner, добавьте пользовательский пакет в конструктор и загрузите ваши классы Shadow в методе bindShadowClasses.Не нужно использовать трюк с пакетом 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);
}
2 голосов
/ 28 октября 2014

Возможно, уже поздно, но отсюда: 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");
  }
2 голосов
/ 05 июня 2012

В качестве обновления я смог создавать тени для своих собственных классов, пока я тщательно привязываю класс теней до того, как какой-либо возможный загрузчик воздействует на этот класс. Итак, по инструкции, в 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);
    }
}
...