4

我正在使用 Google 推荐的 Android 测试框架:ActivityInstrumentationTestCase2。我在RANDOM测试运行中遇到了以下错误,但持续死亡。这意味着有时所有测试都通过了(很高兴!),但很多时候它随机失败并出现这三个错误之一。这很令人沮丧,让我对测试结果没有信心。

为了详细描述这些问题,我提供了我的测试类的简化伪代码和以下三个问题。两个测试用例相互独立

public class FirstActivityTest 
extends ActivityInstrumentationTestCase2<FirstActivity> {
    private FirstActivity mActivity;
    private ActivityMonitor mActivityMonitor;

    public FirstActivityTest () {
        super(FirstActivity.class);
    }

    public void setUp() throws Exception {
        super.setUp();
        setActivityInitialTouchMode(false);

        mActivity = getActivity();
        assertNotNull("Cannot start test since target Activity is NULL!", mActivity);

        mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);
    }

    public void tearDown() throws Exception {
        super.tearDown();
        if(mActivity != null) {
            mActivity.finish();
            mActivity = null;
        }
        if(mActivityMonitor != null) {
            getInstrumentation().removeMonitor(mActivityMonitor);
            mActivityMonitor = null;
        }
    }

    /**
     * Open FirstActivity, enter a text and click submit button. 
     * Verifies SecondActivity is open.
     */
    public void testA_HappyPath() {
        Activity secondActivity = null;
        try {
            //(Omitted) Get edit text and enter a valid value
            //(Omitted) Find submitButton view
            //Click submit button
            TouchUtils.clickView(this, submitButton);

            //Wait for result and validate:
            secondActivity = mActivityMonitor.waitForActivityWithTimeout(10000);
            assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
        } finally {
            //Clean up:
            if(secondActivity != null) {
                secondActivity .finish();
                secondActivity = null;
            }
        }
    }

    /**
     * Open FirstActivity, do NOT enter a text and click submit button. 
     * Verifies error message is returned.
     */
    public void testB_SadPath() {
        //(Omitted) Find submitButton view
        //Click submit button
        TouchUtils.clickView(this, submitButton);

        //(Omitted) Validate error message is displayed
    }
}

现在,我一遍又一遍地运行这两个测试用例(它们将按字母顺序运行),结果如下:

  1. 两个测试用例都通过了,或者
  2. testA_HappyPath() 失败,因为 ActivityMonitor.waitForActivityWithTimeout() 返回 NULL SecondActivity。但是当我查看我的设备时,SecondActivity 显示正确。不知何故,测试没有注意到它。为什么?
  3. 当 testA_HappyPath() 失败时,下一个 testB_SadPath() 将在 setUp() > getActivity() 期间无限期挂起。我想我关闭了 tearDown() 中的所有内容。为什么?
  4. testB_SadPath() 在 TouchUtils.clickView() 上经常失败,并出现以下错误:“注入另一个应用程序需要INJECT_EVENTS 权限”(无论 testA_HappyPath 是通过还是失败)。为什么?

任何有用的反馈表示赞赏。谢谢!


到目前为止,我已经处理了这 3 个问题,研究了互联网上的许多建议,并进行了几次尝试和错误。然而,没有一个人立即解决了特定问题——通过结合我发现我解决了上面的问题 (1) 和 (2),但仍然有问题 (3) 未解决。以下是我为完成这项工作所做的修复的详细信息。

ISSUE (1) ActivityMonitor.waitForActivityWithTimeout() 返回 NULL

1.1。我现在了解到我必须在 getActivit() 之前声明 getInstrumentation().addMonitor()。请查看我如何更改 setUp() 方法,这以某种方式解决了问题。任何理解为什么这是要求的人请告诉我们,我们很感激。

1.2. 在模拟器上,此调用偶尔会返回 NULL 导致测试失败。我知道这是因为等待时间太短了。因此,增加等待时间有助于防止 ActivityMonitor 过早返回。

ISSUE (2) 下一个 testB_SadPath() 将在 setUp() > getActivity() 期间无限期挂起

2.1。如上所述,这发生在之前的测试 (testA_HappyPath) 失败时。我认为我的 tearDown() 清理了所有内容并准备好运行下一个测试。发生的事情是,testA 正在等待 SecondActivity 出现在屏幕上,但由于 ActivityMonitor.waitForActivityWithTimeout() 返回 NULL,testA 失败。tearDown() 执行得很好。问题是 SecondActivity 确实出现在屏幕上,但它永远不会在 finally 块中关闭,因为它的方法实例“secondActivity”仍然为空。让 SecondActivity 活着并在屏幕上逗留会导致下一个 getActivity() 挂起。我通过更改 finally 块来解决此问题,以确保 SecondActivity 是否存在,它会被关闭。

这些更改总结在下面的代码中(参见 setUp() 和 finally 块)。

