使用仪器和JUnit4重新创建Android Activity的测试

10

我想为重新创建活动编写测试。旋转是可选的。

我希望使用Google赞助的最新版本测试框架"Blessed"编写测试。我是测试编写的新手,因此我想学习基本的、主流的、得到良好支持的工具。在我掌握基础知识之后,任何第三方测试框架都可以。而且由于我想测试非常基础、频繁发生的情况,所以基本工具应该足够了,对吧?

最小化测试代码:

public class MainActivity extends AppCompatActivity {

    static int creationCounter = 0;
    Integer instanceId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ++creationCounter;
        instanceId = new Integer(creationCounter);
        Log.d("TEST", "creating activity " + this + ", has id " + instanceId);
    }
}

还有测试类:

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void useAppContext() throws Exception {

        MainActivity activity1 = mActivityTestRule.getActivity();
        int act1 = activity1.instanceId.intValue();
        int counter1 = MainActivity.creationCounter;
        assertEquals(1, counter1);
        assertEquals(1, act1);


        Log.d("TEST", "requesting rotation");
        // method 1
         activity1.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        // method 2 //https://gist.github.com/nbarraille/03e8910dc1d415ed9740#file-orientationchangeaction-java
        // onView(isRoot()).perform(orientationLandscape());

        getInstrumentation().waitForIdleSync(); // I thought this should suffice
        // How to do this?
        //somehowRefreshActivityInstanceInsideTestRule();

        MainActivity activity2 = mActivityTestRule.getActivity();
        int act2 = activity2.instanceId.intValue();
        int counter2 = MainActivity.creationCounter;
        Log.d("TEST", "newly acquired activity " + activity2 + " has id " + act2 + "/" + counter2);

        assertEquals(2, counter2);
        assertEquals(2, act2);
    }
}

以上代码(无论是method1还是2)都会产生logcat:

D/ActivityTestRule: Launching activity example.com.rotationtest.MainActivity
D/TEST: creating activity example.com.rotationtest.MainActivity@47404a3, has id 1
D/TEST: requesting rotation
D/TEST: creating activity example.com.rotationtest.MainActivity@169887e, has id 2
D/TEST: newly acquired activity example.com.rotationtest.MainActivity@47404a3 has id 1/2
I/TestRunner: failed: useAppContext(example.com.rotationtest.ExampleInstrumentedTest)
I/TestRunner: ----- begin exception -----
I/TestRunner: java.lang.AssertionError: expected:<2> but was:<1>

我的诊断,如有错误请纠正:

  1. activity1.setRequestedOrientation会在其他线程中创建新的Activity。我希望它能正确地接收Bundle。
  2. getInstrumentation().waitForIdleSync()会导致测试等待新的Activity被创建。
  3. mActivityTestRule.getActivity()仍然返回旧的Activity实例。
  4. 我需要一种方法来刷新测试规则中保存的Activity实例,并释放先前保存的实例。

我在旧版本的测试框架中找到了答案:Instrumentation test for Android - How to receive new Activity after orientation change?

mActivity.setRequestedOrientation(
    ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mActivity.finish();
setActivity(null);
mActivity = getActivity();
getInstrumentation().waitForIdleSync();

但我不知道如何将其翻译成新版本。

编辑:

上述两种方法都会使活动保持在已销毁的状态:assertFalse(mActivityTestRule.getActivity().isDestroyed()); 失败。

我找到了另一种方法(使用Testing Support Library销毁和重启Activity),它会重新创建活动实例,但不会通过onSaveInstanceState保存其状态。


ActivityTestRule.getActivity 不会创建新的 Activity,它只是获取相同实例。 - Yoni Gross
Yoni,我知道这个不起作用。我想知道如何使其工作,不一定要使用ActivityTestRule。 - MateuszL
3个回答

1

@MateuszL的解决方案现在仍然适用于androidx测试协调器和Kotlin,如果这个版本有帮助的话:

import androidx.test.espresso.core.internal.deps.guava.collect.Iterables
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage

class CurrentActivityTestRule<T : Activity> : ActivityTestRule<T> {

    val currentActivity: T
        get() {
            getInstrumentation().waitForIdleSync()
            val activity = arrayOfNulls<Activity>(1)
            getInstrumentation().runOnMainSync(Runnable {
                val activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
                activity[0] = Iterables.getOnlyElement(activities)
            })
            @Suppress("UNCHECKED_CAST")
            return activity[0] as T
        }

    constructor(activityClass: Class<T>) : super(activityClass, false)

    constructor(activityClass: Class<T>, initialTouchMode: Boolean) : super(activityClass, initialTouchMode, true)

    constructor(activityClass: Class<T>, initialTouchMode: Boolean, launchActivity: Boolean) : super(
        activityClass,
        initialTouchMode,
        launchActivity
    )
}

...并且在Kotlin中像这样使用,例如,如果我们知道在测试的这一点上当前活动应该是RegistrationActivity,并且我们需要在集成测试期间直接访问其中一个函数:

@Suppress("CAST_NEVER_SUCCEEDS")
private fun getRegistrationActivity()= activityTestRule.currentActivity as RegistrationActivity

1

我在这里找到了可行的解决方案:在Espresso Android中获取当前活动

经过适应我的需求后,代码如下:

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

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

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

    public T getCurrentActivity() {
        getInstrumentation().waitForIdleSync();
        final Activity[] activity = new Activity[1];
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                java.util.Collection<Activity> activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
                activity[0] = Iterables.getOnlyElement(activities);
            }});
        T current = (T) activity[0];
        return current;
    }
}

"and" 是这样使用的:
onView(isRoot()).perform(orientationLandscape());
Activity oldActivityInstance = mActivityTestRule.getActivity();
Activity currentActivityInstance = mActivityTestRule.getCurrentActivity();

我已经用以下库版本使其工作:

androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestCompile('com.android.support.test:runner:0.5', {
    exclude group: 'com.android.support', module: 'support-annotations'
})

这仍然适用于androidx和androidx测试协调器,并且在Kotlin中也可以使用。为了帮助其他人,我添加了一个答案并将您的答案作为参考来源。 - ChrisPrime

0

android.test.rule 的当前版本已经解决了这个问题。 在调用 activityTestRule.getActivity() 之前,您只需要调用 InstrumentationRegistry.getInstrumentation().waitForIdleSync() 即可。


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