Java 如何在浓缩咖啡中重新运行失败的测试?-头脑风暴

Java 如何在浓缩咖啡中重新运行失败的测试?-头脑风暴,java,android,unit-testing,junit,android-espresso,Java,Android,Unit Testing,Junit,Android Espresso,我想弄清楚,如何使用浓缩咖啡重新运行失败的测试。我认为普通JUnit测试用例要复杂一些,因为您需要在测试开始之前恢复应用程序中的状态 我的方法是创建自己的ActivityTestRule,所以我只是从这个类复制了整个代码,并将其命名为MyActivityTestRule 在仪器测试的情况下,规则还需要关于我们希望如何开始活动的信息。我更喜欢自己启动它,而不是让环境为我做。例如: @Rule public MyActivityTestRule<ActivityToStartW

我想弄清楚,如何使用浓缩咖啡重新运行失败的测试。我认为普通JUnit测试用例要复杂一些,因为您需要在测试开始之前恢复应用程序中的状态

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

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

    @Rule
    public MyActivityTestRule<ActivityToStartWith> activityRule = new MyActivityTestRule<>(
           ActivityToStartWith.class, true, false
    );
并在@After注释方法中进行清理:

   @Before
    public void setUp() throws Exception {
        activityRule.launchActivity(new Intent());
    }
    @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();
        }
    }
}
我的测试用例运行没有任何问题。多亏了这些,我所剩下的——围绕mBase.evaluate()的内容是:

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

据我所知,mBase.evaluate()在@Test注释方法中运行我的代码。它还可以在抛出throwable时停止测试用例。这意味着我可以捕获它并重新启动它-就像在那里提议的那样:

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

 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;
            }
        }
    }
公共类ActivityRetryStatement扩展语句{
私人期末报告MBA;
私人最终int MAX_运行=2;
公共活动eTryStatement(报表库){
mBase=基础;
}
@凌驾
public void evaluate()可丢弃{
Throwable-Throwable=null;
对于(int i=0;i
所以catch块中的那些注释是我不知道如何做的部分。我试图根据我在setUp()中第一次运行测试时使用的意图执行launchActivity。但是mBase.evaluate()并没有让它做出反应(测试用例并没有再次出现)-什么都并没有发生,我想这并不能真正拯救我。我在@SetUp中缺少一些初始化,所以没有再次调用它。我真的很想找到一种方法,在“测试”之前,在“测试”之后,重新启动整个测试生命周期。可能需要从代码中调用Instrumentation或TestRunner


有没有想过如何做到这一点?

答案非常简单。只需确保您将espresso测试升级到Junit 4,然后查看

是否有任何解决方案?如果您使用的是firebase测试实验室,您可以使用一个标志--“num flaky test attempts 2”(重试两次)。对我来说,将重复行为烘焙到测试本身被证明太脆弱/难以排除故障,因此我不鼓励这样做。我尝试将
evaluate()
放入循环中,但发生的事情->@测试代码块再次执行,但设备没有反应。这对你来说是否适用于浓缩咖啡-而不是普通的Junit测试?(我使用的是Junit 4.12版)是的,我在我们的浓缩咖啡测试中实现了这一点,效果很好。问题可能在于您设置测试的方式。你能发布一个示例测试的要点吗,这个示例测试还包括我链接到的答案中实现的重试规则?嘿,非常感谢你提供的信息。目前我的工作很忙,但我肯定会尽快检查,因为我对此非常感兴趣,如果它有效,我会给你反馈,或者提供一些代码,以便我们可以检查我做错了什么:)
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();
        }
    }
}
 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;
            }
        }
    }