Skip to content

Commit ccfbd91

Browse files
committed
[JUnit Platform Engine] Add Surefire naming strategy
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 e774093 commit ccfbd91

File tree

7 files changed

+144
-31
lines changed

7 files changed

+144
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Added
14+
- [JUnit Platform Engine] Add surefire naming strategy
1315

1416
## [7.22.2] - 2025-05-12
1517
### 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: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ cucumber.filter.tags= # a cucumber tag
416416
cucumber.glue= # comma separated package names.
417417
# example: com.example.glue
418418
419-
cucumber.junit-platform.naming-strategy= # long or short.
419+
cucumber.junit-platform.naming-strategy= # long, short or surefire.
420420
# default: short
421421
# include parent descriptor name in test descriptor.
422422
@@ -429,6 +429,11 @@ cucumber.junit-platform.naming-strategy.long.example-name= # number or pickl
429429
# default: number
430430
# Use example number or pickle name for examples when
431431
# long naming strategy is used
432+
433+
cucumber.junit-platform.naming-strategy.surefire.example-name= # number or pickle.
434+
# default: number
435+
# Use example number or pickle name for examples when
436+
# surefire naming strategy is used
432437
433438
cucumber.plugin= # comma separated plugin strings.
434439
# example: pretty, json:path/to/report.json

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

Lines changed: 58 additions & 28 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 makes sense. The feature name will be rendered as the
140+
* class name. The long name without the feature will be rendered as the
141+
* 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";
@@ -143,36 +148,61 @@ public final class Constants {
143148
* Property name used to configure the naming strategy of examples in case
144149
* of short naming strategy: {@value}
145150
* <p>
146-
* Value must be one of {@code number} or {@code pickle}. By default,
147-
* numbers are used.
148-
* <p>
149-
* When set to {@code pickle} the pickle name is used. So for scenario name
150-
* {@code Adding <a> and <b>} and example with params {@code a = 10} and
151-
* {@code b = 20} the following name would be produced:
151+
* Value must be one of {@code number}, {@code pickle}, or
152+
* {@code number-and-pickle-if-parameterized}. By default,
153+
* {@code number-and-pickle-if-parameterized} is used.
154+
* <ul>
155+
* <li>When set to {@code number} examples are numbered. So the first
156+
* example of the first examples section would be named {@code #1.1}
157+
* <li>When set to {@code pickle} the pickle name is used. So for scenario
158+
* name {@code Adding <a> and <b>} and example with params {@code a = 10}
159+
* and {@code b = 20} the following name would be produced:
152160
* {@code Adding 10 and 20}.
153-
* <p>
154-
* Using example numbers works well in all scenarios, but if parameterized
155-
* scenario names are used consistently, the pickle name provides more
156-
* clarity.
161+
* <li>When set to {@code number-and-pickle-if-parameterized} the name would
162+
* be rendered as {@code #1.1: Adding 10 and 20}.
163+
* </ul>
157164
*/
158165
@API(status = Status.EXPERIMENTAL, since = "7.16.2")
159166
public static final String JUNIT_PLATFORM_SHORT_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME = "cucumber.junit-platform.naming-strategy.short.example-name";
160167

161168
/**
162169
* Property name used to configure the naming strategy of examples in case
163-
* of long naming strategy: {@value}
164-
* <p>
165-
* Value must be one of {@code number} or {@code pickle}. By default,
166-
* numbers are used.
170+
* of surefire naming strategy: {@value}
167171
* <p>
168-
* When set to {@code pickle} the pickle name is used. So for scenario name
169-
* {@code Adding <a> and <b>} and example with params {@code a = 10} and
170-
* {@code b = 20} the following name would be produced:
171-
* {@code Feature Name - Rule Name - Adding <a> and <b> - Examples Name - Adding 10 and 20}.
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+
189+
/**
190+
* Property name used to configure the naming strategy of examples in case
191+
* of long naming strategy: {@value}
172192
* <p>
173-
* Using example numbers works well in all scenarios, but if parameterized
174-
* scenario names are used consistently, the pickle name provides more
175-
* clarity.
193+
* Value must be one of {@code number}, {@code pickle}, or
194+
* {@code number-and-pickle-if-parameterized}. By default,
195+
* {@code number-and-pickle-if-parameterized} is used.
196+
* <ul>
197+
* <li>When set to {@code number} examples are numbered. So the first
198+
* example of the first examples section would be named {@code #1.1}
199+
* <li>When set to {@code pickle} the pickle name is used. So for scenario
200+
* name {@code Adding <a> and <b>} and example with params {@code a = 10}
201+
* and {@code b = 20} the following name would be produced:
202+
* {@code Adding 10 and 20}.
203+
* <li>When set to {@code number-and-pickle-if-parameterized} the name would
204+
* be rendered as {@code #1.1: Adding 10 and 20}.
205+
* </ul>
176206
*/
177207
@API(status = Status.EXPERIMENTAL, since = "7.16.2")
178208
public static final String JUNIT_PLATFORM_LONG_NAMING_STRATEGY_EXAMPLE_NAME_PROPERTY_NAME = "cucumber.junit-platform.naming-strategy.long.example-name";

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::exampleNumberStrategy)
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::exampleNumberStrategy)
43+
.apply(DefaultNamingStrategyProvider::surefireStrategy);
44+
}
3445
};
3546

3647
abstract NamingStrategy create(ConfigurationParameters configuration);
@@ -100,4 +111,43 @@ private static String longStrategy(Node node, String currentNodeName) {
100111

101112
return builder.toString();
102113
}
114+
115+
private static String surefireStrategy(Node node, String currentNodeName) {
116+
// Surefire uses the parents of test nodes to determine the class name.
117+
// As we want the class name to match the feature name we name the
118+
// parents of the test containing nodes after the feature.
119+
if (node instanceof Node.Examples || node instanceof Node.Rule) {
120+
return nameOrKeyword(findFeature(node));
121+
}
122+
// Scenarios and examples names are used by surefire to populate the
123+
// testname. We want this to be long, but without the feature name
124+
// because that will be used for the class name
125+
if (node instanceof Node.Scenario || node instanceof Node.Example) {
126+
return longStrategyWithoutFeatureName(node, currentNodeName);
127+
}
128+
// Everything else, can be short, will be ignored by surefire.
129+
return shortStrategy(node, currentNodeName);
130+
}
131+
132+
private static String longStrategyWithoutFeatureName(Node node, String currentNodeName) {
133+
StringBuilder builder = new StringBuilder();
134+
builder.append(currentNodeName);
135+
node = node.getParent().orElse(null);
136+
137+
while (node != null && !(node instanceof Node.Feature)) {
138+
builder.insert(0, " - ");
139+
builder.insert(0, nameOrKeyword(node));
140+
node = node.getParent().orElse(null);
141+
}
142+
return builder.toString();
143+
}
144+
145+
private static Node findFeature(Node node) {
146+
Node candidate = node.getParent().orElse(null);
147+
while (candidate != null) {
148+
node = candidate;
149+
candidate = node.getParent().orElse(null);
150+
}
151+
return node;
152+
}
103153
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ private TestDescriptor getOutline() {
130130
return iterator.next();
131131
}
132132

133+
private TestDescriptor getParameterizedOutline() {
134+
Iterator<? extends TestDescriptor> iterator = getFeature().getChildren().iterator();
135+
iterator.next();
136+
iterator.next();
137+
iterator.next();
138+
return iterator.next();
139+
}
140+
133141
@Test
134142
void example() {
135143
TestDescriptor example = getExample();
@@ -191,6 +199,24 @@ void shortNamesWithPickleNames() {
191199
assertEquals("A scenario outline", example.getDisplayName());
192200
}
193201

202+
@Test
203+
void surefireNames() {
204+
configurationParameters = new MapConfigurationParameters(
205+
JUNIT_PLATFORM_NAMING_STRATEGY_PROPERTY_NAME, "surefire");
206+
207+
TestDescriptor example = getExample();
208+
assertEquals("A scenario outline - With some text - Example #1.1",
209+
example.getDisplayName());
210+
211+
TestDescriptor examples = example.getParent().get();
212+
assertEquals("A feature with scenario outlines",
213+
examples.getDisplayName());
214+
215+
TestDescriptor feature = examples.getParent().get();
216+
assertEquals("A feature with scenario outlines",
217+
examples.getDisplayName());
218+
}
219+
194220
private TestDescriptor getExample() {
195221
return getOutline().getChildren().iterator().next().getChildren().iterator().next();
196222
}

0 commit comments

Comments
 (0)