【问题标题】:How to get new instance of the my Application class per each unit test?如何为每个单元测试获取我的 Application 类的新实例?
【发布时间】:2016-10-12 08:00:00
【问题描述】:

我有一个 Android 应用,它具有继承自 ApplicationMyApplication 类。

我创建了几个使用@RunWith(AndroidJUnit4.class) 运行的单元测试。如果我分别运行每个测试,它们都会通过。如果我一起运行它们 - 第一个通过,然后(其中一些)其他失败。

问题在于,似乎只创建了一个 MyApplication 实例,然后将其保留并用于导致失败的所有测试,因为 MyApplication 中有一个状态必须只初始化一次.

有没有办法运行单元测试 (androidTest),以便为每个测试重新启动应用程序?我不在乎它是否会很慢(例如,每次都必须重新安装应用程序)我只想让测试彼此独立运行。

单元测试的实际代码如下所示(根据@Zinc 的要求):

@RunWith(AndroidJUnit4.class)
public class AutoLogin_ActMainTest {
    @Rule
    public ActivityTestRule<ActMain> mActivityRule = new ActivityTestRule<ActMain>(
            ActMain.class) {


        @Override
        protected void beforeActivityLaunched() {
            super.beforeActivityLaunched();

            MyTestApp app = (MyTestApp) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
            DependencyInjector.reset();
            app.reset();


            FakeUnitDaggerModule fudm = new FakeUnitDaggerModule();

            Session session = new SessionImpl(new TimeProviderImpl());
            fudm.setResMain(new ResMainTest(session));

            FakeAppPrefs appPrefs = new FakeAppPrefs();
            FakeLoginPrefs loginPrefs = new FakeLoginPrefs();
            CurrentUserHolder currentUserHolder = new CurrentUserHolder();

            FakeComponent inj = DaggerFakeComponent.builder().
                    fakeMyAppDaggerModule(new FakeMyAppDaggerModule(app, appPrefs, loginPrefs, currentUserHolder)).
                    appInfoDaggerModule(new AppInfoDaggerModule("1")).
                    fakeSessionDaggerModule(new FakeSessionDaggerModule(session)).
                    fakeExchangeDaggerModule(new FakeExchangeDaggerModule("https://test.com")).
                    fakeUnitDaggerModule(fudm).
                    build();

            DependencyInjector.init(inj);
            DependencyInjector.getInstance().inject(app);


            app.onStart();
        }
    };


    @Test
    public void testAutoLogin() {
        ElapsedTimeIdlingResource idlingResource = new ElapsedTimeIdlingResource(500);
        Espresso.registerIdlingResources(idlingResource);
        idlingResource.startWaiting();

        onView(ViewMatchers.withId(R.id.tv_logged_in_as)).check(matches(isDisplayed()));
        Espresso.unregisterIdlingResources(idlingResource);
    }
}

【问题讨论】:

  • 你能分享你的代码吗?
  • @Zinc 添加了其中一项测试的代码
  • 你为什么不使用 degger ?
  • @Saveen degger?什么意思?
  • @Ognyan dagger 用于依赖注入从这里查看square.github.io/dagger

标签: android unit-testing


【解决方案1】:

问题在于,似乎只创建了一个 MyApplication 实例,然后将其保留并用于导致失败的所有测试,因为 MyApplication 中有一个状态必须只初始化一次。

恕我直言,这是应用程序中的错误,应该修复。 Application 不适合很多真正的业务逻辑(尽管它可以用于初始化你的崩溃报告库,StrictMode 等)。其他所有东西都应该可以单独测试,可以直接、通过模拟、通过依赖注入等。

话虽如此,有时问题并不在于您控制的代码,而是来自库或框架的代码。

有没有办法运行单元测试 (androidTest) 以便为每个测试重新启动应用程序?

现在,是的,尽管当时没有提出这个问题。 Android Test Orchestrator (ATO) 在隔离每个测试方面做得更好,但代价是测试执行速度。

【讨论】:

  • app 类中唯一的代码是依赖注入器(Dagger 2)的初始化。这个 ATO 看起来正是我正在寻找的,但我有这样的担忧:在其文档中指出:“因此,如果您的测试共享应用程序状态,则该共享状态的 大部分将从您的设备中删除每次测试后的 CPU 或内存”。 “最”这个词是问题 - 你知道什么没有被删除,或者至少我可以在哪里找到 ATO 的代码以便我自己查看它?
  • @Ognyan:“你知道什么没有被删除吗”——当我让 ATO 完成它的步伐时,它似乎为每个测试创建了新的流程。我不知道文档的那部分指的是什么。 “我在哪里可以找到 ATO 的代码以便自己查看?” -- 不是我的头,对不起。
  • 如果有人感兴趣:未删除的一件事是在公共目录(即getExternalFilesDir())中创建的文件。仍然无法测试是否保留了私有目录中的文件,但如果是这种情况,您必须特别注意隐藏文件的创建,例如 http 缓存。另一个陷阱可能是相机、GPS 等“外部”设备可能需要在每次测试之间手动重置。
  • ATO 的另一个缺点是,到目前为止,我无法找到一种方法来标记哪些测试需要Instrumentation 的新实例,哪些不需要,因此迫使我做出全有或全无的选择(从我的角度来看,这使得 ATO 几乎无法使用——我需要它进行不到 5% 的测试。对于中型应用程序,这意味着在 ATO 中执行所有测试将花费很长时间,而不是提到 CI 服务器可能会过载)。
【解决方案2】:

您需要重构您的应用程序,以便控制状态的任何代码都不会绑定到应用程序类,而是绑定到另一个对象中。然后,您可以重置该部分或将其模拟出来,而无需关心应用程序类的持久性。最好使用某种形式的依赖注入来完成。

【讨论】:

  • 另一个对象并不是真正的解决方案,因为它只是将问题从应用程序转移到该对象(我已经对问题和包括 DI 在内的不同解决方案进行了很多尝试)。我知道 Google 建议“通常不需要继承 Application。在大多数情况下,静态单例可以提供相同的功能”。目前我在每次测试开始时重置应用程序状态,但这是一个 hack。我希望有一种真正的方法来告诉测试每次都使用新的应用程序运行。诸如 gradle 设置甚至 SDK 调用之类的东西。
【解决方案3】:

我不太确定你的问题。但是您可以使用 ApplicationTestCase。比如:

public class MyApplicationTest extends ApplicationTestCase<MyTestApp> {
    public void test1() {
        createApplication();

        ... test here ...

        terminateApplication();
    }

    public void test2() {
        createApplication();

        ... test here ...

        terminateApplication();
    }
}

参考:https://developer.android.com/reference/android/test/ApplicationTestCase.html

【讨论】:

  • 我恐怕不能使用ApplicationTestCase,我需要规则的beforeActivityLaunched()ApplicationTestCase 也已被弃用...
  • ApplicationTestCase 没有被弃用?
【解决方案4】:
    public class TestApplication extends Application {

@Override
public void onCreate() {
    super.onCreate();
    // Sdk.terminate(); - If you specify TestApplication as an 
    //                    application class in AndroidManifest, 
    //                    you'll have to uncomment this(due to issue with test runner)
    Sdk.initialize();
}

@Override
public void onTerminate() {
    super.onTerminate();
    Sdk.terminate();
}
}

SDK类

    public class Sdk {

private static Sdk sInstance;
private void Sdk(){
}

public static Sdk getInstance() throws RuntimeException {
    if (sInstance == null) {
        throw new RuntimeException();
    }
    return sInstance;
}

public static void terminate() {
    sInstance = null;
}

public static void initialize() {
    if (sInstance == null) {
        sInstance = new Sdk();
        //save some information according to what is on the default configurations
    } else {
        throw new RuntimeException("Method was already initialized");
    }
}}

测试:

    public class MyApplicationTest extends ApplicationTestCase<TestApplication> {

public MyApplicationTest() {
    super(TestApplication.class);
}

public void testMultiplicationTests() {
    createApplication();

    int answer = 42;
    assertEquals(42, answer);

    terminateApplication();
}


public void testDefaultSettings() {
    createApplication();

    assertNotNull(Sdk.getInstance());

    terminateApplication();
}}

【讨论】:

  • 正如我在另一条评论中提到的,ApplicationTestCase 已被弃用,我真的需要使用新的ActivityTestRule。否则解决方案似乎可以解决问题(我现在正在使用类似的方法)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-07
  • 2015-01-16
  • 1970-01-01
  • 2016-05-23
  • 2013-04-17
  • 1970-01-01
相关资源
最近更新 更多