Как определить, выполняет ли приложение Android тест пользовательского интерфейса с помощью Espresso

Я пишу несколько тестов Espresso для Android. Я работаю со следующей проблемой:

Чтобы определенный тестовый пример работал правильно, мне нужно отключить некоторые функции в приложении. Поэтому в моем приложении мне нужно определить, запускаю ли я тест Espresso, чтобы я мог его отключить. Однако я не хочу использовать BuildConfig.DEBUG, потому что я не хочу, чтобы эти функции были отключены в отладочной сборке. Кроме того, я хотел бы избежать создания нового buildConfig, чтобы избежать создания слишком большого количества вариантов сборки (у нас уже определено много разновидностей).

Я искал способ определить buildConfigField для теста, но не смог найти ссылку в Google.


person Comtaler    schedule 16.02.2015    source источник
comment
Одним из хакерских решений является Class.forName() проверка наличия кода тестирования в виртуальной машине: wtanaka.com/node/8041   -  person CommonsWare    schedule 17.02.2015
comment
Вы решили эту проблему?   -  person Marcus    schedule 21.02.2015
comment
да. Я собираюсь опубликовать свое решение   -  person Comtaler    schedule 24.02.2015


Ответы (9)


В сочетании с комментарием CommonsWare. Вот мое решение:

Я определил переменную AtomicBoolean и функцию, чтобы проверить, выполняется ли тест:

private AtomicBoolean isRunningTest;

public synchronized boolean isRunningTest () {
    if (null == isRunningTest) {
        boolean istest;

        try {
            Class.forName ("myApp.package.name.test.class.name");
            istest = true;
        } catch (ClassNotFoundException e) {
            istest = false;
        }

        isRunningTest = new AtomicBoolean (istest);
    }

    return isRunningTest.get ();
}

Это позволяет избежать выполнения проверки try-catch каждый раз, когда вам нужно проверить значение, и запускает проверку только при первом вызове этой функции.

person Comtaler    schedule 24.02.2015
comment
Как насчет использования логического (не логического) объекта вместо класса AtomicBoolean? - person Paul; 27.07.2017
comment
Это использование AtomicBoolean не является потокосохраняющим. Вам нужно использовать сеттеры одного финального AtomicBoolean вместо проверки null и создания нового. В настоящее время это так же безопасно, как и использование обычного boolean. - person ByteHamster; 27.05.2018
comment
Но если метод синхронизирован, то volatile Boolean (volatile для видимости) должно быть достаточным и совершенно потокобезопасным, верно? - person Danilo Bargen; 17.03.2021

Комбинируя комментарий Commonsware + решение Comtaler, вы можете сделать это для любого тестового класса, используя платформу Espresso.

private static AtomicBoolean isRunningTest;

public static synchronized boolean isRunningTest () {
    if (null == isRunningTest) {
        boolean istest;

        try {
            Class.forName ("android.support.test.espresso.Espresso");
            istest = true;
        } catch (ClassNotFoundException e) {
            istest = false;
        }

        isRunningTest = new AtomicBoolean (istest);
    }

    return isRunningTest.get();
}
person Ryhan    schedule 28.10.2015
comment
Еще один момент, после перехода на androidX должно быть -> Class.forName("androidx.test.espresso.Espresso") - person walkmn; 29.10.2019

Как насчет флага в классе BuildConfig?

android {
    defaultConfig {
        // No automatic import :(
        buildConfigField "java.util.concurrent.atomic.AtomicBoolean", "IS_TESTING", "new java.util.concurrent.atomic.AtomicBoolean(false)"
    }
}

Добавьте это где-нибудь в свои тестовые классы.

static {
    BuildConfig.IS_TESTING.set(true);
}
person KenIchi    schedule 09.01.2019
comment
Это помогло мне избежать написания большого количества кода только для одной строки, которая должна отличаться при тестировании. - person sparkly_frog; 14.05.2019

Основываясь на ответах выше, следующий код Kotlin эквивалентен:

