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 configuration parameter for setting default test instance lifecycle semantics #905

Closed
9 tasks done
sbrannen opened this issue Jun 29, 2017 · 54 comments
Closed
9 tasks done

Comments

@sbrannen
Copy link
Member

sbrannen commented Jun 29, 2017

Overview

For teams that prefer per-class test instance lifecycle semantics, in order to prevent such teams from having to copy-n-paste @TestInstance(Lifecycle.PER_CLASS) across the code base, it would be beneficial to introduce a configuration flag (e.g., system property) that allows the default test instance lifecycle semantics to be set on a per-project basis (e.g., for a build).

Switching the default semantics would remain an opt-in decision but would be considerably less intrusive with the configuration flag.

See #419 (comment) for background information.

In addition, we could introduce support for setting ConfigurationParameters via a properties file (e.g., a junit.properties file in the root of the classpath). This would allow such configuration to be contained in the project itself (and checked into a VCS) as opposed to having to set such a flag in the build script as well as in the IDE (perhaps on a per test run basis).

Related Issues

Deliverables

  • Introduce junit.jupiter.testinstance.lifecycle.default configuration parameter for setting default test instance lifecycle semantics.
    • Introduce new constant in org.junit.jupiter.engine.Constants.
    • Support enum constant names from TestInstance.Lifecycle, ignoring case.
    • Introduce unit tests for the new configuration parameter.
    • Introduce integration tests for the new configuration parameter, including override semantics for local config, config params provided to the launcher, and system properties.
  • Log INFO message if the test instance lifecycle mode has been set via the configuration parameter.
  • Update JavaDoc for @TestInstance.
  • Update User Guide.
  • Update release notes.
@sdeleuze
Copy link

sdeleuze commented Jun 29, 2017

Strong +1 for this one and not only from a Kotlin developer perspective, to me @TestInstance(Lifecycle.PER_CLASS) makes sense as the default behavior in Java as well. Since it seems not possible to set that as the default in JUnit 5, being able to set such default globally would be super useful.

@marcphilipp marcphilipp modified the milestones: 5.0 M6, 5.1 Backlog Jul 18, 2017
@sdeleuze
Copy link

Since this issue has been postponed to 5.1 backlog (I can understand there is some concern introducing a different mode of execution of the tests based on configuration), I was wondering if we could find a better alternative solution.

For example, can't we turn this issue into enabling automatically PER_CLASS on classes with non-static methods annotated with @BeforeAll and @AfterAll rather than just failing in such case?

@sbrannen
Copy link
Member Author

sbrannen commented Jul 25, 2017

For example, can't we turn this issue into enabling automatically PER_CLASS on classes with non-static methods annotated with @BeforeAll and @AfterAll rather than just failing in such case?

That's certainly an interesting idea.

But I'm concerned that it would come as a surprise to developers who simply forgot to type static but meant to.

@junit-team/junit-lambda What do the rest of you think?

@smoyer64
Copy link
Contributor

it would come as a surprise to developers

I agree - I think feature switches that change the default behavior should be specified explicitly.

@sdeleuze
Copy link

Ok, I understand.

Is supporting @TestInstance(Lifecycle.PER_CLASS) at package level something we could consider?

@marcphilipp
Copy link
Member

I still think making it the default for test classes written in Kotlin would be a clear rule and solve your problem...

@junit-team/junit-committers Thoughts?

@sdeleuze
Copy link

sdeleuze commented Jul 27, 2017

@marcphilipp I guess I missed your proposal, but indeed that would be even better, so big +1 for that !!!

The code for detecting Kotlin classes is straightforward and does not need any dependencies:

private static boolean isKotlinClass(Class<?> clazz) {
	for (Annotation annotation : clazz.getDeclaredAnnotations()) {
		if (annotation.annotationType().getName().equals("kotlin.Metadata")) {
			return true;
		}
	}
	return false;
}

@sbrannen
Copy link
Member Author

I still think making it the default for test classes written in Kotlin would be a clear rule and solve your problem...

@junit-team/junit-committers Thoughts?

I think you meant to address the "JUnit Lambda" team. 😛

In any case, I don't have a problem with making it the default for classes written in Kotlin.

Kotlin developers could always explicitly revert back to per-method semantics if desired.

@sbrannen
Copy link
Member Author

@sdeleuze, thanks for the isKotlinClass() snippet!

@sbrannen
Copy link
Member Author

FYI: I just created #991 to enable per-class lifecycle semantics by default for Kotlin test classes.

@JLLeitschuh
Copy link
Contributor

JLLeitschuh commented Aug 1, 2017

Test classes written in the Kotlin programming language are now executed with @TestInstance(Lifecycle.PER_CLASS) semantics by default.

I read this in the release notes and was kind of surprised by this change. One of the things that I like about Junit over TestNG is that each test it executed in its own instance of the test. I don't want state to be shared between tests. I write a lot of my unit tests in Kotlin and this change would probably break many of my tests. Not only that but if a user were to go through and convert their Java tests to Kotlin they would end up seeing different results which could lead to quite a bit of confusion.

