Android Studio: нельзя записывать в общие настройки в инструментальном тесте

Я пытаюсь написать тестовый пример, чтобы проверить класс, который записывает в Shared Preferences. Я использую Android Studio v1.5.

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

Я потратил все утро, пытаясь понять, как получить ненулевой контекст в тестах Android Studio. Очевидно, модульные тесты, сделанные для eclipse, несовместимы с платформой тестирования Android Studio, так как вызов getContext() возвращает null. Я думал, что нашел ответ в этом вопросе: Получить контекст тестового проекта в тестовом примере Android junit

Все изменилось со временем, поскольку старые версии Android Studio не имели полной поддержки тестирования. Так много ответов - это просто хаки. По-видимому теперь вместо расширения InstrumentationTestCase или AndroidTestCase вы должны написать свои тесты следующим образом:

@RunWith(AndroidJUnit4.class)
public class MyTest {

    @Test
    public void testFoo(){
        Context instrumentationContext = InstrumentationRegistry.getContext();
        Context mainProjectContext = InstrumentationRegistry.getTargetContext();            
    }   
}

Итак, теперь у меня есть не нулевой контекст инструментария, а метод getSharedPreferences возвращает экземпляр, который, похоже, работает, но на самом деле нет файл настроек.

Если я это сделаю:

context = InstrumentationRegistry.getContext();      

Затем редактор SharedPreferences записывает и фиксирует правильно, и исключение не генерируется. При ближайшем рассмотрении я вижу, что редактор пытается записать в этот файл:

data/data/<package>.test/shared_prefs/PREFS_FILE_NAME.xml

Но файл никогда не создается и не записывается.

Однако используя это:

context = InstrumentationRegistry.getTargetContext(); 

редактор работает правильно, и настройки записываются в этот файл:

/data/data/<package>/shared_prefs/PREFS_FILE_NAME.xml

Предпочтения создаются в частном режиме:

SharedPreferences sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);

Насколько я знаю, после запуска теста на устройство не было загружено тестовое приложение. Это может объяснить, почему файл не был написан с использованием контекста инструментария. Возможно ли, что этот контекст является фальшивым контекстом, который терпит неудачу?

И если это так, , как я могу получить контекст REAL инструментария, чтобы я мог писать настройки без изменения основных настроек проекта?

Ответы

Ответ 1

Оказывается, вы не можете писать в общие настройки, используя контекст инструментария, и это было верно даже в eclipse. Это было бы эквивалентным тестом для eclipse:

import android.content.Context;
import android.content.SharedPreferences;
import android.test.InstrumentationTestCase;

public class SharedPrefsTest extends InstrumentationTestCase {

    public void test() throws Exception { 
        Context context = getInstrumentation().getContext();
        String fileName = "FILE_NAME";

        SharedPreferences sharedPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putString("key", "value");
        editor.commit();

        SharedPreferences sharedPreferences2 = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
        assertEquals("value", sharedPreferences2.getString("key", null));
    }
}

Я просто запускал его, и он тоже терпел неудачу. Настройки никогда не записываются. Я думаю, что доступ к внутреннему файлу хранилища запрещен в этом контексте, так как вызов Context.getFilesDir() вызывает InvocationTargetException и вызывает вызов File.exists() по файлу настроек (вы можете проверить, какой файл является редактором, записывающим на использование отладчика, просто найдите приватная переменная, называемая mFile внутри экземпляра члена this.$0).

Поэтому я ошибался, думая, что это действительно возможно. Хотя я использовал контекст инструментария для тестирования уровня доступа к данным в прошлом, но мы фактически использовали основной контекст (AndroidTestCase.getContext()), хотя мы использовали разные имена для предпочтений и файлов SQLite. И поэтому модульные тесты не изменяли обычные файлы приложений.

Ответ 2

Инструментарий будет установлен вместе с вашим приложением. Приложение будет работать само по себе, таким образом, считывая и записывая собственный SharedPreferences.

Нечетно, что SharedPreferences из Instrumentation удаляется (или никогда не создается), но даже если они будут созданы, вам будет сложно переносить их в тестируемое приложение. Как указано выше, просто вызов context.getSharedPreferences(); внутри вашего приложения будет по-прежнему предоставлять фактические настройки приложений, а не те, которые вам нужны.

Вам нужно будет найти способ предоставления предпочтений вашему приложению под тестированием. Хорошим решением для этого было бы сохранить предпочтения в вашем Application следующим образом:

public class App extends Application {
    SharedPreferences mPreferences;
    public void onCreate() {
        mPreferences = getSharedPreferences(fileName, Context.MODE_PRIVATE);
    }

    // add public getter / setter
}

Таким образом, вы можете

  • Пока у вас есть контекст, вы получаете предпочтения из одного источника, используя ((App) context.getApplicationContext()).getPreferences()
  • Задайте настройки самостоятельно перед запуском тестов и начните любые действия для ввода тестовых данных.

В вашей тестовой настройке затем вызовите следующее, чтобы ввести любые требуемые настройки

@Before
public void before() {
    ((App) InstrumentationRegistry.getTargetContext()).setPreferences(testPreferences);
}

Обязательно правильно завершить действия после каждого теста, чтобы каждый тест мог получить свои собственные зависимости.

Кроме того, вы должны серьезно подумать о том, чтобы просто издеваться над SharedPreferences с помощью Mockito, других фреймворков или просто реализовать интерфейс SharedPreferences самостоятельно, поскольку это значительно упрощает проверку взаимодействий с моделями.

Ответ 3

Обратите внимание, что вы все равно можете использовать Android Studio для запуска тестов старого стиля, которые расширяют InstrumentationTestCase, AndroidTestCase или ActivityInstrumentationTestCase2 в Android Studio.

Новая библиотека поддержки тестирования предоставляет ActivityTestRule, чтобы обеспечить функциональное тестирование одного действия. Я считаю, что вам нужно создать один из этих объектов, прежде чем пытаться получить Instrumentation и/или Context, используемые для теста. В документации для Espresso приведен пример использования этого класса.

Ответ 4

Если вы хотите тестировать классы без необходимости создания Activity, я нашел проще всего использовать Robolectric. Robolectric предоставляет вам фиктивный контекст, который делает все, что делает контекст. Фактически, этот контекст является основной причиной использования Robolectric для модульного тестирования.