public class FirstActivityTest 
extends ActivityInstrumentationTestCase2<FirstActivity> {
    private FirstActivity mActivity;
    private ActivityMonitor mActivityMonitor;

    public FirstActivityTest () {
        super(FirstActivity.class);
    }

    public void setUp() throws Exception {
        super.setUp();
        setActivityInitialTouchMode(false);
    }

    public void tearDown() throws Exception {
        super.tearDown();
        if(mActivity != null) {
            mActivity.finish();
            mActivity = null;
        }
        if(mActivityMonitor != null) {
            getInstrumentation().removeMonitor(mActivityMonitor);
            mActivityMonitor = null;
        }
    }

    /**
     * Open FirstActivity, enter a text and click submit button. 
     * Verifies SecondActivity is open.
     */
    public void testA_HappyPath() {
        mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);

        mActivity = getActivity();
        assertNotNull("Cannot start test since target Activity is NULL!", mActivity);

        Activity secondActivity = null;
        try {
            //(Omitted) Get edit text and enter a valid value
            //(Omitted) Find submitButton view
            //Click submit button
            TouchUtils.clickView(this, submitButton);

            //Wait for result and validate:
            secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
            assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
        } finally {
            //Clean up:
            if(secondActivity == null) {
                //If empty, wait longer because need to shut down the foreground activity, if any: 
                secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
            }
            if(secondActivity != null) {
                secondActivity .finish();
                secondActivity = null;
            }
        }
    }

    /**
     * Open FirstActivity, do NOT enter a text and click submit button. 
     * Verifies error message is returned.
     */
    public void testB_SadPath() {
        mActivity = getActivity();
        assertNotNull("Cannot start test since target Activity is NULL!", mActivity);

        //(Omitted) Find submitButton view
        //Click submit button
        TouchUtils.clickView(this, submitButton);

        //(Omitted) Validate error message is displayed
    }
}

ISSUE (3) testB_SadPath() 在 TouchUtils.clickView() 上经常失败,并出现以下错误:“注入到另一个应用程序需要 INJECT_EVENTS 权限”

我仍然无法解决最后一个问题:-(

4

1 回答 1

1

ISSUE (3) testB_SadPath() 在 TouchUtils.clickView() 上经常失败,并出现以下错误:“注入到另一个应用程序需要 INJECT_EVENTS 权限”

我在我的 Android 单元测试中找到了一种避免此问题的替代方法。我没有使用 TouchUtils.clickView(),而是通过调用 performClick() 直接对按钮本身执行单击操作。以下修改后的测试代码解决了我偶尔出现的 INJECT_EVENTS 权限错误。特别是,请参阅 populateDataAndClickSubmit()。

/**
 * Open FirstActivity, enter a text and click submit button. 
 * Verifies SecondActivity is open.
 */
public void testA_HappyPath() {
    mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);

    mActivity = getActivity();
    assertNotNull("Cannot start test since target Activity is NULL!", mActivity);

    Activity secondActivity = null;
    try {
        String dataValue = "MyNameIsNoLongerFooNorBar";
        populateDataAndClickSubmit(dataValue);

        //Wait for result and validate:
        secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
        assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
    } finally {
        //Clean up:
        if(secondActivity == null) {
            //If empty, wait longer because need to shut down the foreground activity, if any: 
            secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
        }
        if(secondActivity != null) {
            secondActivity.finish();
            secondActivity = null;
        }
    }
}

/**
 * Open FirstActivity, do NOT enter a text and click submit button. 
 * Verifies error message is returned.
 */
public void testB_SadPath() {
    mActivity = getActivity();
    assertNotNull("Cannot start test since target Activity is NULL!", mActivity);

    String dataValue = null;
    populateDataAndClickSubmit(dataValue);

    //(Omitted) Validate error message is displayed
}

private void populateDataAndClickSubmit(final String dataValueString) {
    final EditText editDataView = //(omitted) find it from the activity layout
    final Button submitButton = //(Omitted) Find submitButton view

    mActivity.runOnUiThread(
            new Runnable() {
                public void run() {
                    editDataView.setText(dataValueString);
                    submitButton.performClick();
               }
            }
        );

    //Wait and allow app to be idle while performClick to finish and activity re-drawn:
    getInstrumentation().waitForIdleSync();
}

笔记:

  1. 此解决方案不是为什么 touchUtils.clickView() 会抛出偶尔的注入事件权限错误的答案
  2. View.performClick() 要求您的活动让相关视图实现 OnClickListener()。在我的例子中,SubmitButton 已经有了,所以这是一个方便的测试代码更改。
  3. getInstrumentation().waitForIdleSync() 允许测试代码空闲,直到应用程序完成工作并适当地重新绘制其布局。如果您查看其 java 代码,则在 touchUtils.clickView() 中执行同一行。
于 2013-09-27T19:20:23.077 回答