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 junit-jupiter-params #728

Merged
merged 30 commits into from
Mar 11, 2017
Merged

Introduce junit-jupiter-params #728

merged 30 commits into from
Mar 11, 2017

Conversation

marcphilipp
Copy link
Member

@marcphilipp marcphilipp commented Mar 11, 2017

Overview

This pull request adds a new module junit-jupiter-params as discussed in the last team call. See ParamsApiPlayground 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

marcphilipp and others added 30 commits March 11, 2017 22:09
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}";
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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;
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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 {
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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 {
Copy link
Member

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.

Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rename to canConvert.

Copy link
Member

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];
Copy link
Member

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.

Copy link
Member

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);
Copy link
Member

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?

Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For building argument...

Copy link
Member

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}.
Copy link
Member

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.

Copy link
Member

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);
Copy link
Member

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! 💥

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed locally.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! 😳

@sbrannen
Copy link
Member

@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,
Copy link
Contributor

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. 😄

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member

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.

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

Successfully merging this pull request may close these issues.

6 participants