问题

我有两个要测试的Android类:

  • CommentContentProvider,它扩展了 ContentProvider 并由SQLiteDatabase支持。
  • CommentActivity,它扩展了 Activity 并通过 CommentContentProvider 间接访问ContentResolver

  • 我目前有两个测试类(class):
  • 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 unit testing with ContentProviders和其他有关测试ContentProvider的问题不同,因为它们可以扩展用于测试ContentProvider的android.test类,而我需要扩展用于测试Activity的类。
  • 这类似于How to inject a dependency when testing an Android activity without a third-party framework?,它也是由我发布的,但未得到答复。如果愿意,我现在愿意使用第三方框架。
  • Query using MockContentResolver leads to NullPointerException是相关的,并导致了下面的选项1中的解决方案,但是我不知道这是否是我的最佳解决方案。

  • 可能的解决方案

    如果可以通过以ContentResolver开头的Intent注入(inject)MockContentResolver(可能是RenamingDelegatingContextCommentActivity),那将是很好的选择,但是我不能这样做,因为 Context 不是 Parcelable

    下列哪个选项最好,或者有更好的选择?

    选项1

    在启动IntentCommentActivity中添加一个调试标志:
    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类,该类提供真正的ContentProviderMockContentProvider:
    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;
        }
    }
    

    在生产中,我(通过 Intent )传入了RealContentResolverFactory实例,在测试中,我传入了MockContentResolverFactory实例。由于两者都没有任何状态,因此它们很容易被打包/可序列化。

    我对这种方法的担心是,我不想成为"that guy"在存在更简单的方法时过度使用设计模式。

    选项3

    将以下方法添加到CommentActivity:
    public void static setContentResolver(ContentResolver) {
      :
    }
    

    这比Option 1干净,因为它将ContentResolver的创建放在CommentActivity之外,但是,像Option 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() cannot be called in the setUp( ) method, per the API,因此增加了更多的复杂性。

    另一个不便之处是必须在触摸模式下测试 Activity ,并且setActivityInitialTouchMode(boolean)是在ActivityInstrumentationTestCase2<T>中定义的,但不是在ActivityUnitTestCase<T>中定义的。

    FWIW,我对实现这一目标有些痴迷,因为我将在我正在教授的Android开发类(class)中进行介绍。

    最佳答案

    选项2对我来说似乎最好。我不担心使用工厂。我对远距离引起行为改变的 Intent 感到不安。但是其他解决方案将非生产代码放入生产代码中,因此您要测试的内容与生产中的工作方式不太相似。希望能有所帮助。

    10-08 03:26