Skip to content

Commit 7b9e895

Browse files
authored
[JUnit Platform Engine] Add Surefire naming strategy (#3003)
When testing with Surefire using the `long` naming strategy the feature name is included in every error message twice. This can be quite annoying when the name is long. For example: ``` [ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.103 s <<< FAILURE! -- in io.cucumber.skeleton.RunCucumberTest [ERROR] Belly.Belly - a few cukes -- Time elapsed: 0.060 s <<< FAILURE! java.lang.AssertionError: at io.cucumber.skeleton.StepDefinitions.I_have_cukes_in_my_belly(StepDefinitions.java:11) at ✽.I have 42 cukes in my belly(classpath:io/cucumber/skeleton/belly.feature:4) ``` Like wise the xml report by Surefire includes the name twice. ```xml <testcase name="Belly - a few cukes" classname="Belly" time="0.06"> <failure type="java.lang.AssertionError"><![CDATA[java.lang.AssertionError: at io.cucumber.skeleton.StepDefinitions.I_have_cukes_in_my_belly(StepDefinitions.java:11) at ✽.I have 42 cukes in my belly(classpath:io/cucumber/skeleton/belly.feature:4) ]]></failure> ``` By using the `cucumber.junit-platform.naming-strategy=surefire` property when configuring surefire tests will look pretty in surefire too. ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.2</version> <configuration> <properties> <!-- Work around. Surefire does not include enough information to disambiguate between different examples and scenarios. --> <configurationParameters> cucumber.junit-platform.naming-strategy=surefire </configurationParameters> </properties> </configuration> </plugin> ``` For example: ``` [ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.102 s <<< FAILURE! -- in io.cucumber.skeleton.RunCucumberTest [ERROR] Belly.a few cukes -- Time elapsed: 0.059 s <<< FAILURE! java.lang.AssertionError: TODO at io.cucumber.skeleton.StepDefinitions.I_have_cukes_in_my_belly(StepDefinitions.java: ``` And ```xml <testcase name="a few cukes" classname="Belly" time="0.059"> <failure message="TODO" type="java.lang.AssertionError"><![CDATA[java.lang.AssertionError: TODO at io.cucumber.skeleton.StepDefinitions.I_have_cukes_in_my_belly(StepDefinitions.java:10) at ✽.I have 42 cukes in my belly(classpath:io/cucumber/skeleton/belly.feature:4) ]]></failure> ```
1 parent 7960abd commit 7b9e895

File tree

11 files changed

+142
-18
lines changed

11 files changed

+142
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
## [Unreleased]
1313
### Added
1414
- [JUnit Platform Engine, TestNG] Remove framework elements from `UndefinedStepException` stacktrace ([#3002](https://github.com/cucumber/cucumber-jvm/pull/3002) M.P. Korstanje)
15+
- [JUnit Platform Engine] Add `surefire` naming strategy ([#3003](https://github.com/cucumber/cucumber-jvm/pull/3003) M.P. Korstanje)
16+
17+
### Changed
18+
- [JUnit Platform Engine] Use `number-and-pickle-if-parameterized ` example naming strategy by default ([#3004](https://github.com/cucumber/cucumber-jvm/pull/3004) M.P. Korstanje)
1519

1620
## [7.22.2] - 2025-05-12
1721
### Changed

cucumber-archetype/src/main/resources/archetype-resources/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
information to disambiguate between different
8484
examples and scenarios. -->
8585
<configurationParameters>
86-
cucumber.junit-platform.naming-strategy=long
86+
cucumber.junit-platform.naming-strategy=surefire
8787
</configurationParameters>
8888
</properties>
8989
</configuration>

cucumber-archetype/src/test/resources/projects/should-generate-project/reference/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@
8383
information to disambiguate between different
8484
examples and scenarios. -->
8585
<configurationParameters>
86-
cucumber.junit-platform.naming-strategy=long
86+
cucumber.junit-platform.naming-strategy=surefire
8787
</configurationParameters>
8888
</properties>
8989
</configuration>

cucumber-junit-platform-engine/README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ for a brief how to.
5454

5555
Because Surefire and Gradle reports provide the results in a `<Class Name> - <Method Name>`
5656
format, only scenario names or example numbers are reported. This
57-
can make for hard to read reports. To improve the readability of the reports
58-
provide the `cucumber.junit-platform.naming-strategy=long` configuration
59-
parameter. This will include the feature name as part of the test name.
57+
can make for hard to read reports.
58+
59+
To improve the readability of the reports provide the
60+
`cucumber.junit-platform.naming-strategy` configuration parameter to
61+
Gradle. This will include the feature name as part of the test name.
6062

6163
##### Maven
6264

@@ -68,7 +70,7 @@ parameter. This will include the feature name as part of the test name.
6870
<configuration>
6971
<properties>
7072
<configurationParameters>
71-
cucumber.junit-platform.naming-strategy=long
73+
cucumber.junit-platform.naming-strategy=surefire
7274
</configurationParameters>
7375
</properties>
7476
</configuration>
@@ -416,7 +418,7 @@ cucumber.filter.tags= # a cucumber tag
416418
cucumber.glue= # comma separated package names.
417419
# example: com.example.glue
418420
419-
cucumber.junit-platform.naming-strategy= # long or short.
421+
cucumber.junit-platform.naming-strategy= # long, short or surefire.
420422
# default: short
421423
# include parent descriptor name in test descriptor.
422424
@@ -429,6 +431,11 @@ cucumber.junit-platform.naming-strategy.long.example-name= # number, number-
429431
# default: number-and-pickle-if-parameterized
430432
# Use example number or pickle name for examples when
431433
# long naming strategy is used
434+
435+
cucumber.junit-platform.naming-strategy.surefire.example-name= # number or pickle.
436+
# default: number-and-pickle-if-parameterized
437+
# Use example number or pickle name for examples when
438+
# surefire naming strategy is used
432439
433440
cucumber.plugin= # comma separated plugin strings.
434441
# example: pretty, json:path/to/report.json
@@ -615,7 +622,7 @@ Note: any files written by Cucumber will be overwritten during the rerun.
615622
information to disambiguate between different
616623
examples and scenarios. -->
617624
<configurationParameters>
618-
cucumber.junit-platform.naming-strategy=long
625+
cucumber.junit-platform.naming-strategy=surefire
619626
</configurationParameters>
620627
</properties>
621628
</configuration>

cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/Constants.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,20 @@ public final class Constants {
126126
/**
127127
* Property name used to configure the naming strategy: {@value}
128128
* <p>
129-
* Value must be one of {@code long} or {@code short}. By default, short
130-
* names are used.
129+
* Value must be one of {@code long}, {@code short}, or {@code surefire}. By
130+
* default, short names are used.
131131
* <p>
132-
* When long names are used the parent descriptor names are included into
133-
* each test descriptor name. So for example a single example would be
134-
* named:
132+
* When the {@code long} naming strategy is used all parent descriptor names
133+
* are included in each test descriptor name. So for example a single
134+
* example would be named:
135135
* {@code Feature Name - Rule Name - Scenario Name - Examples Name - Example #N }.
136-
* This is useful for tools that only report the test name such as Maven and
137-
* Gradle.
136+
* This is useful for tools that only report the test name such as Gradle.
137+
* <p>
138+
* When the {@code surefire} naming strategy is used nodes are named such
139+
* that the Surefire output makes sense. The feature name will be rendered
140+
* as the class name. The long name without the feature will be rendered as
141+
* the test method name. For example:
142+
* {@code Feature Name.Rule Name - Scenario Name - Examples Name - Example #N}.
138143
*/
139144
@API(status = Status.EXPERIMENTAL, since = "7.0.0")
140145
public static final String JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME = "cucumber.junit-platform.naming-strategy";
@@ -160,6 +165,27 @@ public final class Constants {
160165
@API(status = Status.EXPERIMENTAL, since = "7.16.2")
161166
public static final String JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME = "cucumber.junit-platform.naming-strategy.short.example-name";
162167

168+
/**
169+
* Property name used to configure the naming strategy of examples in case
170+
* of surefire naming strategy: {@value}
171+
* <p>
172+
* Value must be one of {@code number}, {@code pickle}, or
173+
* {@code number-and-pickle-if-parameterized}. By default,
174+
* {@code number-and-pickle-if-parameterized} is used.
175+
* <ul>
176+
* <li>When set to {@code number} examples are numbered. So the first
177+
* example of the first examples section would be named {@code #1.1}
178+
* <li>When set to {@code pickle} the pickle name is used. So for scenario
179+
* name {@code Adding <a> and <b>} and example with params {@code a = 10}
180+
* and {@code b = 20} the following name would be produced:
181+
* {@code Adding 10 and 20}.
182+
* <li>When set to {@code number-and-pickle-if-parameterized} the name would
183+
* be rendered as {@code #1.1: Adding 10 and 20}.
184+
* </ul>
185+
*/
186+
@API(status = Status.EXPERIMENTAL, since = "7.23.0")
187+
public static final String JUNIT_PLATFORM_SUREFIRE_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME = "cucumber.junit-platform.naming-strategy.surefire.example-name";
188+
163189
/**
164190
* Property name used to configure the naming strategy of examples in case
165191
* of long naming strategy: {@value}

cucumber-junit-platform-engine/src/main/java/io/cucumber/junit/platform/engine/DefaultNamingStrategyProvider.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
1313
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
14+
import static io.cucumber.junit.platform.engine.Constants.JUNIT_PLATFORM_SUREFIRE_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME;
1415

1516
enum DefaultNamingStrategyProvider {
1617
LONG {
@@ -31,6 +32,16 @@ NamingStrategy create(ConfigurationParameters configuration) {
3132
.orElse(DefaultNamingStrategyProvider::exampleNumberAndPickleIfParameterizedStrategy)
3233
.apply(DefaultNamingStrategyProvider::shortStrategy);
3334
}
35+
},
36+
37+
SUREFIRE {
38+
@Override
39+
NamingStrategy create(ConfigurationParameters configuration) {
40+
return configuration.get(JUNIT_PLATFORM_SUREFIRE_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME)
41+
.map(DefaultNamingStrategyProvider::parseStrategy)
42+
.orElse(DefaultNamingStrategyProvider::exampleNumberAndPickleIfParameterizedStrategy)
43+
.apply(DefaultNamingStrategyProvider::surefireStrategy);
44+
}
3445
};
3546

3647
abstract NamingStrategy create(ConfigurationParameters configuration);
@@ -125,4 +136,43 @@ private static String longStrategy(Node node, String currentNodeName) {
125136

126137
return builder.toString();
127138
}
139+
140+
private static String surefireStrategy(Node node, String currentNodeName) {
141+
// Surefire uses the parents of test nodes to determine the class name.
142+
// As we want the class name to match the feature name we name the
143+
// parents of the test containing nodes after the feature.
144+
if (node instanceof Node.Examples || node instanceof Node.Rule) {
145+
return nameOrKeyword(findFeature(node));
146+
}
147+
// Scenarios and examples names are used by surefire to populate the
148+
// testname. We want this to be long, but without the feature name
149+
// because that will be used for the class name
150+
if (node instanceof Node.Scenario || node instanceof Node.Example) {
151+
return longStrategyWithoutFeatureName(node, currentNodeName);
152+
}
153+
// Everything else, can be short, will be ignored by surefire.
154+
return shortStrategy(node, currentNodeName);
155+
}
156+
157+
private static String longStrategyWithoutFeatureName(Node node, String currentNodeName) {
158+
StringBuilder builder = new StringBuilder();
159+
builder.append(currentNodeName);
160+
node = node.getParent().orElse(null);
161+
162+
while (node != null && !(node instanceof Node.Feature)) {
163+
builder.insert(0, " - ");
164+
builder.insert(0, nameOrKeyword(node));
165+
node = node.getParent().orElse(null);
166+
}
167+
return builder.toString();
168+
}
169+
170+
private static Node findFeature(Node node) {
171+
Node candidate = node.getParent().orElse(null);
172+
while (candidate != null) {
173+
node = candidate;
174+
candidate = node.getParent().orElse(null);
175+
}
176+
return node;
177+
}
128178
}

cucumber-junit-platform-engine/src/test/java/io/cucumber/junit/platform/engine/FeatureResolverTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,31 @@ void shortNamesWithPickleNamesIfParameterized() {
220220
assertEquals("Example #1.1: A scenario with A", example.getDisplayName());
221221
}
222222

223+
@Test
224+
void surefireNamesWithPickleNamesIfParameterized() {
225+
configurationParameters = new MapConfigurationParameters(
226+
JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "surefire");
227+
228+
// The test node gets the long name, without the feature name.
229+
TestDescriptor example = getParametrizedExample();
230+
assertEquals("A scenario with <example> - Examples - Example #1.1: A scenario with A",
231+
example.getDisplayName());
232+
233+
// The parent node gets the feature name.
234+
TestDescriptor examples = example.getParent().get();
235+
assertEquals("A feature with scenario outlines",
236+
examples.getDisplayName());
237+
238+
// Remaining nodes are named by their short names.
239+
TestDescriptor scenarioOutline = examples.getParent().get();
240+
assertEquals("A scenario with <example>",
241+
scenarioOutline.getDisplayName());
242+
243+
TestDescriptor feature = scenarioOutline.getParent().get();
244+
assertEquals("A feature with scenario outlines",
245+
feature.getDisplayName());
246+
}
247+
223248
private TestDescriptor getExample() {
224249
return getOutline().getChildren().iterator().next().getChildren().iterator().next();
225250
}

cucumber-junit-platform-engine/src/test/resources/io/cucumber/junit/platform/engine/feature-with-outline.feature

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,15 @@ Feature: A feature with scenario outlines
4848
| example |
4949
| A |
5050
| B |
51+
52+
@ScenarioOutlineTag
53+
Scenario Outline: A scenario with <example>
54+
Given a parameterized scenario outline
55+
When it is executed
56+
Then <example> is used
57+
58+
@Example1Tag
59+
Examples:
60+
| example |
61+
| A |
62+
| B |

examples/calculator-java-junit5/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
<!-- Work around. Surefire does not include enough information to disambiguate between different
9393
examples. -->
9494
<configurationParameters>
95-
cucumber.junit-platform.naming-strategy=long
95+
cucumber.junit-platform.naming-strategy=surefire
9696
</configurationParameters>
9797
</properties>
9898
</configuration>

examples/calculator-kotlin-junit5/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
<!-- Work around. Surefire does not include enough information to disambiguate between different
115115
examples. -->
116116
<configurationParameters>
117-
cucumber.junit-platform.naming-strategy=long
117+
cucumber.junit-platform.naming-strategy=surefire
118118
</configurationParameters>
119119
</properties>
120120
</configuration>

examples/spring-java-junit5/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@
111111
<!-- Work around. Surefire does not include enough information to disambiguate between different
112112
examples. -->
113113
<configurationParameters>
114-
cucumber.junit-platform.naming-strategy=long
114+
cucumber.junit-platform.naming-strategy=surefire
115115
</configurationParameters>
116116
</properties>
117117
</configuration>

0 commit comments

Comments
 (0)