我正在努力并行执行我的黄瓜测试,并获得有关两者的报告。
我有单个运行器类(BrowserRunner
),该类从testNG xml获取“浏览器”。
在该课程中,我做了一些令人讨厌的巫术,以在运行时修改CucumberOptions
并为Cucumber报告设置不同的输出路径。
如果按一个顺序运行,那么一切都很好,但是如果并行执行,则将tearDownClass()
称为2nd,3rd等的人会得到空指针异常:
java.lang.NullPointerException
at cucumber.runtime.formatter.TestNGFormatter.handleTestCaseStarted(TestNGFormatter.java:132)
at cucumber.runtime.formatter.TestNGFormatter.access$100(TestNGFormatter.java:43)
at cucumber.runtime.formatter.TestNGFormatter$2.receive(TestNGFormatter.java:64)
at cucumber.runtime.formatter.TestNGFormatter$2.receive(TestNGFormatter.java:61)
at cucumber.runner.AbstractEventPublisher.send(AbstractEventPublisher.java:45)
at cucumber.runner.AbstractEventPublisher.sendAll(AbstractEventPublisher.java:52)
at cucumber.runner.CanonicalOrderEventPublisher.handle(CanonicalOrderEventPublisher.java:18)
at cucumber.runtime.formatter.Plugins$1.receive(Plugins.java:55)
at cucumber.runner.AbstractEventPublisher.send(AbstractEventPublisher.java:38)
at cucumber.runner.AbstractEventBus.send(AbstractEventBus.java:9)
at cucumber.runner.TimeServiceEventBus.send(TimeServiceEventBus.java:3)
at cucumber.api.testng.TestNGCucumberRunner.finish(TestNGCucumberRunner.java:77)
at BrowserRunner.tearDownClass(BrowserRunner.java:150)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
at org.testng.internal.MethodInvocationHelper.invokeMethodConsideringTimeout(MethodInvocationHelper.java:59)
at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:458)
at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:222)
at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:142)
at org.testng.internal.TestMethodWorker.invokeAfterClassMethods(TestMethodWorker.java:214)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:111)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.access$000(SuiteRunner.java:40)
at org.testng.SuiteRunner$SuiteWorker.run(SuiteRunner.java:489)
at org.testng.internal.thread.ThreadUtil$1.call(ThreadUtil.java:52)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
基本思想是我有一组测试,并且想要在许多浏览器上运行它们。也许有更好的解决方案? 但是,我想坚持使用Cucumber,因为我喜欢编写测试的方式以及如何轻松读取它们。
tests.xml:
<suite name="Paratest" verbose="1" thread-count="2" parallel="tests" configfailurepolicy="continue">
<listeners>
<listener class-name="org.uncommons.reportng.HTMLReporter" />
<listener class-name="org.uncommons.reportng.JUnitXMLReporter" />
</listeners>
<parameter name="environment" value="TEST"></parameter>
<test name="Chrome">
<parameter name="browser" value="chrome" />
<classes>
<class name="BrowserRunner">
<methods>
<include name="scenario"/>
</methods>
</class>
</classes>
</test>
<test name="Firefox">
<parameter name="browser" value="firefox" />
<classes>
<class name="BrowserRunner">
<methods>
<include name="scenario"/>
</methods>
</class>
</classes>
</test>
</suite>
BrowserRunner类:
@CucumberOptions(
plugin = {"html:target/cucumber-html-report-IDENTIFICATION",
"json:target/cucumber-IDENTIFICATION.json",
"testng:target/testng-IDENTIFICATION.txt"},
strict = true,
features = {"src/test/resources/Login.feature"}
)
public class BrowserRunner {
private TestNGCucumberRunner testNGCucumberRunner;
private static final String ANNOTATIONS = "annotations";
public static final String ANNOTATION_DATA = "annotationData";
private String browser;
@BeforeSuite
public void beforeSuite() {
System.out.println("BeforeSuite");
}
@Parameters("browser")
@BeforeClass(alwaysRun = true)
public void setUpClass(String browser) throws Exception {
this.browser = browser;
System.out.println("Debug1. Thread.currentThread(): " + Thread.currentThread().getId() + " " + browser);
Class clazzToBeModified = BrowserRunner.class;
Method method = Class.class.getDeclaredMethod(ANNOTATION_DATA, null);
method.setAccessible(true);
//Since AnnotationData is a private class we cannot create a direct reference to it. We will have to
//manage with just Object
Object annotationData = method.invoke(clazzToBeModified);
//We now look for the map called "annotations" within AnnotationData object.
Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map =
(Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
Annotation toBeModified = map.get(CucumberOptions.class);
CucumberOptions newAnno = new CucumberOptions() {
@Override
public Class<? extends Annotation> annotationType() {
return null;
}
@Override
public boolean dryRun() {
return false;
}
@Override
public boolean strict() {
return false;
}
@Override
public String[] features() {
return new String[]{"src/test/resources/Login.feature"};
}
@Override
public String[] glue() {
return new String[0];
}
@Override
public String[] extraGlue() {
return new String[]{"src/test/java/Hooks.java"};
}
@Override
public String[] tags() {
return new String[]{"@smoke"};
}
@Override
public String[] plugin() {
return new String[]{"html:target/cucumber-html-report-" + browser,
"json:target/cucumber-" + browser + ".json",
"testng:target/testng-" + browser + ".txt"};
}
@Override
public boolean monochrome() {
return false;
}
@Override
public String[] name() {
return new String[0];
}
@Override
public SnippetType snippets() {
return SnippetType.UNDERSCORE;
}
@Override
public String[] junit() {
return new String[0];
}
};
map.put(CucumberOptions.class, newAnno);
testNGCucumberRunner = new TestNGCucumberRunner(this.getClass());
}
@Test(groups = "Cucumber", description = "Runs Cucumber Feature", dataProvider = "scenarios")
public void scenario(PickleEventWrapper pickleEvent, CucumberFeatureWrapper cucumberFeature) throws Throwable {
System.out.println("Debug2. Thread.currentThread(): " + Thread.currentThread().getId() + " " + browser);
testNGCucumberRunner.runScenario(pickleEvent.getPickleEvent());
}
@DataProvider
public Object[][] scenarios() {
return testNGCucumberRunner.provideScenarios();
}
@AfterClass(alwaysRun = true)
public void tearDownClass() throws Exception {
System.out.println("Debug3. Thread.currentThread(): " + Thread.currentThread().getId() + " " + browser);
if (testNGCucumberRunner != null) {
testNGCucumberRunner.finish();
}
}
}