-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Global JUnit rules revisited #1219
Comments
Package annotation based configurationDeclaring Guards for packagesTypically projects are structured into packages
The
Excluding Guards for packages, classes and methodsAs a counter-piece
All annotations are inherited by default, so they do not have to be declared for every package (it is possible to list all packages in the current classloader, then use their names for the hierarchy). A more complete example:
Project A initially inherits the set of Guards In the bad case this should not be possible(e.g. due to problems with Java not having a real package hierarchy or with package annotation processing), we could resort to the properites file solution. There we e.g. could point to classes containing the guard configuration (similar to package-info.java, but as a class). |
In this case I would create super class which has test @rules . That's easy. You can force the devs to inherit such class in every test. You can write a plugins discovering public classes and check if they extend particular super class. btw. the Deltaspike`s test module has JUnit runner which stops the container after test. No need for any rule. |
@tburny: Impressive summary, I like it! I ended up writing a test enforcing the presence of a certain rule in all other tests, take a look if you're interested: RuleEnforcingTestBase and the implementing RuleEnforcingTest (I needed the test in multiple modules). It would however be much more elegant if the test framework would support this. Your API suggestion is a good start. I don't know if it's worth to introduce yet another concept (Guard vs. Rule) but haven't thought about it too much. Maybe a few words on the arguments that have been brought up against Package Rules:
I ended up having the exact same problem with 2 more projects and ended up implementing workarounds that are not really elegant, but do the job (one example being #1074 which would make implementation a lot easier using Package Rules). |
Thank you both for your comments :) @BenRomberg sums up the problem quite perfectly. Guards are easier to review and maintain than to modfiy each test (250+ at my company) and go through all the VCS and QA workflows. Appearantly Global Rules are listed in the Quo Vadis Wiki page, which is great! It would be awesome to receive some feedback of the @junit-team also 😃 To see how difficult it is to implement Guards, I implemented a throughly tested(!) proof of concept with the features I described above - including annotation based configuration - and integrates naturally with Maven Surefire and IntelliJ. I only had to modify There are two problems where I would appreciate some help:
I propose that I'll open a pull request, so we have a good base for further discussions about the architecture and implementation. 😃 Final word: I guess it should be easy to add Guards to JUnit Lambda by reusing the existing code. |
I agree that junit.properties and package level annotations are the step I was supporting JUnit for long time and now I maintain Surefire project. Nowadays people are able to implement their own runners, but more problems Now we have an idea in Maven Surefire to introduce SPI interfaces where the Cheers On Mon, Dec 28, 2015 at 12:52 PM, Tobias Brennecke <notifications@github.com
Cheers |
I saw a few runners since your last post and I figured out that your argument is perfectly valid. In JUnit 5 the concept of annotation based extensions replaces Rules. These extensions (should) allow modifying the test execution without modifing the test runner. In summary these allow having the required flexibility for extending JUnit on a class-level. However still it is quite easy to forget adding an extension annotation or it isn't feasible to do that for hundreds of classes. Maybe leveraging the extension concept in JUnit 5 to a package level would be a good solution, what do you think on this? Would there be reasons against that? |
@tburny JUnit 5 feature requests should be filed at https://github.com/junit-team/junit5 I doubt we will support adding behavior via package level annotations for JUnit4-style tests. Currently we don't even support adding behavior via class annotations (ouside of |
I've created another issue similar to this but focused on Android that references this. See #1307. I also have some prototype changes to add support. Anyone who's interested please take a look at let me know what you think. |
Using a "Global Rules" approach, I was able to replace an even lower-level solution using AspectJ that had the goal of fixing certain things centrally, i.e. without having to change the unit test classes.
And why all this? This appraoch allows us to do monitoring (limit execution time, i.e. global @timeout), logging and resource control globally. Also this is granular on @BeforeClass, test method(s) and @afterclass level! (@Before/After is lumped together with the method execution, though. Would need more code to separate these apart). Cheers, |
@m9aertner Is there any parts you can share on your implementation for global rules... we have a large codebase without mixed tests but looking at efficient ways to apply a global rules? |
Hey everyone. Our team also had struggles with lacking of such feature. So I made a PR, which you can grab for yourself (if it will not be merged). |
@Alviere Your PR with explicit command line options looks great. Thanks for sharing this. |
@m9aertner You're welcome. |
A word in advance: I never have written such an extensive request in such a major project before, so please bear with me. :) My goal was to provide a comprehensive background as well as concrete suggestions for a solution. I hope that does not seem impolite :)
Due to the topic dating back as far as 2009, I tried to sum up previous discussions, so this issue has become a bit lengthy (sorry :/).
TLDR:
TestRule
s,RunListener
s andRunner
s which mostly are scoped to test class level onlyProblem setting
At our company we want to run a set of global checks against each test to ensure it complies with company standards. If not, the according tests should fail.
Some actual use cases are:
@After(Class)#tearDown()
).There are more cases I can name on demand, but I wanted to keep the list short. All these cases have in common that they are some sort of global
TestRule
. In some cases it's not that obvious that a rule has been violated or someone simply forgot a line of code(nobody is perfect).History
"Global rules" have been initially requested in 2009 on the JUnit mailing list, which resulted in #69 Global rules. This discussion then has been continued in #444 JUnit should have an exhaustive listener framework in 2012. I will pick up some suggestions from these issues far further below. Besides the discussion of other use cases, @BenRomberg has been making some important arguments for having such a feature.
Some of the main arguments against global rules have been
TestListener
,Runner
, ...However it seems a part of the community is asking for a long time. There has been a lot of discussion and I guess that JUnit also evolved much more in the past 3 years (hence "revisited").
Tried approaches
We tried to solve the "no standard error output" and the "container is always destroyed problem". These are basically additional assertions/rules after the tests in a test class are run.
RunListener
configured into Maven Surefire. However theRunListener
is removed from the test run if an exception (such anAssertionError
thrown byAssert.*
methods) is thrown. My colleague @globalworming asked on Stackoverflow (suggesting using a customRunner
) and the JUnit mailing list, however there was no global solution. As stated by @dsaff in JUnit should have an exhaustive listener framework #444RunListener
s are read-only and I totally agree this should stay the way it is.Parameterized
tests(which also requires@RunWith(Parameterized.class)
) and adding a rule (a set of rules) to every single class is not an option.Suggestion
With hundreds of test classes in mind, changing every single one of them is no viable option.
To distinguish from existing terms (such as runner, listener or rule) which are declared in classes, I would like to introduce the term Guard. Here, imagine a historic city with solid high walls and one or more guards standing in front. These are checking on people (tests) going in(
@Before
) and out(@After
). Any people which fail their criteria will be put to jail(AssertionError
).By default there are no guards in front of the city and everyone is allowed in and out as they please (current JUnit), although there might be laws in the city (->
TestRule
) for certain groups of people (test classes). You can then opt-in to place Guards in front of your city gates.Some folk tend to come to the city and, as they leave, they are shouting mean things. This is a very polite city, so we tell the guards to put these visitors into jail for their offensive behaviour. That is, we ensure there is no output on standard error. (I can also provide a more comprehensive example, but I left it out to keep things short)
My intention using this analogy was to have an abstract, extensible story for this problem. This takes the focus away from the details towards the main problem.
How to implement this?
I'm not an JUnit expert as many of you are, so I would be glad for ideas and feedback especially on this part.
Before opening this issue, I already tried taking a look at JUnit's and also Maven Surefire's source code to evaluate some options. The goal is to create a global opt-in based approach to Guards which doesn't interfere with existing code, such as test runners + annotations (e.g. parameterized tests). The solution should be resilient to human failure, i.e. it should be possible to declare it in one place instead of for every test class.
RunListener
s can already be declared in theJUnitCore
. As far as I know, this is JUnits common entry point for all applications, such as IDE's, build systems, command line, etc. Adding guards at this point would allow seamless integration with existing test runners (e.g.@RunWith(Parameterized.class)
) and leaving existing test code unchanged.Confguring Guards
A common question in the past was how Guards should be configured. I could think of the following options
JUnitCore
. I think this should always be possible, as external applications (e.g. an IDE, Command line tools or Maven Surefire) can integrate themself here (i.e. adding a Guard to the Maven build via Surefire's configuration)junit.properties
file. However I fear that it takes away much of the flexibility(extensibility) available in normal source code. Also this is error prone to renaming tests/packages and simply not inside the code, which is the way users are used to.(Note: The basic ideas of 2. and 3. were already mentioned in #69 and #444) Any of these options would allow leaving pre-Guard code in user projects as is (no change required whatsoever).
From my point of view options 1) and 3) are the best, with 3) extending 1) with auto-config.
To keep the initial post shorter, I will put my suggestion for a package-based configuration into a separate comment below.
Inside a
Guard
, it would be nice to reuse the normal JUnit infrastructure using rules and class rules:This comes quite close to the
TestFragments
idea in #444. (Side note: Guards must not contain test cases O:-) )What do you think of my ideas so far? Would it be good if MyGuard implemented a certain interface (similar to
RunListener
, but with the ability to fail tests) for lifecycle events? I'm not certain, but if we used@Before
and@After
annotations any JUnit user could easily create/adapt his/her own Guards.On the technical side, it may be better to manage Guards similar to the existing RunListener/RunNotifier code? However, because to the "Exceptions remove the listener" rule, we must not reuse this.
If a guard fails, the error should be reported in a way that it's very clear where it came from, e.g.
This should weaken the "my test fails and I don't know why as I didn't declare anything in the test class" argument a bit.
Possible next steps
I really would like to contribute to JUnit if possible, so my proposal would be the following:
4.x
as a foundation (porting it to JUnit 5 afterwards).So, what do you think? :)
The text was updated successfully, but these errors were encountered: