Junit5: Introduce SPI for generating display names

Created on 21 Feb 2016  Â·  24Comments  Â·  Source: junit-team/junit5

In many cases the strings passed to @DisplayName are just a prettified version of the class/method name, e.g.:

@DisplayName("List")
class ListTest {
    @Nested
    @DisplayName("when empty")
    class WhenEmpty {
        @Test
        @DisplayName("can add")
        void canAdd() {
            ...
        }
    }
}

It might be a nice-to-have if this could happen automatically, e.g.:

@DisplayName(prettify = true)
class ListTest {
    @Nested
    class WhenEmpty {
        @Test
        void canAdd() {
            ...
        }
        @Test
        @DisplayName("can't get")
        void cantGet() {
            ...
        }
    }
}

Related Issues

  • #145
  • #153
  • #432
  • #1036
  • #1577
  • #1589

Deliverables

  • [ ] Introduce an extension for generating display names.
  • [ ] Provide an out-of-the-box implementation of the extension that can be enabled via a JUnit configuration property.
Jupiter extensions new feature

Most helpful comment

Of course, we could also come up with a hybrid camel-case + underscore style.

Actually, the more I think about it (and after having looked at the latest updates @sormuras made to his PR), I think we are actually implementing the _Decorator_ pattern with our styles.

This leads to the following, treating style names as functions.

  • default()
  • underscore(default())
  • camelCase(underscore(default()))

So that means that the camel-case display name generator would automatically convert underscores to spaces simply be delegating to the _underscore_ style which in turn delegates to the _default_ style.

All 24 comments

how about making 'prettify' the default action when no explicit name is given?

I like the proposal from @schauder, and I have changed the title of this issue accordingly.

@junit-team/junit-lambda, what does the rest of the team think about making this change?

I think we should discuss all related issues (#145 and #153) together and make consistent changes: name, display name and unique ID have intermingled concerns which we should separate first.

I like the idea and also support going forward into this direction.

We should discuss in detail what prettify exactly means as we somehow set a standard for naming test methods and I know there are different styles in the community:

https://dzone.com/articles/7-popular-unit-test-naming

With this change we might make a statement which style should be used as we support it the best.

We simply should keep that in mind as we move on. Still, I support this change.

I'd rather not have prettification as default because there'll always be some corner cases (eg. acronyms and abbreviations) where any automatic prettification scheme will fail.

I agree with @bechte about the different testing styles and the corner cases mentioned by @jlink. I'd prefer not having this complexity and ‘one size fits all’ naming convention in junit core.

How about providing an ExtensionPoint to provide the display name?
This way there can be a domain/style/language aware version of pretty for everyone that is willing to write an extension. By defining my own @Test annotation that activates the extension by default, I can even skip the repetitive @DisplayName annotation for all of my test methods.

Two edges to this sword:

  1. Bad examples: https://www.youtube.com/watch?v=bpKbvb95Bv0 (from 06:17:00)
  2. Good examples: http://blog.codefx.org/libraries/junit-5-basics/#Naming-Tests

YMMV but my experience often shows that more people will find and follow bad examples than the good ones. I fear the most common usage we'll end up seeing is where the display names basically repeat what the method name already says. Goodbye DRY, now we're WET (Writing Everything Twice) all over the place.

The link to the bad examples a JavaOne 2016 presentation stream, the relevant segment was given by folks from Penn State University, starting at the 06:17:00 mark. To think that these are the kind of examples that future authors of JUnit tests will learn from makes me shudder. I also would humbly point out that the JUnit 5 user guide doesn't have very good examples either.

I like the idea of automatically prettifying the names for the report through an ExtensionPoint. Are there really that many corner cases where auto-prettify would get tripped up? My teams' test naming convention follows Neal Ford's style, using underscores to separate words so an extension that auto replaces "_" with " " would have a very high, if not perfect, success rate.

In light of recent team discussions, I have renamed this issue to focus on the introduction of a new extension for generating display names.

FYI: I have added a _Deliverables_ section to this issue's description.

This issue is related to #432.

... and #1036

An extension for generating display names would be nice for Kotlin (and Groovy, also). I find myself generally writing sentences as the test name like this:

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo

internal class MkobitTest {
  @Test
  internal fun `when this thing happens, then this other thing happens`(testInfo: TestInfo) {
    println(testInfo.displayName)
  }
}

And in this project, the output is when this thing happens, then this other thing happens$gradle_test_kotlin_extensions_main(TestInfo) - would be nice to register a simple display name extension globally that outputs the Kotlin method name and parameters.

Another good example for/of this requested feature:

From...
source

...to complete sentences as display names.
complete

Motivated by "Structure and Interpretation of Test Cases" (Kevlin Henney)
https://2018.javazone.no/program/5c0fc222-ea41-48bc-a6a4-737ea171ffb7

In light of the recent PR (#1588) from @sormuras, I'd like to point out a few things...

Although the deliverables for this issue discuss an "extension", the team discussed (and decided AFAIR) that it should not actually be an Extension that can be registered via @ExtendWith, @RegisterExtension, etc. Rather, it is more of an _SPI_ than an _Extension API_.

That's why the second deliverable states that the implementation should be configurable via a JUnit Platform _Configuration Property_, so that a display name generator can be configured for JUnit Jupiter once for an entire test plan execution.

The default implementation must remain the exact same as the status quo.

If we provide any other implementations, they should be simple and non-controversial.

For example, I can imagine providing the following two custom implementations.

  1. camel-case: inserts a space between changes in capitalization

    • for example, nullInputShouldThrowAnException() --> "null input should thrown an exception"

    • I imagine having everything lowercase would be OK. Otherwise, we may choose to capitalize only the very first word.

  2. underscore: converts every underscore to a space and otherwise leaves the text unmodified

    • for example, null_input_should_throw_an_exception() --> "null input should thrown an exception"

If we want to make the display name generator configurable declaratively for a given test class or test method, I propose that we set the default value for the existing value attribute to "" and introduce new attributes in @DisplayName for that purpose.

@DisplayName(generator = org.example.MyCustomDisplayNameGenerator.class)

... where generator defaults to org.junit.jupiter.api.extension.DisplayNameGenerator.class.

We could potentially introduce a set of _styles_ to simplify configuration like this:

@DisplayName(style = CAMEL_CASE)
@DisplayName(style = UNDERSCORE)

... where style would be an enum in DisplayName that defaults to DEFAULT.

If generator is set to something other than org.junit.jupiter.api.extension.DisplayNameGenerator.class, we would simply use that.

If generator is set to org.junit.jupiter.api.extension.DisplayNameGenerator.class, we would honor the style.

If the value (i.e., an explicit, custom display name) is supplied, we would use that; however, the style and generator could/should be honored for contained elements (e.g., test methods within a test class).

Regarding the camel-case based display name generator, I've also pondered adding configurable support for acronyms or other keywords.

For example, settingInvalidUrlResultsInException() could be converted to "Setting invalid URL results in exception", if "URL" is a known keyword.

Or perhaps, throwsExceptionForInvalidURL() should be converted to "Throws exception for invalid URL" instead of "Throws exception for invalid url", "Throws exception for invalid uRL", or "Throws exception for invalid u r l".

Food for thought...

Of course, we _could_ also come up with a hybrid camel-case + underscore _style_.

For example, givenZeroAsInput_division_throwsAnException() could result in a display name like "Given zero as input, division, throws an exception" or "Given zero as input - division - throws an exception".

See also: #1589

Regarding the camel-case converter, searching the Internet reveals lots of discussions around this topic -- for example, https://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java/2560017#2560017

Thanks for the review and feedback, Sam. Pushed a second spike to #1588.

Of course, we could also come up with a hybrid camel-case + underscore style.

Actually, the more I think about it (and after having looked at the latest updates @sormuras made to his PR), I think we are actually implementing the _Decorator_ pattern with our styles.

This leads to the following, treating style names as functions.

  • default()
  • underscore(default())
  • camelCase(underscore(default()))

So that means that the camel-case display name generator would automatically convert underscores to spaces simply be delegating to the _underscore_ style which in turn delegates to the _default_ style.

Was this page helpful?
0 / 5 - 0 ratings