Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce named argument set support for @ParameterizedTest #3818

Closed
aws-gibbskt opened this issue May 15, 2024 · 17 comments · Fixed by #3825
Closed

Introduce named argument set support for @ParameterizedTest #3818

aws-gibbskt opened this issue May 15, 2024 · 17 comments · Fixed by #3825

Comments

@aws-gibbskt
Copy link

aws-gibbskt commented May 15, 2024

Summary

I find myself writing the following.

@ParameterizedTest(name = "{0}")

#2301 introduced a way to name an argument, but it feels very unnatural to be used for multiple argument test cases.

The recommendation in that issue is to do the below with the name as defined above.

return Stream.of(
    arguments(named("Test Case Name", firstArg), secondArg, thirdArg),
    arguments(named...)
);

I would much rather have something like:

return Stream.of(
    named("Test Case Name", arguments(firstArg, secondArg, thirdArg)),
    named("Other Case Name", arguments...)
);

Related Issues

Proposal

Provide the ability to name an individual parameterized test invocation by wrapping the Arguments in Named<Arguments> rather than wrapping a specific argument in Named<T>.

@marcphilipp
Copy link
Member

marcphilipp commented May 17, 2024

Team decision: Spike the following:

return Stream.of(
    namedArguments("Test Case Name", firstArg, secondArg, thirdArg),
    namedArguments("Other Case Name", arguments...)
);

where Arguments.namedArguments would return an instance of NamedArguments which implements Named<Arguments> and Arguments and the implementation of Named.getPayload would return this.

@sbrannen sbrannen changed the title Extend the Named<> interface support to ParameterizedTests Arguments generically. Introduce NamedArguments to name a set of arguments in a @ParameterizedTest May 17, 2024
@sbrannen sbrannen self-assigned this May 17, 2024
@scordio
Copy link
Contributor

scordio commented May 17, 2024

@marcphilipp @sbrannen if I understood it correctly, the usage of namedArguments won't require setting any additional attribute for @ParameterizedTest, right?

@sbrannen
Copy link
Member

@scordio, I've already spiked this locally and will push to a feature branch soon.

if I understood it correctly, the usage of namedArguments won't require setting any additional attribute for @ParameterizedTest, right?

That's correct: the goal is that it will work seamlessly, and it does in my current spike.

However, having said that, it does appear that we'll need to introduce a new display name holder and include that in the default display name pattern. Otherwise, I don't yet see how to reliably include this additional information.

Though maybe we can work around that by introducing an internal placeholder for this purpose.

Time will tell...

It's a work in progress. 😉

sbrannen added a commit to sbrannen/junit5 that referenced this issue May 17, 2024
…edTest

This is a proof of concept which demonstrates one way to achieve this.

Running NamedArgumentsSetDemo, results in the following output:

Important Files :: [1] file1=VIP, file2=path2
[2] file1=path3, file2=path4

If we decide to implement this feature, we will need to revisit the
current implementation of ParameterizedTestNameFormatter and consider
introducing a new placeholder for display names for parameterized tests
in order to allow users to specify inclusion of the "name of a set of
arguments".

See junit-team#3818
@scordio
Copy link
Contributor

scordio commented May 17, 2024

Very cool, looking forward to it!

sbrannen added a commit to sbrannen/junit5 that referenced this issue May 17, 2024
@ParameterizedTest

This is a second variant of how this could be implemented, which
revises the implementation in the previous commit so that running
NamedArgumentsSetDemo, now results in the following output:

Important Files
Other Files

With the approach in this commit, if arguments are supplied as
NamedArguments, the display name pattern is completely ignored, and the
display name for the current parameterized invocation is set to the
value returned from NamedArguments#getName.

See junit-team#3818
@sbrannen
Copy link
Member

sbrannen commented May 17, 2024

Current work on this issue can be viewed in the following feature branch.

main...sbrannen:junit5:issues/3818-NamedArguments

I implemented two approaches that we can consider and use for further brainstorming.

The first commit prepends the value returned NamedArguments#getName plus :: to the display name that would otherwise be generated.

This results in display names for two invocations like this (where "VIP" is from the use of named() for a single argument; the first invocation uses namedArguments(); and the second invocation users arguments()):

Important Files :: [1] file1=VIP, file2=path2
[2] file1=path3, file2=path4

Though, in the description of this issue @aws-gibbskt suggested that the "group name" replace what would otherwise be the display name.

The second commit takes that in account and results in display names for two invocations like this (where both invocations use namedArguments()):

Important Files
Other Files

Feedback and further brainstorming is welcome!

@sbrannen
Copy link
Member

In summary, the aforementioned feature branch currently supports a test class like this:

// imports ...
import static org.junit.jupiter.params.provider.Arguments.namedArguments;

class NamedArgumentsSetDemo {

	@ParameterizedTest
	@FieldSource
	void namedArgumentsSet(File file1, File file2, TestInfo testInfo) {
		System.out.println(testInfo.getDisplayName());
	}

	static List<Arguments> namedArgumentsSet = Arrays.asList(
		namedArguments("Important Files", new File("path1"), new File("path2")),
		namedArguments("Other Files", new File("path3"), new File("path4"))
	);

}

Which outputs the following for the display names:

Important Files
Other Files

@marcphilipp
Copy link
Member

I think including the index (by default) would be helpful:

  • [1] Important Files
  • [2] Other Files

@sbrannen
Copy link
Member

I think including the index (by default) would be helpful:

I agree.

I'll make that change.

Though we need to decide if we want this to actually be configurable or hard-coded to do exactly that and only that.

Thoughts?


One compelling reason not to make it configurable is that the use of Named for a single argument completely overrides what would otherwise be included in the display name for that single argument.

Along that line of thinking, I imagined that NamedArguments would also completely override what would otherwise be generated as the display name for the entire invocation.

sbrannen added a commit to sbrannen/junit5 that referenced this issue May 17, 2024
…edTest

This is a third variant of how this could be implemented, which revises
the implementation in the previous commit so that the invocation index
is always included when NamedArguments is used, resulting in the
following output for the NamedArgumentsSetDemo:

[1] Important Files
[2] Other Files

See junit-team#3818
@sbrannen
Copy link
Member

sbrannen commented May 17, 2024

The third commit always includes the invocation index and results in display names for two invocations like this (where both invocations use namedArguments()):

[1] Important Files
[2] Other Files

sbrannen added a commit to sbrannen/junit5 that referenced this issue May 17, 2024
@aws-gibbskt
Copy link
Author

aws-gibbskt commented May 17, 2024

This looks like a great addition to junit and addresses the intent of my request.

[1] Important Files
[2] Other Files

or

Important Files
Other Files

as the default behavior are both fine.

sbrannen added a commit to sbrannen/junit5 that referenced this issue May 18, 2024
Rationale: there is no benefit to implementing Named<Arguments>, since
we never need access to the "payload". The NamedArguments on its own
is sufficient for this use case.

See junit-team#3818
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 18, 2024
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 18, 2024
Running NamedArgumentsSetDemo, now results in the following output for
the two parameterized test methods:

Default display name:

[1] Important Files
[2] Other Files

Custom display name ("{namedArguments} :: {argumentsWithNames}"):

Important Files :: file1=path1, file2=path2
Other Files :: file1=path3, file2=path4

See junit-team#3818
@sbrannen
Copy link
Member

With the latest changes on my feature branch, the following parameterized tests...

@BeforeEach
void printDisplayName(TestInfo testInfo) {
	System.out.println(testInfo.getDisplayName());
}

@ParameterizedTest
@FieldSource("namedArgumentsList")
void defaultNamedArgumentsDisplayName(File file1, File file2) {
}

@ParameterizedTest(name = "{namedArguments} :: {argumentsWithNames}")
@FieldSource("namedArgumentsList")
void customNamedArgumentsDisplayName(File file1, File file2) {
}

static List<Arguments> namedArgumentsList = Arrays.asList(
	namedArguments("Important Files", new File("path1"), new File("path2")),
	namedArguments("Other Files", new File("path3"), new File("path4"))
);

... now output this:

[1] Important Files
[2] Other Files

Important Files :: file1=path1, file2=path2
Other Files :: file1=path3, file2=path4

The default pattern for NamedArguments is:

String DEFAULT_NAMED_ARGUMENTS_DISPLAY_NAME = "[" + INDEX_PLACEHOLDER + "] " + NAMED_ARGUMENTS_PLACEHOLDER;

And that new placeholder is:

String NAMED_ARGUMENTS_PLACEHOLDER = "{namedArguments}";

@sbrannen
Copy link
Member

sbrannen commented May 18, 2024

The reason I posted the above is to discuss naming, because... as we all know...

Naming is hard! ™️ 😉

The new type is called NamedArguments and the new method is Arguments.namedArguments().

I think those names are probably fine as-is, but I'm not happy with the names/values of the constants/placeholders.

When you see @ParameterizedTest(name = "{namedArguments} :: {argumentsWithNames}"), that's very confusing.

What's the difference between "named arguments" and "arguments with names"?

If we go with that "technical" naming strategy, I fear it will be too confusing for users (even with good documentation).

So I'd like to brainstorm about better names for those constants and the placeholder.


Technically, we're talking about a "set of arguments with a name for that entire set."

We are not actually talking about "named arguments".

So, even if we stick with NamedArguments / Arguments.namedArguments(), I don't think we should use that terminology in the constants and placeholder.

We could go very technical with something like "NamedArguments name" (because of NamedArguments#getName()), which could lead to the following.

String NAMED_ARGUMENTS_NAME_PLACEHOLDER = "{namedArgumentsName}";

String DEFAULT_NAMED_ARGUMENTS_DISPLAY_NAME = "[" + INDEX_PLACEHOLDER + "] " + NAMED_ARGUMENTS_NAME_PLACEHOLDER;

But I think that's extremely ugly, and I'm only including it for the sake of argument (pun intended).

Since we're talking about a "set of arguments" or an "argument set", perhaps something like the following would be better.

String ARGUMENT_SET_NAME_PLACEHOLDER = "{argumentSetName}";

String DEFAULT_ARGUMENT_SET_DISPLAY_NAME = "[" + INDEX_PLACEHOLDER + "] " + ARGUMENT_SET_NAME_PLACEHOLDER;

Or perhaps we should refer to this thing as an "argument group".

Any suggestions or brainstorming ideas are welcome! 👍


p.s. Another idea I had was to refer to the name of this new thing as the "invocation name" since you're effectively assigning a name to the current parameterized test invocation when you use namedArguments(). So the placeholder could be {invocationName} or similar if we go that route, but I'm not sure what we'd call the "default XXX display name" pattern in that case, since it would not be a default pattern for all invocations but rather only the default for invocations that make use of namedArguments(). 🤷

@sbrannen sbrannen added this to the 5.11 M3 milestone May 18, 2024
@marcphilipp
Copy link
Member

I'm in favor of introducing a term like "argument set", "invocation name", or "scenario" to refer to. Depending on which we choose, this might also mean renaming namedArguments to sth. else.

Why do we need DEFAULT_NAMED_ARGUMENTS_DISPLAY_NAME/DEFAULT_ARGUMENT_SET_DISPLAY_NAME? Wouldn't DEFAULT_DISPLAY_NAME be changed to [{index}] {argumentSetNameOrArgumentsWithNames}?

@sbrannen
Copy link
Member

sbrannen commented May 19, 2024

Thanks for the feedback, @marcphilipp.

I'm in favor of introducing a term like "argument set", "invocation name", or "scenario" to refer to.

I think we should shy away from "scenario" since that might get confused with "scenario tests" which we don't support, and I basically convinced myself above that "invocation name" is probably too generic.

So, I'm currently leaning toward "argument set" for the terminology and {argumentSetName} for the placeholder.

Depending on which we choose, this might also mean renaming namedArguments to sth. else.

Indeed, that would help to avoid confusion.

Perhaps the static factory method should be named argumentSet(...) instead of namedArguments(...), and the new type would then be ArgumentSet instead of NamedArguments.

@sbrannen
Copy link
Member

sbrannen commented May 19, 2024

Why do we need DEFAULT_NAMED_ARGUMENTS_DISPLAY_NAME/DEFAULT_ARGUMENT_SET_DISPLAY_NAME Wouldn't DEFAULT_DISPLAY_NAME be changed to [{index}] {argumentSetNameOrArgumentsWithNames}?

I hadn't considered that option.

My goal was to make it configurable and clear within the available options/constants in ParameterizedTest (and to use predefined patterns instead of manipulating patterns programmatically), but I suppose we could introduce a new {argumentSetNameOrArgumentsWithNames} placeholder as you've proposed to achieve the same goal.

I'll experiment with that.

sbrannen added a commit to sbrannen/junit5 that referenced this issue May 19, 2024
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 19, 2024
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 19, 2024
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 19, 2024
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 20, 2024
@sbrannen sbrannen changed the title Introduce NamedArguments to name a set of arguments in a @ParameterizedTest Introduce named argument set support for @ParameterizedTest May 20, 2024
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 20, 2024
Running ArgumentSetDemo, results in the following output:

[1] Important Files
[2] Other Files

Important Files :: path1, path2
Other Files :: path3, path4

[1] Mixed Arguments Types :: Important Files
[2] Mixed Arguments Types :: file1=path3, file2=path4

Closes junit-team#3818
sbrannen added a commit to sbrannen/junit5 that referenced this issue May 20, 2024
Running ArgumentSetDemo, results in the following output:

[1] Important Files
[2] Other Files

Important Files :: path1, path2
Other Files :: path3, path4

[1] Mixed Arguments Types :: Important Files
[2] Mixed Arguments Types :: file1=path3, file2=path4

Closes junit-team#3818
@sbrannen
Copy link
Member

I have finalized my proposal and submitted PR #3825, which is ready for review. 😎

sbrannen added a commit to sbrannen/junit5 that referenced this issue Jun 2, 2024
Running ArgumentSetDemo, results in the following output:

[1] Important Files
[2] Other Files

Important Files :: path1, path2
Other Files :: path3, path4

[1] Mixed Arguments Types :: Important Files
[2] Mixed Arguments Types :: file1=path3, file2=path4

Closes junit-team#3818
sbrannen added a commit to sbrannen/junit5 that referenced this issue Jun 2, 2024
Prior to this commit, it was possible to use the Named API to provide
custom names for individual arguments for a @⁠ParameterizedTest when
using @⁠MethodSource, @⁠FieldSource, or @⁠ArgumentsSource, but it was
not possible to provide a custom name for an entire set of arguments
without resolving to obtuse workarounds.

To address that, this commit introduces an argumentSet(String,Object...)
static factory method in Arguments for providing a custom name for an
entire set of arguments for a @⁠ParameterizedTest.

This commit also introduces the following placeholders for the display
name of a @⁠ParameterizedTest.

- {argumentSetName} :: the name of the argument set

- {argumentSetNameOrArgumentsWithNames} :: resolves to {argumentSetName}
  or {argumentsWithNames}, depending on how the arguments are supplied

In addition, the default display name for a @⁠ParameterizedTest
is now "[{index}] {argumentSetNameOrArgumentsWithNames}".

For example, given the following...

@⁠ParameterizedTest
@⁠FieldSource
void argumentSets(File file1, File file2) {
}

static List<Arguments> argumentSets = List.of(
    argumentSet("Important files", new File("path1"), new File("path2")),
    argumentSet("Other files", new File("path3"), new File("path4"))
);

... the resulting display names are:

[1] Important files
[2] Other files

Closes junit-team#3818
sbrannen added a commit to sbrannen/junit5 that referenced this issue Jun 2, 2024
Prior to this commit, it was possible to use the Named API to provide
custom names for individual arguments for a @⁠ParameterizedTest when
using @⁠MethodSource, @⁠FieldSource, or @⁠ArgumentsSource, but it was
not possible to provide a custom name for an entire set of arguments
without resolving to obtuse workarounds.

To address that, this commit introduces an argumentSet(String,Object...)
static factory method in Arguments for providing a custom name for an
entire set of arguments for a @⁠ParameterizedTest.

This commit also introduces the following placeholders for the display
name of a @⁠ParameterizedTest.

- {argumentSetName} :: the name of the argument set

- {argumentSetNameOrArgumentsWithNames} :: resolves to {argumentSetName}
  or {argumentsWithNames}, depending on how the arguments are supplied

In addition, the default display name for a @⁠ParameterizedTest
is now "[{index}] {argumentSetNameOrArgumentsWithNames}".

For example, given the following...

@⁠ParameterizedTest
@⁠FieldSource
void argumentSets(File file1, File file2) {
}

static List<Arguments> argumentSets = List.of(
    argumentSet("Important files", new File("path1"), new File("path2")),
    argumentSet("Other files", new File("path3"), new File("path4"))
);

... the resulting display names are:

[1] Important files
[2] Other files

Closes junit-team#3818
sbrannen added a commit that referenced this issue Jun 2, 2024
Prior to this commit, it was possible to use the Named API to provide
custom names for individual arguments for a @⁠ParameterizedTest when
using @⁠MethodSource, @⁠FieldSource, or @⁠ArgumentsSource, but it was
not possible to provide a custom name for an entire set of arguments
without resolving to obtuse workarounds.

To address that, this commit introduces an argumentSet(String,Object...)
static factory method in Arguments for providing a custom name for an
entire set of arguments for a @⁠ParameterizedTest.

This commit also introduces the following placeholders for the display
name of a @⁠ParameterizedTest.

- {argumentSetName} :: the name of the argument set

- {argumentSetNameOrArgumentsWithNames} :: resolves to {argumentSetName}
  or {argumentsWithNames}, depending on how the arguments are supplied

In addition, the default display name for a @⁠ParameterizedTest
is now "[{index}] {argumentSetNameOrArgumentsWithNames}".

For example, given the following...

@⁠ParameterizedTest
@⁠FieldSource
void argumentSets(File file1, File file2) {
}

static List<Arguments> argumentSets = List.of(
    argumentSet("Important files", new File("path1"), new File("path2")),
    argumentSet("Other files", new File("path3"), new File("path4"))
);

... the resulting display names are:

[1] Important files
[2] Other files

See #3825
Closes #3818
@sbrannen
Copy link
Member

sbrannen commented Jun 2, 2024

argumentSet() support has been introduced in f95d4e8 and is available for testing in 5.11.0 snapshots.

Enjoy! 😎

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants