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

Posted

技术标签:

【中文标题】如何在 Espresso 中重新运行失败的测试? - 头脑风暴【英文标题】:How re-run failed test in Espresso? - brainstorming 【发布时间】:2016-06-25 14:07:45 【问题描述】:

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

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

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

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

所以我也在@Before注释方法中启动我的活动:

   @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 块中的那些 cmets 是我不知道该怎么做的部分。我尝试根据我在 setUp() 中使用的意图执行 launchActivity 第一次运行测试。但是 mBase.evaluate() 并没有让它做出反应(测试用例没有再次出现) - 什么也没发生 + 我认为它不会真正拯救我。我缺少我在@SetUp 中所做的一些启动,它没有被再次调用。我真的很想找到一种方法来重新正确地重新启动整个测试生命周期@Before @Test @After。也许从代码中调用 Instrumentation 或 TestRunner。

有什么想法可以做到吗?

【问题讨论】:

有解决办法吗? 如果碰巧您正在使用 firebase 测试实验室,您可以为此使用标志“--num-flaky-test-attempts 2”(重试两次)。事实证明,将重复行为放入测试本身太不稳定/难以排除故障,因此我不鼓励这样做。 【参考方案1】:

答案超级简单。只需确保将 espresso 测试升级到 Junit 4,然后查看 this answer

【讨论】:

我试图将evaluate() 放入循环中,但发生了什么 -> @Test 代码块再次执行,但设备没有反应。它在 Espresso 上对你有用吗——不是常见的 Junit 测试? (我使用的是 Junit 4.12 版) 是的,我在我们的 Espresso 测试中实现了这一点,效果很好。这可能是您设置测试的方式,这就是问题所在。您能否发布一个示例测试的要点,其中还包括我链接到的答案中实施的重试规则? 嘿,非常感谢您提供的信息。目前我真的很忙,但我肯定会尽快检查它,因为我对此非常感兴趣,如果它有效,我会给你反馈或提供一些代码,以便我们检查我做错了什么:)【参考方案2】:

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

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

【讨论】:

以上是关于如何在 Espresso 中重新运行失败的测试? - 头脑风暴的主要内容,如果未能解决你的问题,请参考以下文章

Android - Espresso:为每次测试重新创建活动

Espresso:如何测试 SwipeRefreshLayout?

如何使用 android studio 运行单个 espresso 测试功能?

成功点击并阻止 60 秒后,Espresso 测试失败

Android Studio Espresso 测试错误:空测试套件

如何让浓缩咖啡测试在特定条件下失败