ПРОБЛЕМА
У меня есть два класса Android, которые я хочу протестировать:
CommentContentProvider
, который расширяетContentProvider
и поддерживается SQLiteDatabase.CommentActivity
, который расширяетActivity
и обращается кCommentContentProvider
косвенно черезContentResolver
.
В настоящее время у меня есть два тестовых класса:
CommentContentProviderTest
, который расширяетProviderTestCase2<CommentContentProvider>
и используетMockContentResolver
. Это прекрасно работает.CommentActivityTest
, который расширяетActivityInstrumentationTestCase2<CommentActivity>
. Это работает нормально, за исключением частейCommentActivity
, которые обращаются кCommentContentProvider
.
Проблема в том, что когда CommentActivity
обращается к CommentContentProvider
, он делает это через стандартный ContentResolver
:
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver().query(...);
Таким образом, когда запускается CommentActivityTest
, он запускает CommentActivity
, который осуществляет доступ (чтение и запись) к производственной базе данных, как показано в двух строках выше.
Мой вопрос заключается в том, как заставить CommentActivity
использовать стандартный ContentResolver
в производстве, но MockContentResolver
во время тестирования.
СВЯЗАННЫЕ ВОПРОСЫ
- Это отличается от модульного тестирования Android с ContentProviders и других вопросов, которые я нашел о тестировании ContentProviders, потому что они могут расширять классы android.test, предназначенные для тестирования ContentProviders, в то время как мне нужно расширить класс для тестирования
Activity
. - Это похоже на Как внедрить зависимость при тестировании активности Android без стороннего фреймворка?, который также был опубликован мной, но остался без ответа. Теперь я готов использовать стороннюю структуру, если это поможет.
- Запрос с использованием MockContentResolver приводит к исключению NullPointerException и приводит к решению в варианте 1 ниже, но я не знаю, лучшее ли это решение в моем случае.
ВОЗМОЖНЫЕ РЕШЕНИЯ
Было бы неплохо, если бы я мог внедрить ContentResolver
(возможно, MockContentResolver
или RenamingDelegatingContext
) через Намерение, которое запускает CommentActivity
, но я не могу этого сделать, так как Context
не являются Parcelable
.
Какой из следующих вариантов лучше, или есть лучший вариант?
ВАРИАНТ 1
Добавьте флаг отладки в Intent
, который запускает CommentActivity
:
public class CommentActivity extends Activity {
public static final String DEBUG_MODE = "DEBUG MODE";
private ContentResolver mResolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
:
// If the flag is not present, debugMode will be set to false.
boolean debugMode = getIntent().getBooleanExtra(DEBUG_MODE, false);
if (debugMode) {
// Set up MockContentResolver or DelegatingContextResolver...
} else {
mResolver = getContentResolver();
}
:
}
Мне не нравится этот вариант, потому что я не люблю размещать код, связанный с тестами, в своих нетестовых классах.
ВАРИАНТ 2
Используйте шаблон абстрактной фабрики, чтобы передать класс Parcelable
, который предоставляет реальный ContentProvider
или MockContentProvider
:
public class CommentActivity extends Activity {
public static final String FACTORY = "CONTENT RESOLVER FACTORY";
private ContentResolver mResolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
:
ContentResolverFactory factory = getIntent().getParcelableExtra(FACTORY);
mResolver = factory.getContentResolver(this);
:
}
где у меня также есть:
public abstract class ContentResolverFactory implements Parcelable {
public abstract ContentResolver getContentResolver(Context context);
}
public abstract class RealContentResolverFactory extends ContentResolverFactory
public ContentResolver getContentResolver(Context context) {
return context.getContextResolver();
}
}
public abstract class MockContentResolverFactory extends ContentResolverFactory
public ContentResolver getContentResolver(Context context) {
MockContentResolver resolver = new MockContentResolver();
// Set up MockContentResolver...
return resolver;
}
}
В рабочей среде я передаю (через намерение) экземпляр RealContentResolverFactory
, а в тесте я передаю экземпляр MockContentResolverFactory
. Поскольку ни у одного из них нет состояния, они легко Parcelable/Serializable.
Меня беспокоит этот подход, потому что я не хочу быть "этим парнем" кто злоупотребляет шаблонами проектирования, когда существуют более простые подходы.
ВАРИАНТ 3
Добавьте следующий метод в CommentActivity
:
public void static setContentResolver(ContentResolver) {
:
}
Это чище, чем вариант 1, поскольку он помещает создание ContentResolver
вне CommentActivity
, но, как и вариант 1, требует изменения тестируемого класса.
ВАРИАНТ 4
Пусть CommentActivityTest
расширит ActivityUnitTestCase<CommentActivity>
вместо ActivityInstrumentationTestCase2<CommentActivity>
. Это позволяет мне установить контекст CommentActivity
через setActivityContext()
а>. Контекст, который я передаю, переопределяет обычный getContentResolver()
для использования MockContentResolver
(который я инициализирую в другом месте).
private class MyContext extends RenamingDelegatingContext {
MyContext(Context context) {
super(context, FILE_PREFIX);
}
@Override
public ContentResolver getContentResolver() {
return mResolver;
}
}
Это работает и не требует модификации тестируемого класса, но добавляет сложности, поскольку ActivityUnitTestCase<CommentActivity>.startActivity()
нельзя вызывать в методе setUp(
) согласно API.
Еще одно неудобство заключается в том, что действие должно быть протестировано в сенсорном режиме, и setActivityInitialTouchMode(boolean) определен в ActivityInstrumentationTestCase2<T>
, но не ActivityUnitTestCase<T>
.
FWIW, я немного одержим тем, чтобы сделать это правильно, потому что я буду представлять это на уроке разработки Android, который я преподаю.