Android Studio:无法在插桩测试中写入共享首选项

Android Studio: Cannot write to Shared Preferences in instrumented test

我正在尝试编写一个测试用例来验证写入共享首选项的 class。 我正在使用 Android Studio v1.5.

在旧的 eclipse 中,当使用 AndroidTestCase 时,第二个 apk 文件被部署到设备,测试可以 运行 使用检测上下文,所以你可以 运行 在不更改主 apk 现有共享首选项文件的情况下使用检测 apk 的共享首选项进行测试。

我整个上午都在试图弄清楚如何在 Android Studio 测试中获取非空上下文。显然,为 eclipse 进行的单元测试与 Android Studio 测试框架不兼容,因为调用 getContext() returns null。 我以为我已经找到了这个问题的答案: Get context of test project in Android junit test case

随着时间的推移,情况发生了变化,因为 Android Studio 的旧版本没有完整的测试支持。所以很多答案只是黑客。显然现在不是扩展 InstrumentationTestCaseAndroidTestCase 你应该这样写你的测试:

@RunWith(AndroidJUnit4.class)
public class MyTest {

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

所以我现在有一个非空的检测上下文,getSharedPreferences 方法 returns 一个似乎有效的实例,但实际上没有写入首选项文件。

如果我这样做:

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

据我所知,运行测试后还没有测试apk上传到设备。这可以解释为什么文件不是使用检测上下文编写的。有没有可能这个上下文是一个无提示失败的假上下文?

如果是这种情况,我如何才能获得真正的检测上下文,以便我可以在不更改主项目首选项的情况下编写首选项

请注意,您仍然可以使用 Android Studio 来 运行 扩展 InstrumentationTestCaseAndroidTestCaseActivityInstrumentationTestCase2 的旧式测试 Android工作室。

新的测试支持库提供 ActivityTestRule to provide functional testing of a single activity. I believe you need to create one of these objects before attempting to get the Instrumentation and/or Context used for the test. The documentation for Espresso 有使用此 class 的示例。

如果您想测试 类 而不必创建 Activity,我发现使用 Robolectric 最简单。 Robolectric 为您提供了一个模拟上下文,它可以完成上下文所做的一切。事实上,这个上下文是我使用 Robolectric 进行单元测试的主要原因。

工具将与您的应用程序一起安装。应用程序将 运行 本身,从而读取和写入它自己的 SharedPreferences.

奇怪的是,InstrumentationSharedPreferences 被删除(或从未创建),但即使创建了它们,您也很难将它们传递到您的应用程序中正在测试中。如上所述,仅在您的应用程序内部调用 context.getSharedPreferences(); 仍会提供实际的应用程序首选项,而不是您的工具。

您需要找到一种方法来为您的被测应用程序提供首选项。一个好的解决方案是将首选项保留在 Application 中,如下所示:

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

    // add public getter / setter
}

这样可以

  1. 只要您有上下文,就可以使用 ((App) context.getApplicationContext()).getPreferences()
  2. 从单一来源获取偏好
  3. 运行进行测试并开始任何活动以注入测试数据之前,自行设置首选项。

然后在您的测试设置中调用以下命令以注入您需要的任何首选项

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

确保每次测试后正确完成活动,以便每个测试都能获得自己的依赖项。

此外,您应该认真考虑使用 Mockito 和其他框架来模拟 SharedPreferences,或者自己简单地实现 SharedPreferences 接口,因为这大大简化了与模型的交互验证。

事实证明您无法使用检测上下文写入共享首选项,即使在 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() 也是如此(您可以使用调试器检查编辑器正在写入哪个文件,只需在 this.[=14=] 成员实例中查找名为 mFile 的私有变量。

所以我认为这实际上是可能的是错误的。我虽然过去曾使用检测上下文进行数据访问层测试,但实际上我们使用了主上下文 (AndroidTestCase.getContext()),尽管我们对首选项和 SQLite 文件使用了不同的名称。这就是为什么单元测试没有修改常规应用程序文件的原因。