java - 如何在 Espresso 中重新运行失败的测试? - 头脑 Storm

标签 java android unit-testing junit android-espresso

我想知道如何使用 Espresso 重新运行失败的测试。我认为它比常见的 JUnit 测试用例要复杂一些,因为您需要在测试开始之前恢复应用程序中的状态。

我的方法是创建我自己的 ActivityTestRule,所以我只是从这个类中复制了整个代码并将其命名为 MyActivityTestRule。

在仪器测试的情况下,规则还需要我们希望如何开始我们的 Activity 的信息。我更喜欢自己启动它,而不是让环境为我启动它。例如:

    @Rule
    public MyActivityTestRule<ActivityToStartWith> activityRule = new MyActivityTestRule<>(
           ActivityToStartWith.class, true, false
    );

所以我也在@Before注解方法中启动了我的activity:

   @Before
    public void setUp() throws Exception {
        activityRule.launchActivity(new Intent());
    }

并在@After 注解方法中进行清理:

    @After
    public void tearDown() throws Exception {
        cleanUpDataBaseAfterTest();
        returnToStartingActivity(activityRule);
    }

这些方法 - setUp()、tearDown() 在每次测试运行之前/之后必须调用 - 以确保测试开始期间的应用状态正确。


在 MyActivityTestRule 中,到目前为止我做了一些修改。首先是更改应用方法:

    @Override
    public Statement apply(final Statement base, Description description) {
       return new ActivityStatement(super.apply(base, description));
    }

这对我来说是个未知数,因为放在 ActivityTestRule 中的 ActivityStatement 有 super.apply 方法,所以它也在 UiThreadStatement 中包装了测试语句:

public class UiThreadStatement extends Statement {
    private final Statement mBase;
    private final boolean mRunOnUiThread;

    public UiThreadStatement(Statement base, boolean runOnUiThread) {
        mBase = base;
        mRunOnUiThread = runOnUiThread;
    }

    @Override
    public void evaluate() throws Throwable {
        if (mRunOnUiThread) {
            final AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
            getInstrumentation().runOnMainSync(new Runnable() {
                public void run() {
                    try {
                        mBase.evaluate();
                    } catch (Throwable throwable) {
                        exceptionRef.set(throwable);
                    }
                }
            });
            Throwable throwable = exceptionRef.get();
            if (throwable != null) {
                throw throwable;
            }
        } else {
            mBase.evaluate();
        }
    }
}

无论我用我的测试做什么,我永远无法创建 case mRunOnUiThread boolean 值为真。如果在我的测试用例中,将出现带有注释 @UiThreadTest 的测试,那将是正确的——或者这就是我从代码中理解的内容。但它从来没有发生过,我不使用任何类似的东西所以我决定忽略这个 UiThreadStatement 并将 MyActivityTestRule 更改为:

    @Override
    public Statement apply(final Statement base, Description description) {
       return new ActivityStatement(base);
    }

我的测试用例运行没有任何问题。多亏了我剩下的一切——环绕 mBase.evaluate() 的是:

private class ActivityStatement extends Statement {

    private final Statement mBase;

    public ActivityStatement(Statement base) {
        mBase = base;
    }

    @Override
    public void evaluate() throws Throwable {
        try {
            if (mLaunchActivity) {
                mActivity = launchActivity(getActivityIntent());
            }
            mBase.evaluate();
        } finally {
            finishActivity();
            afterActivityFinished();
        }
    }
}

一般来说,仅当我在 ActivityTestRule 构造函数的第三个参数中设置值 true 时,才会调用 launchActivity。但是我自己在 setUp() 中启动测试,所以它永远不会发生。

据我了解,mBase.evaluate() 在@Test 注释方法中运行我的代码。它还会在抛出 throwable 期间停止测试用例。这意味着我可以捕获它并重新启动它 - 就像那里提出的那样: How to Re-run failed JUnit tests immediately?

好吧,我做了类似的事情:

 public class ActivityRetryStatement extends Statement {

        private final Statement mBase;
        private final int MAX_RUNS = 2;

        public ActivityRetryStatement(Statement base) {
            mBase = base;
        }

        @Override
        public void evaluate() throws Throwable {

            Throwable throwable = null;

            for (int i = 0; i < MAX_RUNS; i++) {

                try {
                    mBase.evaluate();
                    // if you reach this lane that means evaluate passed
                    // and you don't need to do the next run
                    break;
                } catch (Throwable t) {

                    // save first throwable if occurred
                    if (throwable == null) {
                        throwable = t;
                    }

                    // my try that didn't work
                    launchActivity(testInitialIntent);
                    // I've returned test to starting screen but
                    // mBase.envaluate() didn't make it run again

                    // it would be cool now to:
                    // - invoke @After
                    // - finish current activity
                    // - invoke @Before again and start activity
                    // - mBase.evaluate() should restart @Test on activity started again by @Before
                } 
            }

            finishActivity();
            afterActivityFinished();

            // if 1st try fail but 2nd passes inform me still that there was error
            if (throwable != null) {
                throw throwable;
            }
        }
    }

所以 catch block 中的那些评论是我不知道该怎么做的部分。我尝试按照我在 setUp() 中使用的 Intent 执行 launchActivity 以第一次运行测试。但是 mBase.evaluate() 没有让它使用react(测试用例没有再次进行) - 什么都没发生 + 我认为它不会真正拯救我。我缺少我在@SetUp 中做的一些启动,它没有被再次调用。我真的很想找到一种方法来重新正确地重新启动整个测试生命周期@Before @Test @After。也许从代码中调用 Instrumentation 或 TestRunner。

关于如何做到这一点有什么想法吗?

最佳答案

您可以使用 https://github.com/AdevintaSpain/Barista用于重新运行失败测试的库。

您可以在此处查看有关处理不稳定测试的更多详细信息:https://github.com/AdevintaSpain/Barista#dealing-with-flaky-tests

关于java - 如何在 Espresso 中重新运行失败的测试? - 头脑 Storm ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35921423/

相关文章:

java - 如何将场注入(inject)与 Action 组合一起使用?

android - 无法使用 WindowManager.LayoutParams.FLAG_SECURE 在 Android 12 上保护窗口

android - 如何从字符串数组中随机获取文本?

.net - 如何使用 CSV 数据源控制数据驱动单元测试中列的类型?

java - 不使用正则表达式从查询字符串中删除一个参数

java - Sling 资源与节点

android - Android中NFC智能海报标签写入数据的方法

c# - 我怎样才能模拟这个异步方法?

ios - 从 ios-sim 启动单元测试不会执行所有测试

java - Java 可以在没有 Java 虚拟机的情况下运行吗?