Как да разберете дали приложението за Android изпълнява UI тест с Espresso

Пиша някои Espresso тестове за Android. Работя със следния проблем:

За да може определен тестов случай да работи правилно, трябва да деактивирам някои функции в приложението. Следователно в моето приложение трябва да открия дали изпълнявам тест за еспресо, за да мога да го деактивирам. Въпреки това не искам да използвам 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 (нестабилен за видимост) трябва да е достатъчен и напълно безопасен за нишки, нали? - person Danilo Bargen; 17.03.2021

Комбинирането на Commonsware коментар + решението на Comtaler ето начин да го направите за всеки тестов клас, използвайки Espresso framework.

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.Еспресо за гръб - person Dominik Suszczewicz; 25.10.2018

предпочитам да не използвам отражение, което е бавно на android. Повечето от нас имат dagger2, настроен за инжектиране на зависимости. Имам тестов компонент, настроен за тестване. Ето кратък начин, по който можете да получите режим на приложение (тестващ или нормален):

създайте enum:

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);
    }
}

не забравяйте да го декларирате в gradle така:

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

Сега създайте подклас на метода AppModule с override, който изглежда точно така и не го маркирайте като модул над дефиницията на класа:

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;

така че когато изпълнявате тестове за еспресо, ще се тества enum, но когато е в производствен код, ще бъде нормално enum.

ps не е необходимо, но ако трябва да видите как моята производствена кама изгражда графиката, тя е подобна на тази и е декларирана в подклас на приложение:

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

Ще създам два файла като по-долу

src/main/.../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. Трябва да промените името на опаковката на Еспресо.

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