如何获取在Java中使用Cucumber测试失败时抛出的异常?

时间:2017-03-01 21:38:03

标签: java cucumber

我可以使用以下方法对测试失败执行操作:

@After
public void afterTest(Scenario scenario) {
    if (scenario.isFailed()) {
        /*Do stuff*/
    }
}

但是,我需要执行的一些操作取决于抛出的异常以及抛出的上下文。有没有办法让Throwable导致测试失败?例如,在JUnit中,我会通过扩展TestWatcher并将规则添加到我的测试中来实现:

@Override
protected void failed(Throwable e, Description description) {
    /*Do stuff with e*/
}

然而,黄瓜junit iplementation不允许使用规则,所以这个解决方案不适用于Cucumber。

我认为我不需要解释为什么在测试失败时访问抛出的异常会有用,但是我仍然会提供一个示例:

我的测试环境并不总是稳定的,所以我的测试可能会在任何时候意外失败(我没有特定的地方可以尝试捕获异常,因为它可能随时发生)。当发生这种情况时,我需要重新安排测试以进行另一次尝试,并记录事件,以便我们可以获得有关环境不稳定性的详细统计数据(何时,如何频繁,多长时间等)。

7 个答案:

答案 0 :(得分:3)

我已经使用反射实现了此方法。您无法直接访问步骤错误(堆栈跟踪)。我创建了此静态方法,该方法使您可以访问“ stepResults”属性,然后可以迭代并获取错误,然后执行所需的任何操作。

@After
public void afterScenario(Scenario scenario) {
  if (scenario.isFailed())
    logError(scenario);
}


private static void logError(Scenario scenario) {
   Field field = FieldUtils.getField(((ScenarioImpl) scenario).getClass(), "stepResults", true);
   field.setAccessible(true);
   try {
       ArrayList<Result> results = (ArrayList<Result>) field.get(scenario);
       for (Result result : results) {
           if (result.getError() != null)
               LOGGER.error("Error Scenario: {}", scenario.getId(), result.getError());
       }
   } catch (Exception e) {
       LOGGER.error("Error while logging error", e);
   }
}

答案 1 :(得分:1)

您可以通过编写自己的Formatter & Reporter接口自定义实现来实现此目的。 Formatter的空实现是您可以扩展的NullFormatter.java。您需要提供Reporter接口的实现。

感兴趣的方法将是Reporter接口的result()以及Formatter的done()方法。 result()具有Result对象,但有例外。

为清晰起见,您可以查看RerunFormatter.java

Github Formatter source

public void result(Result result) {
      //Code to create logs or store to a database etc...
      result.getError();
      result.getErrorMessage();
}

您需要将此类(com.myimpl.CustomFormRep)添加到插件选项中。

plugin={"pretty", "html:report", "json:reports.json","rerun:target/rerun.txt",com.myimpl.CustomFormRep}

More details on custom formatters.

您可以使用重新运行插件获取要再次运行的失败方案列表。不确定是否安排了一系列失败的测试,创建批处理作业的代码或在CI工具上安排一个。

答案 2 :(得分:1)

这是使用反射的黄瓜Java版本4.8.0的解决方法。

import cucumber.api.Result;
import io.cucumber.core.api.Scenario;
import io.cucumber.core.logging.Logger;
import io.cucumber.core.logging.LoggerFactory;
import io.cucumber.java.After;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;

@After
public void afterScenario(Scenario scenario) throws IOException {
    if(!scenario.getStatus().isOk(true)){
        logError(scenario);
    }
}

private static void logError(Scenario scenario) {
    try {
        Class clasz = ClassUtils.getClass("cucumber.runtime.java.JavaHookDefinition$ScenarioAdaptor");
        Field fieldScenario = FieldUtils.getField(clasz, "scenario", true);
        fieldScenario.setAccessible(true);
        Object objectScenario =  fieldScenario.get(scenario);

        Field fieldStepResults = objectScenario.getClass().getDeclaredField("stepResults");
        fieldStepResults.setAccessible(true);

        ArrayList<Result> results = (ArrayList<Result>) fieldStepResults.get(objectScenario);
        for (Result result : results) {
            if (result.getError() != null) {
                LOGGER.error(String.format("Error Scenario: %s", scenario.getId()), result.getError());
            }
        }
    } catch (Exception e) {
        LOGGER.error("Error while logging error", e);
    }
}

答案 3 :(得分:1)

Frank Escobar建议的变通方法问题:

通过使用反射进入框架内部,您将取决于实现细节。这是一个坏习惯,每当框架更改其实现时,您的代码都可能会中断,就像您在Cucumber v5.0.0中所观察到的那样。

Cucumber中的挂钩旨在在场景之前和之后操纵测试执行上下文。他们不是要报告测试执行本身。报表是一个横切关注的问题,最好使用插件系统进行管理。

例如:

package com.example;

import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseFinished;

public class MyTestListener implements ConcurrentEventListener {
    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestCaseFinished.class, this::handleTestCaseFinished);
    }

    private void handleTestCaseFinished(TestCaseFinished event) {
        TestCase testCase = event.getTestCase();
        Result result = event.getResult();
        Status status = result.getStatus();
        Throwable error = result.getError();
        String scenarioName = testCase.getName();
        String id = "" + testCase.getUri() + testCase.getLine();
        System.out.println("Testcase " + id + " - " + status.name());
    }
}

使用JUnit 4和TestNG时,您可以使用以下方法激活此插件:

@CucumberOptions(plugin="com.example.MyTestListener")

使用JUnit 5,您可以将其添加到junit-platform.properties

cucumber.plugin=com.example.MyTestListener 

或者如果您使用的是CLI

--plugin com.example.MyTestListener 

答案 4 :(得分:0)

如果您只想按下发送到报告的结果,那么您可以扩展CucumberJSONFormatter并覆盖结果方法,如下所示:

public class CustomReporter extends CucumberJSONFormatter {

    CustomReporter(Appendable out) {
        super(out);
    }

    /**
     * Truncate the error in the output to the testresult.json file.
     * @param result the error result
     */
    @Override
    void result(Result result) {
        String errorMessage = null;
        if (result.error) {
            errorMessage = "Error: " + truncateError(result.error);
        }
        Result myResult = new Result(result.status, result.duration, errorMessage);
        // Log the truncated error to the JSON report
        super.result(myResult);
    }
}

然后将插件选项设置为:

plugin = ["com.myimpl.CustomReporter:build/reports/json/testresult.json"]

答案 5 :(得分:0)

对于cucumber-js https://www.npmjs.com/package/cucumber/v/6.0.3

import { After } from 'cucumber'

After(async function(scenario: any) {
    const exception = scenario.result.exception
    if (exception) {
        this.logger.log({ level: 'error', message: '-----------StackTrace-----------' })
        this.logger.log({ level: 'error', message: exception.stack })
        this.logger.log({ level: 'error', message: '-----------End-StackTrace-----------' })
    }
})

enter image description here

答案 6 :(得分:0)

经过大量实验,我现在删除了“注解之前/之后”,而是依靠“黄瓜事件”。它们包含TestCase类(这是Scenario包装的内容)和一个Result,您可以在其中调用getError();来获得Throwable。

这是使它正常工作的简单示例

import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseFinished;
import io.cucumber.plugin.event.TestCaseStarted;
import org.openqa.selenium.WebDriver;

public class TestCaseListener implements EventListener {
    @Override
    public void setEventPublisher(final EventPublisher publisher) {
        publisher.registerHandlerFor(TestCaseStarted.class, this::onTestCaseStarted);
        publisher.registerHandlerFor(TestCaseFinished.class, this::onTestCaseFinished);
    }

    public void onTestCaseStarted(TestCaseStarted event) {
        TestCase testCase = event.getTestCase();
        System.out.println("Starting " + testCase.getName());

        // Other stuff you did in your @Before-Method.
        // ...
    }

    private void onTestCaseFinished(final TestCaseFinished event) {
        TestCase testCase = event.getTestCase();
        System.out.println("Finished " + testCase.getName());

        Result result = event.getResult();
        if (result.getStatus() == Status.FAILED) {
            final Throwable error = result.getError();
            error.printStackTrace();
        }

        // Other stuff you did in your @After-Method.
        // ...
    }
}

剩下要做的就是将此类注册为Cucumber-Plugin。 我是通过修改@CucumberOptions注释来做到这一点的:

@CucumberOptions(plugin = {"com.example.TestCaseListener"})

我发现这比所有这种反射疯狂要干净得多,但是它需要更多的代码更改。

修改

我不知道为什么,但是这导致许多测试在多线程环境中随机失败。 我试图弄清楚,但现在也使用此线程中提到的丑陋反射:

public class SeleniumUtils {
private static final Logger log = LoggerFactory.getLogger(SeleniumUtils.class);

private static final Field field = FieldUtils.getField(Scenario.class, "delegate", true);
private static Method getError;

public static Throwable getError(Scenario scenario) {
    try {
        final TestCaseState testCase = (TestCaseState) field.get(scenario);
        if (getError == null) {
            getError = MethodUtils.getMatchingMethod(testCase.getClass(), "getError");
            getError.setAccessible(true);
        }
        return (Throwable) getError.invoke(testCase);
    } catch (Exception e) {
        log.warn("error receiving exception", e);
    }
    return null;
}
}