val isRunningTest : Boolean by lazy {
    try {
        Class.forName("android.support.test.espresso.Espresso")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

Затем вы можете проверить значение свойства:

if (isRunningTest) {
  // Espresso only code
}
person David Pacheco    schedule 24.10.2016
comment
androidx.test.espresso.Espresso для реактивной спины - person Dominik Suszczewicz; 25.10.2018

я предпочитаю не использовать отражение, которое медленно работает на Android. У большинства из нас есть dagger2, настроенный для внедрения зависимостей. У меня есть тестовый компонент, настроенный для тестирования. Вот краткий способ получить режим приложения (тестовый или обычный):

создать перечисление:

public enum ApplicationMode {
    NORMAL,TESTING;
}

и обычный AppModule:

@Module
public class AppModule {

    @Provides
    public ApplicationMode provideApplicationMode(){
        return ApplicationMode.NORMAL;
    }
}

создайте тестовый бегун, как я:

public class PomeloTestRunner extends AndroidJUnitRunner {

    @Override
    public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            return super.newApplication(cl, MyTestApplication.class.getName(), context);
    }
}

не забудьте объявить это в градле следующим образом:

defaultConfig {
testInstrumentationRunner "com.mobile.pomelo.base.PomeloTestRunner"
}

Теперь создайте подкласс AppModule с методом переопределения, который выглядит точно так же, и не помечайте его как модуль над определением класса:

public class TestAppModule extends AppModule{

    public TestAppModule(Application application) {
        super(application);
    }

    @Override
    public ApplicationMode provideApplicationMode(){
        return ApplicationMode.TESTING; //notice we are testing here
    }
}

теперь в вашем классе MyTestApplication, который вы объявили в пользовательском тестировщике, объявлено следующее:

public class PomeloTestApplication extends PomeloApplication {

    @Singleton
    @Component(modules = {AppModule.class})
    public interface TestAppComponent extends AppComponent {
        }

    @Override
    protected AppComponent initDagger(Application application) {
        return DaggerPomeloTestApplication_TestAppComponent.builder()
                .appModule(new TestAppModule(application)) //notice we pass in our Test appModule here that we subclassed which has a ApplicationMode set to testing
                .build();
    }
}

Теперь, чтобы использовать его, просто вставьте его в производственный код, например:

@Inject
    ApplicationMode appMode;

поэтому, когда вы запускаете тесты эспрессо, он будет тестировать перечисление, но в производственном коде это будет обычное перечисление.

ps не обязательно, но если вам нужно посмотреть, как мой производственный кинжал строит график, он выглядит так и объявлен в подклассе приложения:

 protected AppComponent initDagger(Application application) {
        return DaggerAppComponent.builder()
                .appModule(new AppModule(application))
                .build();
    }
person j2emanue    schedule 14.11.2017

Я создам два файла, как показано ниже

источник/основной/.../Injection.java

src/androidTest/.../Injection.java

А в Injection.java я буду использовать другую реализацию или просто статическую переменную.

Поскольку androidTest является исходным набором, а не частью типа сборки, я думаю, что то, что вы хотите сделать, сложно.

person Nevin Chen    schedule 27.02.2018
comment
Я сомневаюсь, что это вызовет ошибку сборки при тестировании, если 2 файла были помещены в один и тот же пакет... - person KenIchi; 26.02.2019

Если вы используете JitPack с kotlin. Вам нужно изменить название пакета Espresso .

val isRunningTest : Boolean by lazy {
    try {
        Class.forName("androidx.test.espresso.Espresso")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

Для проверки

if (isRunningTest) {
  // Espresso only code
}
person hemantsb    schedule 02.07.2019
comment
Для форматирования: выберите код и нажмите на иконку {} в редакторе - person adiga; 02.07.2019

Для этого вы можете использовать SharedPreferences.

Установить режим отладки:

boolean isDebug = true;

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("DEBUG_MODE", isDebug);
editor.commit();

Проверьте, если режим отладки:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
boolean isDebug = sharedPref.getBoolean("DEBUG_MODE", false);

if(isDebug){
    //Activate debug features
}else{
    //Disable debug features
}
person Marcus    schedule 16.02.2015
comment
Не отвечает на вопрос. - person vvbYWf0ugJOGNA3ACVxp; 19.10.2018

Вот способ адаптировать принятое решение для Android-приложения, ориентированного на реакцию.

// MainActivity.java

// ...

  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
    return new ReactActivityDelegate(this, getMainComponentName()) {

      // ...

      @Override
      protected Bundle getLaunchOptions() {
        Bundle initialProperties = new Bundle();
        boolean testingInProgress;

        try {
          Class.forName ("androidx.test.espresso.Espresso");
          testingInProgress = true;
        } catch (ClassNotFoundException e) {
          testingInProgress = false;
        }

        initialProperties.putBoolean("testingInProgress", testingInProgress);

        return initialProperties;
      }
    };
  }
}

Затем вы сможете получить доступ к testingInProgress в качестве реквизита, предоставленного вашему самому верхнему компоненту (обычно App.js). Оттуда вы можете использовать componentDidMount или эквивалент для доступа к нему и добавить его в свой магазин Redux (или что вы используете), чтобы сделать его доступным для остальной части вашего приложения.

Мы используем это, чтобы активировать некоторую логику в нашем приложении, чтобы помочь нам делать снимки экрана с помощью Fastlane.

person Pend    schedule 23.12.2020