diff --git a/junit/src/main/java/cucumber/api/junit/Cucumber.java b/junit/src/main/java/cucumber/api/junit/Cucumber.java index 93e6b91e59..0323ec552d 100644 --- a/junit/src/main/java/cucumber/api/junit/Cucumber.java +++ b/junit/src/main/java/cucumber/api/junit/Cucumber.java @@ -10,7 +10,7 @@ import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.io.ResourceLoaderClassFinder; import cucumber.runtime.junit.Assertions; -import cucumber.runtime.junit.FeatureRunner; +import cucumber.runtime.junit.FeatureSuite; import cucumber.runtime.junit.JUnitReporter; import cucumber.runtime.model.CucumberFeature; import cucumber.runtime.snippets.SummaryPrinter; @@ -38,9 +38,9 @@ * * @see Options */ -public class Cucumber extends ParentRunner { +public class Cucumber extends ParentRunner { private final JUnitReporter jUnitReporter; - private final List children = new ArrayList(); + private final List children = new ArrayList(); private final Runtime runtime; /** @@ -51,7 +51,7 @@ public class Cucumber extends ParentRunner { * @throws org.junit.runners.model.InitializationError * if there is another problem */ - public Cucumber(Class clazz) throws InitializationError, IOException { + public Cucumber(Class clazz) throws InitializationError, IOException { super(clazz); ClassLoader classLoader = clazz.getClassLoader(); Assertions.assertNoCucumberAnnotatedMethods(clazz); @@ -68,17 +68,17 @@ public Cucumber(Class clazz) throws InitializationError, IOException { } @Override - public List getChildren() { + public List getChildren() { return children; } @Override - protected Description describeChild(FeatureRunner child) { + protected Description describeChild(FeatureSuite child) { return child.getDescription(); } @Override - protected void runChild(FeatureRunner child, RunNotifier notifier) { + protected void runChild(FeatureSuite child, RunNotifier notifier) { child.run(notifier); } @@ -92,7 +92,7 @@ public void run(RunNotifier notifier) { private void addChildren(List cucumberFeatures) throws InitializationError { for (CucumberFeature cucumberFeature : cucumberFeatures) { - children.add(new FeatureRunner(cucumberFeature, runtime, jUnitReporter)); + children.add(new FeatureSuite(getTestClass().getJavaClass(), cucumberFeature, runtime, jUnitReporter)); } } diff --git a/junit/src/main/java/cucumber/runtime/junit/ExamplesRunner.java b/junit/src/main/java/cucumber/runtime/junit/ExamplesRunner.java deleted file mode 100644 index 523d27633b..0000000000 --- a/junit/src/main/java/cucumber/runtime/junit/ExamplesRunner.java +++ /dev/null @@ -1,48 +0,0 @@ -package cucumber.runtime.junit; - -import cucumber.runtime.Runtime; -import cucumber.runtime.model.CucumberExamples; -import cucumber.runtime.model.CucumberScenario; -import org.junit.runner.Description; -import org.junit.runner.Runner; -import org.junit.runners.Suite; -import org.junit.runners.model.InitializationError; - -import java.util.ArrayList; -import java.util.List; - -class ExamplesRunner extends Suite { - private final CucumberExamples cucumberExamples; - private Description description; - - protected ExamplesRunner(Runtime runtime, CucumberExamples cucumberExamples, JUnitReporter jUnitReporter) throws InitializationError { - super(ExamplesRunner.class, new ArrayList()); - this.cucumberExamples = cucumberExamples; - - List exampleScenarios = cucumberExamples.createExampleScenarios(); - for (CucumberScenario scenario : exampleScenarios) { - try { - ExecutionUnitRunner exampleScenarioRunner = new ExecutionUnitRunner(runtime, scenario, jUnitReporter); - getChildren().add(exampleScenarioRunner); - } catch (InitializationError initializationError) { - initializationError.printStackTrace(); - } - } - } - - @Override - protected String getName() { - return cucumberExamples.getExamples().getKeyword() + ": " + cucumberExamples.getExamples().getName(); - } - - @Override - public Description getDescription() { - if (description == null) { - description = Description.createSuiteDescription(getName(), cucumberExamples.getExamples()); - for (Runner child : getChildren()) { - description.addChild(describeChild(child)); - } - } - return description; - } -} diff --git a/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitNotifier.java b/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitNotifier.java new file mode 100644 index 0000000000..756f49fbee --- /dev/null +++ b/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitNotifier.java @@ -0,0 +1,59 @@ +package cucumber.runtime.junit; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.internal.AssumptionViolatedException; +import org.junit.internal.runners.model.EachTestNotifier; +import org.junit.runner.Description; +import org.junit.runner.notification.RunNotifier; + +/** + * This class makes sure that an execution unit with a missing step definition is properly + * marked as ignored. The main reason for being of this class is that in JUnit, a test + * that is to be ignored can't be started. While for a cuke, we only figure out that a step + * definition is missing when we get at that step. + */ +public class ExecutionUnitNotifier extends EachTestNotifier { + + private final List failures = new ArrayList(); + private boolean ignored; + + public ExecutionUnitNotifier(RunNotifier notifier, Description description) { + super(notifier, description); + } + + public void addFailure(Throwable targetException) { + this.failures.add(targetException); + } + + public void addFailedAssumption(AssumptionViolatedException e) { + this.failures.add(e); + } + + public void fireTestFinished() { + if (ignored) { + super.fireTestIgnored(); + } else { + super.fireTestStarted(); + for (Throwable t : failures) { + if (t instanceof AssumptionViolatedException) { + super.addFailedAssumption((AssumptionViolatedException) t); + } else { + super.addFailure(t); + } + } + super.fireTestFinished(); + } + failures.clear(); + ignored = false; + } + + public void fireTestStarted() { + } + + public void fireTestIgnored() { + ignored = true; + } + +} \ No newline at end of file diff --git a/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitRunner.java b/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitRunner.java index c2cc9624bd..7331792623 100644 --- a/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitRunner.java +++ b/junit/src/main/java/cucumber/runtime/junit/ExecutionUnitRunner.java @@ -1,78 +1,37 @@ package cucumber.runtime.junit; -import cucumber.runtime.Runtime; -import cucumber.runtime.model.CucumberScenario; -import gherkin.formatter.model.Step; import org.junit.runner.Description; +import org.junit.runner.Runner; import org.junit.runner.notification.RunNotifier; -import org.junit.runners.ParentRunner; import org.junit.runners.model.InitializationError; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import cucumber.runtime.Runtime; +import cucumber.runtime.model.CucumberScenario; /** * Runs a scenario, or a "synthetic" scenario derived from an Examples row. */ -public class ExecutionUnitRunner extends ParentRunner { +public class ExecutionUnitRunner extends Runner { + private final Runtime runtime; private final CucumberScenario cucumberScenario; private final JUnitReporter jUnitReporter; - private Description description; - private final Map stepDescriptions = new HashMap(); + private final Description description; - public ExecutionUnitRunner(Runtime runtime, CucumberScenario cucumberScenario, JUnitReporter jUnitReporter) throws InitializationError { - super(ExecutionUnitRunner.class); + public ExecutionUnitRunner(Class testClass, Runtime runtime, String name, CucumberScenario cucumberScenario, JUnitReporter jUnitReporter) throws InitializationError { this.runtime = runtime; this.cucumberScenario = cucumberScenario; this.jUnitReporter = jUnitReporter; + this.description = Description.createTestDescription(testClass, replaceParenthesis(name)); } - @Override - protected List getChildren() { - return cucumberScenario.getSteps(); - } - - @Override - public String getName() { - return cucumberScenario.getVisualName(); + // eclipse implementation can't live with parenthesis in descriptions + private static String replaceParenthesis(String name) { + return name.replace('(', '<').replace(')','>'); } @Override public Description getDescription() { - if (description == null) { - description = Description.createSuiteDescription(getName(), cucumberScenario.getGherkinModel()); - - if (cucumberScenario.getCucumberBackground() != null) { - for (Step backgroundStep : cucumberScenario.getCucumberBackground().getSteps()) { - // We need to make a copy of that step, so we have a unique one per scenario - Step copy = new Step( - backgroundStep.getComments(), - backgroundStep.getKeyword(), - backgroundStep.getName(), - backgroundStep.getLine(), - backgroundStep.getRows(), - backgroundStep.getDocString() - ); - description.addChild(describeChild(copy)); - } - } - - for (Step step : getChildren()) { - description.addChild(describeChild(step)); - } - } - return description; - } - - @Override - protected Description describeChild(Step step) { - Description description = stepDescriptions.get(step); - if (description == null) { - description = Description.createTestDescription(getName(), step.getKeyword() + step.getName(), step); - stepDescriptions.put(step, description); - } return description; } @@ -84,11 +43,4 @@ public void run(final RunNotifier notifier) { jUnitReporter.finishExecutionUnit(); } - @Override - protected void runChild(Step step, RunNotifier notifier) { - // The way we override run(RunNotifier) causes this method to never be called. - // Instead it happens via cucumberScenario.run(jUnitReporter, jUnitReporter, runtime); - throw new UnsupportedOperationException(); - // cucumberScenario.runStep(step, jUnitReporter, runtime); - } } diff --git a/junit/src/main/java/cucumber/runtime/junit/FeatureRunner.java b/junit/src/main/java/cucumber/runtime/junit/FeatureRunner.java deleted file mode 100644 index dd49c5175e..0000000000 --- a/junit/src/main/java/cucumber/runtime/junit/FeatureRunner.java +++ /dev/null @@ -1,90 +0,0 @@ -package cucumber.runtime.junit; - -import cucumber.runtime.CucumberException; -import cucumber.runtime.Runtime; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.CucumberScenario; -import cucumber.runtime.model.CucumberScenarioOutline; -import cucumber.runtime.model.CucumberTagStatement; -import gherkin.formatter.model.Feature; -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.ParentRunner; -import org.junit.runners.model.InitializationError; - -import java.util.ArrayList; -import java.util.List; - -public class FeatureRunner extends ParentRunner { - private final List children = new ArrayList(); - - private final CucumberFeature cucumberFeature; - private final Runtime runtime; - private final JUnitReporter jUnitReporter; - private Description description; - - public FeatureRunner(CucumberFeature cucumberFeature, Runtime runtime, JUnitReporter jUnitReporter) throws InitializationError { - super(null); - this.cucumberFeature = cucumberFeature; - this.runtime = runtime; - this.jUnitReporter = jUnitReporter; - buildFeatureElementRunners(); - } - - @Override - public String getName() { - Feature feature = cucumberFeature.getGherkinFeature(); - return feature.getKeyword() + ": " + feature.getName(); - } - - @Override - public Description getDescription() { - if (description == null) { - description = Description.createSuiteDescription(getName(), cucumberFeature.getGherkinFeature()); - for (ParentRunner child : getChildren()) { - description.addChild(describeChild(child)); - } - } - return description; - } - - @Override - protected List getChildren() { - return children; - } - - @Override - protected Description describeChild(ParentRunner child) { - return child.getDescription(); - } - - @Override - protected void runChild(ParentRunner child, RunNotifier notifier) { - child.run(notifier); - } - - @Override - public void run(RunNotifier notifier) { - jUnitReporter.uri(cucumberFeature.getUri()); - jUnitReporter.feature(cucumberFeature.getGherkinFeature()); - super.run(notifier); - jUnitReporter.eof(); - } - - private void buildFeatureElementRunners() { - for (CucumberTagStatement cucumberTagStatement : cucumberFeature.getFeatureElements()) { - try { - ParentRunner featureElementRunner; - if (cucumberTagStatement instanceof CucumberScenario) { - featureElementRunner = new ExecutionUnitRunner(runtime, (CucumberScenario) cucumberTagStatement, jUnitReporter); - } else { - featureElementRunner = new ScenarioOutlineRunner(runtime, (CucumberScenarioOutline) cucumberTagStatement, jUnitReporter); - } - children.add(featureElementRunner); - } catch (InitializationError e) { - throw new CucumberException("Failed to create scenario runner", e); - } - } - } - -} diff --git a/junit/src/main/java/cucumber/runtime/junit/FeatureSuite.java b/junit/src/main/java/cucumber/runtime/junit/FeatureSuite.java new file mode 100644 index 0000000000..577117134d --- /dev/null +++ b/junit/src/main/java/cucumber/runtime/junit/FeatureSuite.java @@ -0,0 +1,58 @@ +package cucumber.runtime.junit; + +import java.util.ArrayList; + +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.Suite; +import org.junit.runners.model.InitializationError; + +import cucumber.runtime.Runtime; +import cucumber.runtime.model.CucumberFeature; +import cucumber.runtime.model.CucumberScenario; +import cucumber.runtime.model.CucumberScenarioOutline; +import cucumber.runtime.model.CucumberTagStatement; + +public class FeatureSuite extends Suite { + + private final CucumberFeature cucumberFeature; + private final Runtime runtime; + private final JUnitReporter jUnitReporter; + private final NameProvider nameProvider; + + public FeatureSuite(Class testClass, CucumberFeature cucumberFeature, Runtime runtime, JUnitReporter jUnitReporter) throws InitializationError { + super(testClass, new ArrayList()); + this.cucumberFeature = cucumberFeature; + this.runtime = runtime; + this.jUnitReporter = jUnitReporter; + this.nameProvider = new NameProvider(cucumberFeature); + buildFeatureElementRunners(); + } + + @Override + public String getName() { + return nameProvider.getName(); + } + + @Override + public void run(RunNotifier notifier) { + jUnitReporter.uri(cucumberFeature.getUri()); + jUnitReporter.feature(cucumberFeature.getGherkinFeature()); + super.run(notifier); + jUnitReporter.eof(); + } + + private void buildFeatureElementRunners() throws InitializationError { + for (CucumberTagStatement cucumberTagStatement : cucumberFeature.getFeatureElements()) { + Runner featureElementRunner; + if (cucumberTagStatement instanceof CucumberScenario) { + String name = nameProvider.getName(cucumberTagStatement); + featureElementRunner = new ExecutionUnitRunner(getTestClass().getJavaClass(), runtime, name, (CucumberScenario) cucumberTagStatement, jUnitReporter); + } else { + featureElementRunner = SuiteWithName.newSuite(getTestClass().getJavaClass(), runtime, (CucumberScenarioOutline) cucumberTagStatement, jUnitReporter, nameProvider); + } + getChildren().add(featureElementRunner); + } + } + +} diff --git a/junit/src/main/java/cucumber/runtime/junit/JUnitReporter.java b/junit/src/main/java/cucumber/runtime/junit/JUnitReporter.java index 004e8ba4ed..60b89c8035 100644 --- a/junit/src/main/java/cucumber/runtime/junit/JUnitReporter.java +++ b/junit/src/main/java/cucumber/runtime/junit/JUnitReporter.java @@ -1,6 +1,6 @@ package cucumber.runtime.junit; -import cucumber.api.PendingException; +import static cucumber.runtime.Runtime.isPending; import gherkin.formatter.Formatter; import gherkin.formatter.Reporter; import gherkin.formatter.model.Background; @@ -10,14 +10,14 @@ import gherkin.formatter.model.Scenario; import gherkin.formatter.model.ScenarioOutline; import gherkin.formatter.model.Step; -import org.junit.internal.runners.model.EachTestNotifier; -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; import java.util.ArrayList; import java.util.List; -import static cucumber.runtime.Runtime.isPending; +import org.junit.internal.runners.model.EachTestNotifier; +import org.junit.runner.notification.RunNotifier; + +import cucumber.api.PendingException; public class JUnitReporter implements Reporter, Formatter { private final List steps = new ArrayList(); @@ -26,11 +26,7 @@ public class JUnitReporter implements Reporter, Formatter { private final Formatter formatter; private final boolean strict; - EachTestNotifier stepNotifier; - private ExecutionUnitRunner executionUnitRunner; - private RunNotifier runNotifier; - EachTestNotifier executionUnitNotifier; - private boolean ignoredStep; + private EachTestNotifier notifier; public JUnitReporter(Reporter reporter, Formatter formatter, boolean strict) { this.reporter = reporter; @@ -39,25 +35,17 @@ public JUnitReporter(Reporter reporter, Formatter formatter, boolean strict) { } public void startExecutionUnit(ExecutionUnitRunner executionUnitRunner, RunNotifier runNotifier) { - this.executionUnitRunner = executionUnitRunner; - this.runNotifier = runNotifier; - this.stepNotifier = null; - this.ignoredStep = false; - - executionUnitNotifier = new EachTestNotifier(runNotifier, executionUnitRunner.getDescription()); - executionUnitNotifier.fireTestStarted(); + notifier = strict ? + new EachTestNotifier(runNotifier, executionUnitRunner.getDescription()) : + new ExecutionUnitNotifier(runNotifier, executionUnitRunner.getDescription()); + notifier.fireTestStarted(); } public void finishExecutionUnit() { - if (ignoredStep) { - executionUnitNotifier.fireTestIgnored(); - } - executionUnitNotifier.fireTestFinished(); + notifier.fireTestFinished(); } public void match(Match match) { - Description description = executionUnitRunner.describeChild(steps.remove(0)); - stepNotifier = new EachTestNotifier(runNotifier, description); reporter.match(match); } @@ -73,54 +61,32 @@ public void write(String text) { public void result(Result result) { Throwable error = result.getError(); - if (Result.SKIPPED == result) { - stepNotifier.fireTestIgnored(); - } else if (isPendingOrUndefined(result)) { + if (isPendingOrUndefined(result)) { addFailureOrIgnoreStep(result); - } else { - if (stepNotifier != null) { - //Should only fireTestStarted if not ignored - stepNotifier.fireTestStarted(); - if (error != null) { - stepNotifier.addFailure(error); - } - stepNotifier.fireTestFinished(); - } - if (error != null) { - executionUnitNotifier.addFailure(error); - } - } - if (steps.isEmpty()) { - // We have run all of our steps. Set the stepNotifier to null so that - // if an error occurs in an After block, it's reported against the scenario - // instead (via executionUnitNotifier). - stepNotifier = null; + } else if (error != null) { + notifier.addFailure(error); } reporter.result(result); } private boolean isPendingOrUndefined(Result result) { - Throwable error = result.getError(); - return Result.UNDEFINED == result || isPending(error); + return Result.UNDEFINED == result || isPending(result.getError()); } private void addFailureOrIgnoreStep(Result result) { if (strict) { addFailure(result); } else { - ignoredStep = true; - stepNotifier.fireTestIgnored(); + notifier.fireTestIgnored(); } } private void addFailure(Result result) { - Throwable error = result.getError(); if (error == null) { error = new PendingException(); } - stepNotifier.addFailure(error); - executionUnitNotifier.addFailure(error); + notifier.addFailure(error); } @Override @@ -137,7 +103,7 @@ public void after(Match match, Result result) { private void handleHook(Result result) { if (result.getStatus().equals(Result.FAILED)) { - executionUnitNotifier.addFailure(result.getError()); + notifier.addFailure(result.getError()); } } diff --git a/junit/src/main/java/cucumber/runtime/junit/NameProvider.java b/junit/src/main/java/cucumber/runtime/junit/NameProvider.java new file mode 100644 index 0000000000..55b1297a6f --- /dev/null +++ b/junit/src/main/java/cucumber/runtime/junit/NameProvider.java @@ -0,0 +1,62 @@ +package cucumber.runtime.junit; + +import org.junit.runner.Description; + +import gherkin.formatter.model.Examples; +import gherkin.formatter.model.TagStatement; +import cucumber.runtime.model.CucumberFeature; +import cucumber.runtime.model.CucumberTagStatement; + +/** + * This class provides a name for a {@link Description} for the various + * cucumber elements (feature, scenario, scenario outline). + * + * + * In order to work properly in the Eclipse IDE, this name has to have the + * following properties: + * + *
    + *
  • The name has to be unique accross all features
  • + *
  • The name can't contain parenthesis
  • + *
+ * + */ +public class NameProvider { + + private final CucumberFeature feature; + + public NameProvider(CucumberFeature feature) { + this.feature = feature; + } + + public String getName() { + return replaceParenthesis(feature.getGherkinFeature().getName()); + } + + public String getName(CucumberTagStatement cucumberTagStatement) { + String info = cucumberTagStatement.getVisualName().startsWith("|") ? + " <" + cucumberTagStatement.getVisualName() + ">" : ""; + return getName(cucumberTagStatement.getGherkinModel(), info); + } + + private String getName(TagStatement statement, String info) { + String name = statement.getName(); + if (name == null || name.length() == 0) { + name = statement.getKeyword(); + } + return replaceParenthesis(name + info + getSuffix(statement)); + } + + private String getSuffix(TagStatement statement) { + return " [" + feature.getUri() + ":" + statement.getLine() + "]"; + } + + private static String replaceParenthesis(String name) { + return name.replace('(', '<').replace(')','>'); + } + + public String getName(Examples examples) { + return getName(examples, ""); + } + +} diff --git a/junit/src/main/java/cucumber/runtime/junit/ScenarioOutlineRunner.java b/junit/src/main/java/cucumber/runtime/junit/ScenarioOutlineRunner.java deleted file mode 100644 index b4b801917f..0000000000 --- a/junit/src/main/java/cucumber/runtime/junit/ScenarioOutlineRunner.java +++ /dev/null @@ -1,40 +0,0 @@ -package cucumber.runtime.junit; - -import cucumber.runtime.Runtime; -import cucumber.runtime.model.CucumberExamples; -import cucumber.runtime.model.CucumberScenarioOutline; -import org.junit.runner.Description; -import org.junit.runner.Runner; -import org.junit.runners.Suite; -import org.junit.runners.model.InitializationError; - -import java.util.ArrayList; - -class ScenarioOutlineRunner extends Suite { - private final CucumberScenarioOutline cucumberScenarioOutline; - private Description description; - - public ScenarioOutlineRunner(Runtime runtime, CucumberScenarioOutline cucumberScenarioOutline, JUnitReporter jUnitReporter) throws InitializationError { - super(null, new ArrayList()); - this.cucumberScenarioOutline = cucumberScenarioOutline; - for (CucumberExamples cucumberExamples : cucumberScenarioOutline.getCucumberExamplesList()) { - getChildren().add(new ExamplesRunner(runtime, cucumberExamples, jUnitReporter)); - } - } - - @Override - public String getName() { - return cucumberScenarioOutline.getVisualName(); - } - - @Override - public Description getDescription() { - if (description == null) { - description = Description.createSuiteDescription(getName(), cucumberScenarioOutline.getGherkinModel()); - for (Runner child : getChildren()) { - description.addChild(describeChild(child)); - } - } - return description; - } -} diff --git a/junit/src/main/java/cucumber/runtime/junit/SuiteWithName.java b/junit/src/main/java/cucumber/runtime/junit/SuiteWithName.java new file mode 100644 index 0000000000..e792f361d4 --- /dev/null +++ b/junit/src/main/java/cucumber/runtime/junit/SuiteWithName.java @@ -0,0 +1,48 @@ +package cucumber.runtime.junit; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.runner.Runner; +import org.junit.runners.Suite; +import org.junit.runners.model.InitializationError; + +import cucumber.runtime.Runtime; +import cucumber.runtime.model.CucumberExamples; +import cucumber.runtime.model.CucumberScenario; +import cucumber.runtime.model.CucumberScenarioOutline; + +class SuiteWithName extends Suite { + private final String name; + + public static SuiteWithName newSuite(Class testClass, Runtime runtime, CucumberScenarioOutline cucumberScenarioOutline, JUnitReporter jUnitReporter, NameProvider nameProvider) throws InitializationError { + List children = new ArrayList(); + for (CucumberExamples cucumberExamples : cucumberScenarioOutline.getCucumberExamplesList()) { + children.add(newSuite(testClass, runtime, cucumberExamples, jUnitReporter, nameProvider)); + } + String name = nameProvider.getName(cucumberScenarioOutline); + return new SuiteWithName(testClass, children, name); + } + + public static SuiteWithName newSuite(Class testClass, Runtime runtime, CucumberExamples cucumberExamples, JUnitReporter jUnitReporter, NameProvider nameProvider) throws InitializationError { + List children = new ArrayList(); + List exampleScenarios = cucumberExamples.createExampleScenarios(); + for (CucumberScenario scenario : exampleScenarios) { + String name = nameProvider.getName(scenario); + children.add(new ExecutionUnitRunner(testClass, runtime, name, scenario, jUnitReporter)); + } + String name = nameProvider.getName(cucumberExamples.getExamples()); + return new SuiteWithName(testClass, children, name); + } + + private SuiteWithName(Class testClass, List runners, String name) throws InitializationError { + super(testClass, runners); + this.name = name; + } + + @Override + public String getName() { + return name; + } + +} diff --git a/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java b/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java index 99e82c5008..666a6fbd0a 100644 --- a/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java +++ b/junit/src/test/java/cucumber/runtime/junit/CucumberTest.java @@ -1,22 +1,25 @@ package cucumber.runtime.junit; -import cucumber.annotation.DummyWhen; -import cucumber.api.CucumberOptions; -import cucumber.api.junit.Cucumber; -import cucumber.runtime.CucumberException; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.runner.Description; import org.junit.runner.RunWith; import org.junit.runners.model.InitializationError; -import java.io.File; -import java.io.IOException; -import java.util.List; - -import static java.util.Collections.emptyList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import cucumber.annotation.DummyWhen; +import cucumber.api.CucumberOptions; +import cucumber.api.junit.Cucumber; +import cucumber.runtime.CucumberException; public class CucumberTest { @@ -39,17 +42,38 @@ public void ensureOriginalDirectory() { @Test public void finds_features_based_on_implicit_package() throws IOException, InitializationError { Cucumber cucumber = new Cucumber(ImplicitFeatureAndGluePath.class); - assertEquals(3, cucumber.getChildren().size()); - assertEquals("Feature: FA", cucumber.getChildren().get(0).getName()); + assertEquals(4, cucumber.getChildren().size()); + assertEquals("FA", cucumber.getChildren().get(0).getName()); } @Test public void finds_features_based_on_explicit_root_package() throws IOException, InitializationError { Cucumber cucumber = new Cucumber(ExplicitFeaturePath.class); - assertEquals(3, cucumber.getChildren().size()); - assertEquals("Feature: FA", cucumber.getChildren().get(0).getName()); + assertEquals(4, cucumber.getChildren().size()); + assertEquals("FA", cucumber.getChildren().get(0).getName()); + } + + @Test + public void allRunnersShouldHaveUniqueDescription() throws Exception { + Cucumber cucumber = new Cucumber(ExplicitFeaturePath.class); + Set descriptions = getRunnerDescriptions(cucumber.getDescription()); + // one for each scenario and example + assertEquals(7, descriptions.size()); } + private Set getRunnerDescriptions(Description description) { + Set result = new HashSet(); + if (description.isSuite()) { + for (Description child : description.getChildren()) { + result.addAll(getRunnerDescriptions(child)); + } + } else { + result.add(description); + } + return result; + } + + @Test public void testThatParsingErrorsIsNicelyReported() throws Exception { try { @@ -63,8 +87,7 @@ public void testThatParsingErrorsIsNicelyReported() throws Exception { @Test public void finds_no_features_when_explicit_feature_path_has_no_features() throws IOException, InitializationError { Cucumber cucumber = new Cucumber(ExplicitFeaturePathWithNoFeatures.class); - List children = cucumber.getChildren(); - assertEquals(emptyList(), children); + assertEquals(emptyList(), cucumber.getChildren()); } @RunWith(Cucumber.class) diff --git a/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java b/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java deleted file mode 100644 index 345117e80d..0000000000 --- a/junit/src/test/java/cucumber/runtime/junit/ExecutionUnitRunnerTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package cucumber.runtime.junit; - -import cucumber.runtime.io.ClasspathResourceLoader; -import cucumber.runtime.model.CucumberFeature; -import cucumber.runtime.model.CucumberScenario; -import gherkin.formatter.model.Step; -import org.junit.Test; -import org.junit.runner.Description; - -import java.util.Collections; -import java.util.List; - -import static java.util.Arrays.asList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -public class ExecutionUnitRunnerTest { - @Test - public void shouldAssignUnequalDescriptionsToDifferentOccurrencesOfSameStepInAScenario() throws Exception { - List features = CucumberFeature.load( - new ClasspathResourceLoader(this.getClass().getClassLoader()), - asList("cucumber/runtime/junit/fb.feature"), - Collections.emptyList() - ); - - ExecutionUnitRunner runner = new ExecutionUnitRunner( - null, - (CucumberScenario) features.get(0).getFeatureElements().get(0), - null - ); - - // fish out the two occurrences of the same step and check whether we really got them - Step stepOccurrence1 = runner.getChildren().get(0); - Step stepOccurrence2 = runner.getChildren().get(2); - assertEquals(stepOccurrence1.getName(), stepOccurrence2.getName()); - - // then check that the descriptions are unequal - Description runnerDescription = runner.getDescription(); - - Description stepDescription1 = runnerDescription.getChildren().get(0); - Description stepDescription2 = runnerDescription.getChildren().get(2); - - assertFalse("Descriptions must not be equal.", stepDescription1.equals(stepDescription2)); - } - - @Test - public void shouldIncludeScenarioNameAsClassNameInStepDescriptions() throws Exception { - List features = CucumberFeature.load( - new ClasspathResourceLoader(this.getClass().getClassLoader()), - asList("cucumber/runtime/junit/feature_with_same_steps_in_different_scenarios.feature"), - Collections.emptyList() - ); - - ExecutionUnitRunner runner = new ExecutionUnitRunner( - null, - (CucumberScenario) features.get(0).getFeatureElements().get(0), - null - ); - - // fish out the data from runner - Step step = runner.getChildren().get(0); - Description runnerDescription = runner.getDescription(); - Description stepDescription = runnerDescription.getChildren().get(0); - - assertEquals("description includes scenario name as class name", runner.getName(), stepDescription.getClassName()); - assertEquals("description includes step keyword and name as method name", step.getKeyword() + step.getName(), stepDescription.getMethodName()); - } -} diff --git a/junit/src/test/java/cucumber/runtime/junit/JUnitReporterTest.java b/junit/src/test/java/cucumber/runtime/junit/JUnitReporterTest.java index 1ad142fb37..f1ba2336c9 100644 --- a/junit/src/test/java/cucumber/runtime/junit/JUnitReporterTest.java +++ b/junit/src/test/java/cucumber/runtime/junit/JUnitReporterTest.java @@ -1,23 +1,22 @@ package cucumber.runtime.junit; -import cucumber.api.PendingException; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import gherkin.formatter.Formatter; import gherkin.formatter.Reporter; import gherkin.formatter.model.Result; + import org.junit.Test; -import org.junit.internal.runners.model.EachTestNotifier; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import cucumber.api.PendingException; public class JUnitReporterTest { @@ -35,6 +34,7 @@ public void resultWithError() { createRunNotifier(description); jUnitReporter.result(result); + jUnitReporter.finishExecutionUnit(); ArgumentCaptor failureArgumentCaptor = ArgumentCaptor.forClass(Failure.class); verify(runNotifier).fireTestFailure(failureArgumentCaptor.capture()); @@ -47,57 +47,46 @@ public void resultWithError() { @Test public void result_with_undefined_step_non_strict() { createNonStrictReporter(); - EachTestNotifier stepNotifier = mock(EachTestNotifier.class); - jUnitReporter.stepNotifier = stepNotifier; - + createDefaultRunNotifier(); jUnitReporter.result(Result.UNDEFINED); + jUnitReporter.finishExecutionUnit(); - verify(stepNotifier, times(0)).fireTestStarted(); - verify(stepNotifier, times(0)).fireTestFinished(); - verify(stepNotifier, times(0)).addFailure(Matchers.any(Throwable.class)); - verify(stepNotifier).fireTestIgnored(); + verify(runNotifier, never()).fireTestStarted(any(Description.class)); + verify(runNotifier, never()).fireTestFinished(any(Description.class)); + verify(runNotifier, never()).fireTestFailure(any(Failure.class)); + verify(runNotifier).fireTestIgnored(any(Description.class)); } @Test public void result_with_undefined_step_strict() { createStrictReporter(); createDefaultRunNotifier(); - EachTestNotifier stepNotifier = mock(EachTestNotifier.class); - jUnitReporter.stepNotifier = stepNotifier; - EachTestNotifier executionUnitNotifier = mock(EachTestNotifier.class); - jUnitReporter.executionUnitNotifier = executionUnitNotifier; jUnitReporter.result(Result.UNDEFINED); + jUnitReporter.finishExecutionUnit(); - verify(stepNotifier, times(0)).fireTestStarted(); - verify(stepNotifier, times(0)).fireTestFinished(); - verifyAddFailureWithPendingException(stepNotifier); - verifyAddFailureWithPendingException(executionUnitNotifier); - verify(stepNotifier, times(0)).fireTestIgnored(); - } + verify(runNotifier).fireTestStarted(any(Description.class)); + verify(runNotifier).fireTestFinished(any(Description.class)); + verify(runNotifier).fireTestFailure(any(Failure.class)); + verify(runNotifier, never()).fireTestIgnored(any(Description.class)); - private void verifyAddFailureWithPendingException(EachTestNotifier stepNotifier) { - ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); - verify(stepNotifier).addFailure(captor.capture()); - Throwable error = captor.getValue(); - assertTrue(error instanceof PendingException); } @Test public void result_with_pending_step_non_strict() { createNonStrictReporter(); + createDefaultRunNotifier(); Result result = mock(Result.class); when(result.getError()).thenReturn(new PendingException()); - EachTestNotifier stepNotifier = mock(EachTestNotifier.class); - jUnitReporter.stepNotifier = stepNotifier; - + jUnitReporter.result(result); + jUnitReporter.finishExecutionUnit(); - verify(stepNotifier, times(0)).fireTestStarted(); - verify(stepNotifier, times(0)).fireTestFinished(); - verify(stepNotifier, times(0)).addFailure(Matchers.any(Throwable.class)); - verify(stepNotifier).fireTestIgnored(); + verify(runNotifier, never()).fireTestStarted(any(Description.class)); + verify(runNotifier, never()).fireTestFinished(any(Description.class)); + verify(runNotifier, never()).fireTestFailure(any(Failure.class)); + verify(runNotifier).fireTestIgnored(any(Description.class)); } @Test @@ -107,50 +96,43 @@ public void result_with_pending_step_strict() { Result result = mock(Result.class); when(result.getError()).thenReturn(new PendingException()); - EachTestNotifier stepNotifier = mock(EachTestNotifier.class); - jUnitReporter.stepNotifier = stepNotifier; - EachTestNotifier executionUnitNotifier = mock(EachTestNotifier.class); - jUnitReporter.executionUnitNotifier = executionUnitNotifier; - jUnitReporter.result(result); + jUnitReporter.finishExecutionUnit(); - verify(stepNotifier, times(0)).fireTestStarted(); - verify(stepNotifier, times(0)).fireTestFinished(); - verifyAddFailureWithPendingException(stepNotifier); - verifyAddFailureWithPendingException(executionUnitNotifier); - verify(stepNotifier, times(0)).fireTestIgnored(); + verify(runNotifier).fireTestStarted(any(Description.class)); + verify(runNotifier).fireTestFinished(any(Description.class)); + verify(runNotifier).fireTestFailure(any(Failure.class)); + verify(runNotifier, never()).fireTestIgnored(any(Description.class)); } @Test public void result_without_error_non_strict() { createNonStrictReporter(); + createDefaultRunNotifier(); Result result = mock(Result.class); - EachTestNotifier stepNotifier = mock(EachTestNotifier.class); - jUnitReporter.stepNotifier = stepNotifier; - jUnitReporter.result(result); + jUnitReporter.finishExecutionUnit(); - verify(stepNotifier).fireTestStarted(); - verify(stepNotifier).fireTestFinished(); - verify(stepNotifier, times(0)).addFailure(Matchers.any(Throwable.class)); - verify(stepNotifier, times(0)).fireTestIgnored(); + verify(runNotifier).fireTestStarted(any(Description.class)); + verify(runNotifier).fireTestFinished(any(Description.class)); + verify(runNotifier, never()).fireTestFailure(any(Failure.class)); + verify(runNotifier, never()).fireTestIgnored(any(Description.class)); } @Test public void result_without_error_strict() { createStrictReporter(); + createDefaultRunNotifier(); Result result = mock(Result.class); - EachTestNotifier stepNotifier = mock(EachTestNotifier.class); - jUnitReporter.stepNotifier = stepNotifier; - jUnitReporter.result(result); + jUnitReporter.finishExecutionUnit(); - verify(stepNotifier).fireTestStarted(); - verify(stepNotifier).fireTestFinished(); - verify(stepNotifier, times(0)).addFailure(Matchers.any(Throwable.class)); - verify(stepNotifier, times(0)).fireTestIgnored(); + verify(runNotifier).fireTestStarted(any(Description.class)); + verify(runNotifier).fireTestFinished(any(Description.class)); + verify(runNotifier, never()).fireTestFailure(any(Failure.class)); + verify(runNotifier, never()).fireTestIgnored(any(Description.class)); } private void createDefaultRunNotifier() { diff --git a/junit/src/test/resources/cucumber/runtime/junit/fc.feature b/junit/src/test/resources/cucumber/runtime/junit/fc.feature new file mode 100644 index 0000000000..34211ed0f5 --- /dev/null +++ b/junit/src/test/resources/cucumber/runtime/junit/fc.feature @@ -0,0 +1,15 @@ +Feature: FC +# Scenario outline feature + + Scenario Outline: + When foo + Then bar + + Examples: + | x | y | + | 1 | one | + | 2 | two | + + Examples: + | x | y | + | 3 | three |