Is there some reason why this should be the default for kotlin? Personally, I don't think this is the right decision.

https://martinfowler.com/bliki/JunitNewInstance.html

I would personally be fine with this being configurable but having this be the default for junit RC2 is enough to prevent me from moving my tests forward to this release.

@marcphilipp
Copy link
Member

marcphilipp commented Aug 1, 2017

Just so everyone is on the same page, this is what it looks like to use PER_METHOD with @BeforeAll/@AfterAll from Kotlin:

@TestInstance(PER_METHOD)
class SimpleKotlinTestCase {

    companion object {
        @JvmStatic
        @BeforeAll
        fun beforeAll() {
            println("beforeAll")
        }

        @JvmStatic
        @AfterAll
        fun afterAll() {
            println("afterAll")
        }
    }

    @BeforeEach
    fun beforeEach(testInfo: TestInfo) {
        println("beforeEach: " + testInfo)
    }

    @AfterEach
    fun afterEach(testInfo: TestInfo) {
        println("afterEach: " + testInfo)
    }

    @Test
    fun firstTest() {
        println("firstTest")
    }

    @Test
    fun secondTest() {
        println("secondTest")
    }
}

Having to declare the companion object and using @JvmStatic did strike us as ugly and non-obvious, thus the change in #991.

It would be great to get a representative survey of all users who use Jupiter with Kotlin. Otherwise, it's very hard to decide this trade-off. Any ideas?

@sdeleuze Did I state your main concerns with using PER_CLASS correctly or did I miss something?

@sdeleuze
Copy link

sdeleuze commented Aug 1, 2017

@marcphilipp Yes

If you prefer making it configurable via a global config or a static field, that's would be also fine to me and would be appreciated by a lot of users including non Kotlin ones IMO. What would be a pain would be to specify @TestInstance(PER_CLASS) on each tests.

@marcphilipp
Copy link
Member

On the other hand, it's only 4 additional lines, and using @BeforeAll/@AfterAll is not something you need in every test. So, we could solve it with additional documentation on how to use @BeforeAll/@AfterAll with Kotlin in the User Guide. 🤔

@marcphilipp
Copy link
Member

If you prefer making it configurable via a global config or a static field, that's would be also fine to me and would be appreciated by a lot of users including non Kotlin ones IMO. What would be a pain would be to specify @TestInstance(PER_CLASS) on each tests.

How would the static field approach work?

@JLLeitschuh
Copy link
Contributor

JLLeitschuh commented Aug 1, 2017

I 100% expect that this is what I would need to do in order to correctly use @BeforeAll and @AfterAll in kotlin given what I know of the language.

The problem with kotlin is that if you don't do this then you end up with lots of code like this:

class `Some class with a nice name` {
    lateinit val someList : MutableList<Int>
    @BeforeEach
    fun beforeEach() {
        someList = mutableListOf(30)
    }
    @Test
    fun `test that modifies someList`() {
        // Change some list somehow
    }
}

VS

The far simpler:

class `Some class with a nice name` {
    val someList = mutableListOf(30)
    @Test
    fun `test that modifies someList`() {
        // Change some list somehow
    }
}

Having to put lateinit everywhere becomes quite a bit of an annoyance after a while.

@JLLeitschuh
Copy link
Contributor

You could do something like mockito where they have a config switch that they put in a resource file:
https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable
If you want to enable the mocking of final classes then they expect you to put:

mock-maker-inline

in a file in: src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

@marcphilipp
Copy link
Member

marcphilipp commented Aug 1, 2017

If we decide to go that route, we could introduce a general mechanism for reading default ConfigurationParameters from a Properties file, e.g. junit-platform-config.properties (on the classpath).

This file could look like this:

junit.extensions.autodetection.enabled = true
junit.testinstance.lifecycle.default = PER_CLASS

We could read the file when building LauncherConfigurationParameters. Any property passed explicitly to LauncherDiscoveryRequestBuilder (e.g. from an IDE) would override those from the file.

@junit-team/junit-lambda Thoughts?

@mlevison
Copy link

mlevison commented Aug 1, 2017 via email

@sbrannen sbrannen changed the title Introduce configuration flag for setting default test instance lifecycle semantics Introduce configuration parameter for setting default test instance lifecycle semantics Aug 4, 2017
@kcooney
Copy link
Member

kcooney commented Aug 5, 2017

@sbrannen thanks for the explanation

I'm not to trilled with the idea of specifying the lifecycle semantics via a system property. That would likely lead to the possibility the setting being different when tests are run via a build tool vs when debugging in an IDE. Using a config file stored as a Java resource would be preferable. Regardless, a warning in the docs might be a good idea.

@kcooney
Copy link
Member

kcooney commented Aug 5, 2017

@JLLeitschuh FYI: I created #1006 to track my proposal in #905 (comment)

sdeleuze added a commit to sdeleuze/spring-kotlin-functional that referenced this issue Aug 5, 2017
See junit-team/junit5#905
I will configure PER_CLASS by default when available
@sbrannen
Copy link
Member Author

FYI: for anyone interested in the progress on this issue...

Here's the code: a916e47

All that's left is documentation.

@sdeleuze
Copy link

sdeleuze commented Aug 12, 2017

In a Gradle project like https://github.com/sdeleuze/spring-kotlin-functional, what would be the recommended way to change the default to PER_CLASS with this improvement?

Is there a way to set the configuration parameter at Gradle level? Or should I set a system property?

@sbrannen
Copy link
Member Author

Hi @sdeleuze,

Unfortunately there is currently no native support for setting a configuration parameter via the console launcher, Maven Surefire plugin, or Gradle plugin.

Thus, the only option from the build perspective is to set a system property.

For the JUnit Platform Gradle Plugin, you can achieve that as discussed here: #475 (comment).

@sbrannen
Copy link
Member Author

@sdeleuze,

FYI: I raised #1015 to address the lacking support for setting configuration parameters with the Gradle plugin.

sbrannen added a commit that referenced this issue Aug 12, 2017
Prior to this commit, the test instance lifecycle mode could only be
changed from the default per-method value to per-class by annotating
every single test class or test interface with @testinstance(PER_CLASS).

This commit addresses this issue by introducing a new configuration
parameter that allows the default test instance lifecycle semantics to
be set on a per-project basis (e.g., for a build).

Specifically, the default test instance lifecycle mode can now be set
via a configuration parameter or JVM system property named
`junit.jupiter.testinstance.lifecycle.default` with a value equal to
one of the enum constants in TestInstance.Lifecycle.

Issue: #905
sbrannen added a commit that referenced this issue Aug 12, 2017
Prior to this commit, the test instance lifecycle mode could only be
changed from the default per-method value to per-class by annotating
every single test class or test interface with @testinstance(PER_CLASS).

This commit addresses this issue by introducing a new configuration
parameter that allows the default test instance lifecycle semantics to
be set on a per-project basis (e.g., for a build).

Specifically, the default test instance lifecycle mode can now be set
via a configuration parameter or JVM system property named
`junit.jupiter.testinstance.lifecycle.default` with a value equal to
one of the enum constants in TestInstance.Lifecycle.

Issue: #905
@sbrannen
Copy link
Member Author

Resolved in master in commit f750c85.

@JLLeitschuh
Copy link
Contributor

How does that constant work when running tests in IntelliJ? Will IntelliJ pickup that the gradle plugin has that default configured?

@sbrannen
Copy link
Member Author

How does that constant work when running tests in IntelliJ?

Unless IntelliJ provides explicit support for setting configuration parameters to pass to the JUnit Platform Launcher (and I don't think it does yet), your only option (currently) is to set the configuration parameter as a JVM system property for the run configuration in IDEA (or whatever it's called in IDEA -- "run configuration" is the term in Eclipse).

Will IntelliJ pickup that the gradle plugin has that default configured?

Nope. There's currently no explicit support for setting configuration parameters in the Gradle plugin (although there is a work-around). That will be addressed in #1015.

However, the best (i.e., the only robust) solution is the use of the JUnit Platform configuration file which I'll be committing soon in conjunction with #1003. 😉

@sbrannen
Copy link
Member Author

@sdeleuze,

In Gradle project like https://github.com/sdeleuze/spring-kotlin-functional, what would be the recommanded way to change the default to PER_CLASS with this improvement? Is there a way to set the configuration parameter at Gradle level ? Or should I set a system property ?

Now that I've resolved #1003, you can give the configuration file approach a try against the latest snapshots.

If you do try it out, please let us know how it works for you.

Cheers

@sdeleuze
Copy link

@sbrannen Works as expected, and seems a good outcome, thanks !!! sdeleuze/spring-kotlin-functional@7aef220

@sbrannen
Copy link
Member Author

Works as expected, and seems a good outcome, thanks !!! sdeleuze/spring-kotlin-functional@7aef220

Cool!

Thanks for letting us know.

Andrei94 pushed a commit to Andrei94/junit5 that referenced this issue Jun 23, 2018
Prior to this commit, the test instance lifecycle mode could only be
changed from the default per-method value to per-class by annotating
every single test class or test interface with @testinstance(PER_CLASS).

This commit addresses this issue by introducing a new configuration
parameter that allows the default test instance lifecycle semantics to
be set on a per-project basis (e.g., for a build).

Specifically, the default test instance lifecycle mode can now be set
via a configuration parameter or JVM system property named
`junit.jupiter.testinstance.lifecycle.default` with a value equal to
one of the enum constants in TestInstance.Lifecycle.

Issue: junit-team#905
Andrei94 pushed a commit to Andrei94/junit5 that referenced this issue Jun 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants