使用测试支持库销毁和重启Activity

11
在Android中使用旧的JUnit3风格测试时,我可以执行以下操作来销毁并重新启动一个Activity:
Instrumentation inst = getInstrumentation();
Activity activity = inst.getActivity();
// do something
activity.finish();
Assert.assertTrue(this.activity.isFinishing());
activity = inst.getActivity();
// assert that activity's state is restored

如何使用新的测试支持库来完成相同的事情?我可以使用Espresso和/或UI Automator或新库提供的任何其他机制。

更新:

我尝试了以下方法:

Activity activity = activityTestRule.getActivity();
// do something
activity.finish();
Assert.assertTrue(this.activity.isFinishing());
activity = activityTestRule.getActivity();
// assert that activity's state is restored

然而,似乎 ActivityTestRule.getActivity() 不会重新启动活动。


我认为 finish() 部分应该与以前没有区别。我不知道在 ActivityTestRule 上调用的 getActivity() 是否会重新创建一个已销毁的活动。 - CommonsWare
现在我想起来了,我不太确定您希望在finish()后恢复什么状态。您可以尝试在finish()后调用launchActivity()并查看发生了什么,但那将创建一个全新的实例。或者,您也可以自己添加。Fork ActivityTestRule 后随心所欲地添加! - CommonsWare
@CommonsWare 或许这是一个 XY 问题。最初的动机是测试我的应用程序中以下序列:1. 启动活动。2. 输入一些数据。3. 销毁活动,触发 onSaveInstanceState()。4. 恢复具有先前状态的活动。5. 断言先前输入的数据仍在正确的视图中。--- 那么有更好的方法来测试吗? - Code-Apprentice
2
也许这是一个 XY 问题 -- 嗯,好吧,我是男性,那就是我的染色体对,如果你是指这个的话。 :-) "销毁活动,触发 onSaveInstanceState()" -- 当活动被销毁时不会调用 onSaveInstanceState()。它是因为活动经历了配置更改而被调用的。finish() 不应该触发 onSaveInstanceState()。"那么有更好的方法来测试这个吗?" -- 你可以尝试使用 UIAutomation.setRotation() 然后看看是否会触发 onSaveInstanceState() - CommonsWare
@CommonsWare 什么是XY问题? - Code-Apprentice
显示剩余5条评论
4个回答

8

正如@CommonsWare所提到的,自定义规则非常有用。这是我的简单解决方案(有很多改进的空间,但这只是一个可以构建的快速框架):

public class ControlledActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
    public ControlledActivityTestRule(Class<T> activityClass) {
        super(activityClass, false);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
        super(activityClass, initialTouchMode, true);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
        super(activityClass, initialTouchMode, launchActivity);
    }

    public void finish() {
        finishActivity();
    }

    public void relaunchActivity() {
        finishActivity();
        launchActivity();
    }

    public void launchActivity() {
        launchActivity(getActivityIntent());
    }
}

请注意,如果您按照此方式操作,则该类需要位于包android.support.test.rule中,以访问包私有方法ActivityTestRule#finishActivity。然后,在您的测试用例中实现此规则:
@Rule
public ControlledActivityTestRule<TestFountainPreferenceActivity> actRule = new ControlledActivityTestRule<>(TestFountainPreferenceActivity.class);

在您的个人测试用例中,调用 actRule.finish()actRule.launchActivity() 来杀死和重新启动它。或者,如果您不需要在其间执行任何操作,则可以调用 actRule.relaunchActivity()。请注意,如果您有初始启动,可以将第三个参数传递为 false 以延迟启动活动,然后调用 actRule.launchActivity() 来启动它,但您将失去某些内置句柄的访问权限,例如 #afterActivityLaunched#afterActivityFinished()


没错,#finishActivity是包保护的,所以我使用提供的#finish方法来公开它。 - JCricket
1
有没有办法扩展它,使得重新创建的活动可以从onSaveInstanceState接收Bundle? - MateuszL

2

正如JCricket和djunod所说,实现这一点的方法是创建一个自定义规则。我在他们的解决方案中遇到的问题是finishActivity方法受到包保护,因此您无法在自定义测试规则中使用它。

这是我的解决方案:

public class RelaunchActivityRule<T extends Activity> extends ActivityTestRule<T> {

  public RelaunchActivityRule(Class<T> activityClass) {
    super(activityClass,false);
  }

  public RelaunchActivityRule(Class<T> activityClass, boolean initialTouchMode) {
    super(activityClass, initialTouchMode,true);
  }

  public RelaunchActivityRule(Class<T> activityClass, boolean initialTouchMode,
      boolean launchActivity) {
    super(activityClass, initialTouchMode, launchActivity);
  }

  @Override protected void afterActivityFinished() {
    super.afterActivityFinished();
    launchActivity(getActivityIntent());
  }
}

0

一旦我加了一个Sleep,JCricket的答案就对我起作用了...

package android.support.test.rule;

public class ControlledActivityTestRule<T extends Activity> extends ActivityTestRule<T> {
    public ControlledActivityTestRule(Class<T> activityClass) {
        super(activityClass, false);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
        super(activityClass, initialTouchMode, true);
    }

    public ControlledActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
        super(activityClass, initialTouchMode, launchActivity);
    }

    public void finish() {
        finishActivity();
    }

    public void relaunchActivity(int seconds) {
        finishActivity();
        sleep(seconds);
        launchActivity();
        sleep(seconds);
    }

    public void launchActivity() {
        launchActivity(getActivityIntent());
    }

    public void sleep(int seconds) {
        if (seconds > 0) {
            try {
                Thread.sleep(seconds * 1000);
            } catch (Exception ex) {
            }
        }
    }
}

然后我能够进行迭代测试:

@Rule
public ControlledActivityTestRule<MainActivity> mActivityRule = new ControlledActivityTestRule<>(MainActivity.class);

@Test
public void testOAA310() {
    int count = 1000;
    for (int i = 0; i < count; i++) {
        testCaseOAA310();
        mActivityRule.relaunchActivity(5);
    }
}

void testCaseOAA310() { /* ... blah blah blah... */ }

为什么testOAA310()会调用自身?这似乎是无限递归。 - Code-Apprentice
testOAA310 调用 testCaseOAA310... 不是调用自身。 - djunod
这些方法名称有什么区别? - Code-Apprentice
OIC...test和testCase之间的区别似乎很差,因为对我来说并不是显而易见的。 - Code-Apprentice
1
只是出于好奇,你需要放一个睡眠是因为什么失败了?虽然在测试中不是很可怕,但我讨厌放睡眠,因为它会破坏适当测试场景的信任。我最初遇到一些同步问题,但通常追溯到一些其他编码错误,留下了应该被销毁的活动。 - JCricket
我相信 getInstrumentation().waitForIdleSync(); 是 sleep 的更好替代方案。 - MateuszL

0
尝试创建自定义规则。在afterActivityFinished方法中调用launchActivity(getActivityIntent()),它将在完成后重新启动活动。
public class RestartActivityRule<T extends Activity> extends ActivityTestRule<T> {

    public RestartActivityRule(Class<T> activityClass) {
        super(activityClass,false);
    }

    public RestartActivityRule(Class<T> activityClass, boolean initialTouchMode) {
        super(activityClass, initialTouchMode,true);
    }

    public RestartActivityRule(Class<T> activityClass, boolean initialTouchMode,
      boolean launchActivity) {
        super(activityClass, initialTouchMode, launchActivity);
    }

    @Override protected void afterActivityFinished() {
        super.afterActivityFinished();
        launchActivity(getActivityIntent());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接