我可以使用以下方法对测试失败执行操作:
@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。
我认为我不需要解释为什么在测试失败时访问抛出的异常会有用,但是我仍然会提供一个示例:
我的测试环境并不总是稳定的,所以我的测试可能会在任何时候意外失败(我没有特定的地方可以尝试捕获异常,因为它可能随时发生)。当发生这种情况时,我需要重新安排测试以进行另一次尝试,并记录事件,以便我们可以获得有关环境不稳定性的详细统计数据(何时,如何频繁,多长时间等)。
答案 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
。
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-----------' })
}
})
答案 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;
}
}