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 для модульного тестирования.