Skip to content

Commit 8798dbe

Browse files
authored
Add ReplaceCamelCaseAndUnderscoreAndNumber DisplayNameGenerator (#793 / #819)
This extension handles method names with CamelCase, underscore, and numbers. The aim is to simplify unit test display names. Instead of using the method annotation `org.junit.jupiter.api.DisplayName`, we can just use the class annotation `org.junit.jupiter.api.DisplayNameGeneration` and only use the method annotation if needed. This generator follows 3 rules: * Each uppercase letter is turned into its lowercase value prepended by a space. * Each underscore is turned into a space. Words bounded by underscores or just starting with underscore are not transformed. Usually, these words represent classes or variables. * Each number is prepended by a space. Closes: #793 PR: #819
1 parent f5dca5e commit 8798dbe

16 files changed

Lines changed: 615 additions & 0 deletions

CONTRIBUTING.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ Classes usually belong into one of these packages:
9191
* `...json` - JSON argument sources for Jupiter's `@ParameterizedTest`
9292
* `...params` - extensions for Jupiter's `@ParameterizedTest`
9393
* `...resource` - extensions for injecting resources
94+
* `...displaynamegenerator` - implementations of `org.junit.jupiter.api.DisplayNameGenerator`
9495
* `org.junitpioneer.vintage` - extensions to older JUnit versions
9596

9697
If none of them is a good fit, we'll find one together.

README.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ The least we can do is to thank them and list some of their accomplishments here
123123

124124
==== 2024
125125

126+
* https://github.com/FanJups[Fanon Jupkwo (FanJups)] added a new display name generator extending `org.junit.jupiter.api.DisplayNameGenerator.Standard` to support CamelCase, underscores, and numbers: https://junit-pioneer.org/docs/replace-camelcase-and-underscore-and-number/[ReplaceCamelCaseAndUnderscoreAndNumber] (#793 / #819)
126127
* https://github.com/TWiStErRob[Papp Róbert (TWiStErRob)] updated Gradle and GitHub Actions tooling to latest. (#803 / #804 / #805)
127128
* https://github.com/boris-faniuk-n26[Boris Faniuk] contributed to `@RetryingTest` extension (#821)
128129

docs/docs-nav.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# * the entries are sorted lexicographically by their title (i.e. not their slug)
44
- title: JUnit Pioneer Extensions
55
children:
6+
- title: "DisplayName generator to replace CamelCase, underscore and numbers"
7+
url: /docs/replace-camelcase-and-underscore-and-number/
68
- title: "Cartesian Product of Parameters"
79
url: /docs/cartesian-product/
810
- title: "Clear, Set, and Restore System Properties"
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
:page-title: ReplaceCamelCaseAndUnderscoreAndNumber
2+
:page-description: New display name generator extending `org.junit.jupiter.api.DisplayNameGenerator.Standard` to support CamelCase, underscores and numbers
3+
:xp-demo-dir: ../src/demo/java
4+
:demo: {xp-demo-dir}/org/junitpioneer/jupiter/displaynamegenerator/ReplaceCamelCaseAndUnderscoreAndNumberDemo.java
5+
6+
The aim is to simplify unit test display names for method names combining CamelCase, underscore and numbers.
7+
Instead of using the method annotation `org.junit.jupiter.api.DisplayName`, we can just use the class annotation `org.junit.jupiter.api.DisplayNameGeneration` and only use the method annotation if needed.
8+
9+
Applying `@DisplayNameGeneration(ReplaceCamelCaseAndUnderscoreAndNumber.class)` to the test class will provide human-readable descriptions for test methods not annotated with `@DisplayName`.
10+
11+
=== 3 key rules:
12+
* Each uppercase letter is turned into its lowercase value prepended by a space.
13+
* Each underscore is turned into a space. Words bounded by underscores or just starting with underscore are not transformed. Usually, these words represent classes or variables.
14+
* Each number is prepended by a space.
15+
16+
== `@DisplayNameGeneration(ReplaceCamelCaseAndUnderscoreAndNumber.class)`
17+
18+
Usage Example:
19+
20+
[source,java,indent=0]
21+
----
22+
include::{demo}[tag=class_using_replace_camel_case_and_underscore_and_number_generator]
23+
----
24+
25+
=== Method 1
26+
27+
Generated display name: `Should return error when maxResults is negative`
28+
29+
[source,java,indent=0]
30+
----
31+
include::{demo}[tag=shouldReturnErrorWhen_maxResults_IsNegative]
32+
----
33+
34+
35+
=== Method 2
36+
37+
Generated display name: `Should create limit with range (String)`
38+
39+
[source,java,indent=0]
40+
----
41+
include::{demo}[tag=shouldCreateLimitWithRange]
42+
----
43+
44+
45+
=== Method 3
46+
47+
Generated display name: `Should return 5 errors (int)`
48+
49+
[source,java,indent=0]
50+
----
51+
include::{demo}[tag=shouldReturn5Errors]
52+
----
53+
54+
55+
=== Method 4
56+
57+
Generated display name: `Should return 5 errors`
58+
59+
[source,java,indent=0]
60+
----
61+
include::{demo}[tag=shouldReturn5errors_no_params]
62+
----
63+
64+
65+
=== Method 5
66+
67+
Generated display name: `Should return 23 errors`
68+
69+
[source,java,indent=0]
70+
----
71+
include::{demo}[tag=shouldReturn23Errors]
72+
----
73+
74+
75+
=== Method 6
76+
77+
Generated display name: `Should return the value of maxResults`
78+
79+
[source,java,indent=0]
80+
----
81+
include::{demo}[tag=shouldReturnTheValueOf_maxResults]
82+
----
83+
84+
85+
=== Method 7
86+
87+
Generated display name: `Should return the number of errors as numberOfErrors inferior or equal to 5 (String)`
88+
89+
[source,java,indent=0]
90+
----
91+
include::{demo}[tag=shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo5]
92+
----
93+
94+
95+
=== Method 8
96+
97+
Generated display name: `Should return the number of errors as numberOfErrors inferior or equal to 15`
98+
99+
[source,java,indent=0]
100+
----
101+
include::{demo}[tag=shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo15]
102+
----
103+
104+
=== Method 9
105+
106+
Generated display name: `@DisplayName prevails`
107+
108+
[source,java,indent=0]
109+
----
110+
include::{demo}[tag=testDisplayNamePrevails]
111+
----
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2016-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junitpioneer.jupiter.displaynamegenerator;
12+
13+
import org.junit.jupiter.api.DisplayName;
14+
import org.junit.jupiter.api.DisplayNameGeneration;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.params.ParameterizedTest;
17+
import org.junit.jupiter.params.provider.ValueSource;
18+
19+
public class ReplaceCamelCaseAndUnderscoreAndNumberDemo {
20+
21+
// tag::class_using_replace_camel_case_and_underscore_and_number_generator[]
22+
23+
@DisplayNameGeneration(ReplaceCamelCaseAndUnderscoreAndNumber.class)
24+
class ReplaceCamelCaseAndUnderscoreAndNumberStyleTest {
25+
26+
// tag::shouldReturnErrorWhen_maxResults_IsNegative[]
27+
@Test
28+
void shouldReturnErrorWhen_maxResults_IsNegative() {
29+
}
30+
// end::shouldReturnErrorWhen_maxResults_IsNegative[]
31+
32+
// tag::shouldCreateLimitWithRange[]
33+
@ParameterizedTest
34+
@ValueSource(strings = { "", " " })
35+
void shouldCreateLimitWithRange(String input) {
36+
}
37+
// end::shouldCreateLimitWithRange[]
38+
39+
// tag::shouldReturn5Errors[]
40+
@ParameterizedTest
41+
@ValueSource(ints = { 5, 23 })
42+
void shouldReturn5Errors(int input) {
43+
}
44+
// end::shouldReturn5Errors[]
45+
46+
// tag::shouldReturn5errors_no_params[]
47+
@Test
48+
void shouldReturn5errors() {
49+
}
50+
// end::shouldReturn5errors_no_params[]
51+
52+
// tag::shouldReturn23Errors[]
53+
@Test
54+
void shouldReturn23Errors() {
55+
}
56+
// end::shouldReturn23Errors[]
57+
58+
// tag::shouldReturnTheValueOf_maxResults[]
59+
@Test
60+
void shouldReturnTheValueOf_maxResults() {
61+
}
62+
// end::shouldReturnTheValueOf_maxResults[]
63+
64+
// tag::shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo5[]
65+
@ParameterizedTest
66+
@ValueSource(strings = { "", " " })
67+
void shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo5(String input) {
68+
}
69+
// end::shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo5[]
70+
71+
// tag::shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo15[]
72+
@Test
73+
void shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo15() {
74+
}
75+
// end::shouldReturnTheNumberOfErrorsAs_numberOfErrors_InferiorOrEqualTo15[]
76+
77+
// tag::testDisplayNamePrevails[]
78+
@DisplayName("@DisplayName prevails")
79+
@Test
80+
void testDisplayNamePrevails() {
81+
}
82+
// end::testDisplayNamePrevails[]
83+
84+
}
85+
86+
// end::class_using_replace_camel_case_and_underscore_and_number_generator[]
87+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Package containing demonstration tests for corresponding package in the main project.
3+
*/
4+
5+
package org.junitpioneer.jupiter.displaynamegenerator;

src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
exports org.junitpioneer.jupiter.params;
2525
exports org.junitpioneer.jupiter.json;
2626
exports org.junitpioneer.jupiter.converter;
27+
exports org.junitpioneer.jupiter.displaynamegenerator;
2728

2829
opens org.junitpioneer.vintage to org.junit.platform.commons;
2930
opens org.junitpioneer.jupiter to org.junit.platform.commons;
@@ -33,6 +34,7 @@
3334
opens org.junitpioneer.jupiter.resource to org.junit.platform.commons;
3435
opens org.junitpioneer.jupiter.json to org.junit.platform.commons, com.fasterxml.jackson.databind;
3536
opens org.junitpioneer.jupiter.converter to org.junit.platform.commons;
37+
opens org.junitpioneer.jupiter.displaynamegenerator to org.junit.platform.commons;
3638

3739
provides org.junit.platform.launcher.TestExecutionListener
3840
with org.junitpioneer.jupiter.issue.IssueExtensionExecutionListener;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2016-2023 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junitpioneer.jupiter.displaynamegenerator;
12+
13+
import java.lang.reflect.Method;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
import org.junit.jupiter.api.DisplayNameGenerator;
18+
19+
/**
20+
* <p>A class extending {@linkplain DisplayNameGenerator.Standard }.</p>
21+
*
22+
* <p>This extension handles method names with CamelCase, underscore and numbers.</p>
23+
*
24+
* <p>The aim is to simplify unit test display names. Instead of using this method annotation {@linkplain org.junit.jupiter.api.DisplayName },
25+
* we can just use this class annotation {@linkplain org.junit.jupiter.api.DisplayNameGeneration } and use that method annotation if needed.
26+
* </p>
27+
*
28+
* <p>This generator follows 3 rules:</p>
29+
*
30+
* <ul>
31+
* <li>Each uppercase letter is turned into its lowercase value prepended by space.</li>
32+
* <li>Each underscore is turned into space. Words bounded by underscores or just starting with underscore are not transformed. Usually these words represent classes, variables...</li>
33+
* <li>Each number is prepended by space.</li>
34+
* </ul>
35+
* <p>
36+
* Usage example:
37+
*
38+
* <pre>
39+
*
40+
* {@code @DisplayNameGeneration(ReplaceCamelCaseAndUnderscoreAndNumber.class)}
41+
* class ExampleTest {}
42+
* </pre>
43+
*
44+
* @since 2.3.0
45+
* @see org.junit.jupiter.api.DisplayNameGenerator.Standard
46+
* @see <a href="https://junit-pioneer.org/docs/replace-camelcase-and-underscore-and-number">Usage example of ReplaceCamelCaseAndUnderscoreAndNumber</a>
47+
*/
48+
public class ReplaceCamelCaseAndUnderscoreAndNumber extends DisplayNameGenerator.Standard {
49+
50+
public static final DisplayNameGenerator INSTANCE = new ReplaceCamelCaseAndUnderscoreAndNumber();
51+
52+
private ReplaceCamelCaseAndUnderscoreAndNumber() {
53+
}
54+
55+
@Override
56+
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
57+
if (hasParameters(testMethod)) {
58+
return replaceCamelCaseAndUnderscoreAndNumber(testMethod.getName()) + " "
59+
+ DisplayNameGenerator.parameterTypesAsString(testMethod);
60+
}
61+
return replaceCamelCaseAndUnderscoreAndNumber(testMethod.getName());
62+
}
63+
64+
private String replaceCamelCaseAndUnderscoreAndNumber(String input) {
65+
// Remove leading underscore(s)
66+
var sanitized = input.replaceAll("^_+(.*)$", "$1");
67+
List<String> list = new ArrayList<>();
68+
String[] split = sanitized.split("_");
69+
for (int i = 0; i < split.length; i++) {
70+
if (i % 2 == 1) {
71+
// If parity is odd, we are between two underscores, no formatting necessary
72+
list.add(split[i]);
73+
} else {
74+
list.add(formatCamelCase(split[i]));
75+
}
76+
}
77+
// Some cases can lead to double spaces - i.e.: underscore (closing) followed by capital letter
78+
var joined = String.join(" ", list).replaceAll("\\s{2}", " ");
79+
// Capitalize the first letter
80+
return joined.substring(0, 1).toUpperCase() + joined.substring(1);
81+
}
82+
83+
private String formatCamelCase(String in) {
84+
return in.replaceAll("(\\d+)", " $1").replaceAll("([A-Z]+)", " $1").toLowerCase();
85+
}
86+
87+
private boolean hasParameters(Method method) {
88+
return method.getParameterCount() > 0;
89+
}
90+
91+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* This package contains custom display name generators.
3+
*
4+
* <p>See the following types for details:</p>
5+
*
6+
* <ul>
7+
* <li>{@link org.junitpioneer.jupiter.displaynamegenerator.ReplaceCamelCaseAndUnderscoreAndNumber}</li>
8+
* </ul>
9+
*
10+
* @see org.junit.jupiter.api.DisplayNameGenerator
11+
*/
12+
13+
package org.junitpioneer.jupiter.displaynamegenerator;

src/test/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
exports org.junitpioneer.jupiter.params;
2525
exports org.junitpioneer.jupiter.json;
2626
exports org.junitpioneer.jupiter.converter;
27+
exports org.junitpioneer.jupiter.displaynamegenerator;
2728

2829
opens org.junitpioneer.vintage to org.junit.platform.commons;
2930
opens org.junitpioneer.jupiter to org.junit.platform.commons, nl.jqno.equalsverifier;
@@ -33,6 +34,7 @@
3334
opens org.junitpioneer.jupiter.resource to org.junit.platform.commons;
3435
opens org.junitpioneer.jupiter.json to org.junit.platform.commons, com.fasterxml.jackson.databind;
3536
opens org.junitpioneer.jupiter.converter to org.junit.platform.commons;
37+
opens org.junitpioneer.jupiter.displaynamegenerator to org.junit.platform.commons;
3638

3739
provides org.junit.platform.launcher.TestExecutionListener
3840
with org.junitpioneer.jupiter.issue.IssueExtensionExecutionListener;

0 commit comments

Comments
 (0)