-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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 junit-jupiter-params #728
Conversation
While Iterators cannot be closed, Streams can. Since providers now return Streams we close them after consumption. Thus, providers may now read directly from files etc. and can register a hook using Stream.onClose() to close their resources.
Streams are already implement AutoCloseable so there’s no need to implement our own mechanism to close ArgumentsProviders or the Iterators they used to return.
@ExtendWith(ParameterizedTestExtension.class) | ||
public @interface ParameterizedTest { | ||
|
||
String name() default "[{index}] {arguments}"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not in favor of introducing a name
attribute in @ParameterizedTest
since we decided against doing that for @Test
, @Step
, etc.
@DisplayName
would be more natural and, more importantly, consistent.
The only issue is determining when the above default should be used for parameterized tests. Maybe that logic could be built into the ParameterizedTestExtension
, by looking for a DisplayName
annotation instead of the ParameterizedTest
. If @DisplayName
is not present, you could then use "[{index}] {arguments}"
as the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about using @DisplayName
. But then the container descriptor for the method would also get that display name. IMHO that doesn't make sense so I decided to do it differently, in a way like we do for TestFactory
methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha.... the container name. I see.
Makes sense now.
|
||
@Override | ||
public boolean supports(ParameterContext parameterContext, ExtensionContext extensionContext) { | ||
return parameterContext.getIndex() < arguments.length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this intended to be used in conjunction with other parameter resolvers (e.g., mocks, DI)?
Are "parameters" required to come first as arguments, and then it's open game for other resolvers to contribute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, for now we would require them to be the first arguments. We could later introduce an annotation, e.g. @Parameter
with an optional index
value to mark them explicitly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK. We'll just have to make sure to properly document that behavior.
|
||
import org.junit.jupiter.api.extension.ParameterContext; | ||
|
||
public interface ArgumentConverter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still like to see this declared with generics <S, T>
(for source and target) even if it only serves to improve readability for the time being.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would we S
and T
for JavaTimeArgumentConverter
and DefaultArgumentConverter
? Both Object
?
If we add generics we will have to use unchecked calls to the converter and make sure we catch resulting ClassCastExceptions
in ParameterizedTestParameterResolver
. I'm not sure that makes it more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking as an idealist for what I think a Converter
API should look like, but now that I've looked at the code in the IDE... I agree with you: it's best to leave off the generics in this particular case.
+ input.getClass().getName() + " to type " + targetClass.getName()); | ||
} | ||
|
||
interface StringConversion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename all *Conversion
classes to *Converter
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed locally.
|
||
interface StringConversion { | ||
|
||
boolean isResponsible(Class<?> targetClass); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename to canConvert
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed locally.
&& Annotation.class.isAssignableFrom(method.getParameterTypes()[0]); | ||
Method method = ReflectionUtils.findMethods(instance.getClass(), methodPredicate, | ||
HierarchyTraversalMode.BOTTOM_UP).get(0); | ||
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) method.getParameterTypes()[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move the declaration of annotationType
higher up to avoid breaking the DRY principle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never mind: that made zero sense.
instance.accept(annotation); | ||
} | ||
catch (Exception ex) { | ||
throw new JUnitException("Failed to initialize instance: " + instance, ex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we come up with a better term than instance
in this context, something more meaningful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed locally.
@@ -0,0 +1,8 @@ | |||
/** | |||
* Support classes for building |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For building argument...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed locally.
@@ -78,4 +84,31 @@ private CollectionUtils() { | |||
return collectingAndThen(toList(), Collections::unmodifiableList); | |||
} | |||
|
|||
/** | |||
* Convert a well-known object into a {@code Stream}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document which well-known object types are supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
return Arrays.stream((Object[]) object); | ||
} | ||
throw new PreconditionViolationException( | ||
"Cannot convert instance of " + object.getClass().getName() + " into a Stream: " + object); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And... if object
is null
... Boom! 💥
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed locally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! 😳
@marcphilipp, thanks for all of the hard work on this! Looks really nice, and I'm looking forward to seeing it released in M4. I've made a some minor suggestions for improvements, but otherwise I think it looks good (at least from within the browser 😉). |
@@ -88,33 +84,20 @@ protected void invokeTestMethod(JupiterEngineExecutionContext context, DynamicTe | |||
dynamicTest -> registerAndExecute(dynamicTest, index.incrementAndGet(), dynamicTestExecutor)); | |||
} | |||
catch (ClassCastException ex) { | |||
throw invalidReturnTypeException(testExtensionContext); | |||
throw invalidReturnTypeException(ex); | |||
} | |||
}); | |||
} | |||
|
|||
@SuppressWarnings("unchecked") | |||
private Stream<DynamicTest> toDynamicTestStream(TestExtensionContext testExtensionContext, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you guys don't mind me chiming in.
testExtensionContext now seems to be an unused argument. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is still used by executableInvoker.invoke
but it might be slightly better if it was moved inside the lambda's scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually mean the TestExtensionContext in toDynamicTestStream but the comment is placed a but akwardly on the diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LiamClark, good catch. I'll remove that unused argument.
Overview
This pull request adds a new module
junit-jupiter-params
as discussed in the last team call. SeeParamsApiPlayground
for examples.This branch is still lacking Javadoc, API annotations, documentation, and, in some cases, unit tests. We've decided to merge it into master now to reduce conflicts with other pull requests and other work. The remaining tasks will be addressed in #14.
I hereby agree to the terms of the JUnit Contributor License Agreement.
Definition of Done
There are no TODOs left in the codeThere are TODOs that will be addressed as part of Introduce support for parameterized tests #14.Method preconditions are checked and documented in the method's JavadocJavadoc will be added as part of Introduce support for parameterized tests #14.Change is covered by automated testsA few classes that I've extracted fromParameterizedTestExtension
still lack proper unit tests.Public API has Javadoc andWill be added as part of Introduce support for parameterized tests #14.@API
annotationsChange is documented in the User Guide and Release NotesWill be added as part of Introduce support for parameterized tests #14.