-
Notifications
You must be signed in to change notification settings - Fork 23
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
JUnit rules - attempt to implement them in Spectrum #56
Conversation
I'll tackle the code coverage after initial feedback on how this goes. I think this solution is easily extended to a more JUnit native look too - for situations where you don't need a fresh instance of the test class, i think you could just do
or maybe have Spectrum do this for you as the first step, since if "this" doesn't have rules, nothing will be applied. |
So I got the coverage back to 100%... this PR needs more thought and more testing. Note: the mixin classes must be public or JUnit won't play with them. Note: I stole a lot of the implementation from JUnit BlockJUnitRunner. Knowing where to put it is the hard bit :) |
ea3148c
to
00ba83b
Compare
eb7f275
to
7348f48
Compare
* Subclass of {@link Suite} that represent the fact that some tests are composed | ||
* of interrelated steps which add up to a single test. | ||
*/ | ||
class CompositeTest extends Suite implements Atomic { |
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.
Extracted to class as Suite
was getting big!
@@ -16,14 +18,19 @@ public void run() throws Throwable { | |||
try { | |||
final Constructor<?> constructor = this.klass.getDeclaredConstructor(); | |||
constructor.setAccessible(true); | |||
constructor.newInstance(); | |||
testObject = constructor.newInstance(); |
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.
We'll need this soon... I'm planning a cheat version of the rule stuff where you can use the test class the old way if you like and it just kinda works...
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.
Yeah - I made that. It would be terrible in a parallel running scenario... potentially!
} | ||
|
||
static <T> RuleContextScope<T> ofNonRecursive(final RuleContext<T> context) { | ||
return new RuleContextScope<>(context, -2); |
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.
yeah.. -2 - that means it's "my generation" - this needs woyk!
@@ -3,7 +3,7 @@ | |||
import org.junit.runner.Description; | |||
import org.junit.runner.notification.RunNotifier; | |||
|
|||
final class Spec implements Child { | |||
final class Spec implements Child, Atomic { |
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.
A fine Blondie album
@@ -55,7 +55,7 @@ | |||
* | |||
*/ | |||
static void compositeSpec(final String context, final com.greghaskins.spectrum.Block block) { | |||
final Suite suite = getCurrentSuiteBeingDeclared().addAbortingSuite(context); | |||
final Suite suite = getCurrentSuiteBeingDeclared().addCompositeTest(context); |
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.
clearer neater name
suite.beforeAll.addBlock(this.beforeAll); | ||
suite.beforeEach.addBlock(this.beforeEach); | ||
suite.afterEach.addBlock(this.afterEach); | ||
rulesToApply.stream() | ||
.map(RuleContextScope::nextGeneration) | ||
.filter(scope -> scope != null) |
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.
Optional won't work here - something to do with type erasure, I think.
.map(RuleContextScope::get) | ||
.collect(Collectors.toList()), | ||
child, () -> runChildWithRules(child, notifier), getDescription())) | ||
.run(getDescription(), notifier); |
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.
This duplication needs removing before completion.
*/ | ||
public interface StubJUnitFrameworkMethod { | ||
class Stub { | ||
public void method() {} |
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.
This is the so-called test method JUnit thinks is under test!
Still to do:
@DirtiesContext
@Test
public void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
} |
The class blocks of the unique rule class blocks should all be run once before the first actual test. |
437e68b
to
4a75b3e
Compare
|
57a79aa
to
6c17f55
Compare
The junit stuff wants to work in a kind of "for each atomic" way. This means that, recursively down the tree, it will apply to The automagic JUnit stuff, to work the way JUnit users might expect, should be If I'm not mistaken, I think the name
Here's what specifically went wrong for the DropWizard rules. I've never seen this happen before by the way, so I reckon this is a weak use case - i.e. <1%. The DropWizard rule requires you to initialise the instance rule variable using the static rule variable. JUnit can guarantee that the instance variable won't be instantiated before the This means there are two easily worked around limitations of Spectrum automagic;
In both cases, you'd try, fail and drop to In 95%+ of cases, you can probably just use JUnit rules out of the box as JUnit was intended. It's a real shame to force people to type extra and people assume you can just use the rules. While I appreciate the point about BlockJUnitRunner and Spectrum being different, the ONLY sticking point is actually the new object instance thing, and that's not actually such a big deal in practice.
|
A thought that's been on my mind is the way that It's the difference between shallow and deep propagation. As far as I understand it, Conversely, Now, it's possible that you think |
…present, and supporting mix-in objects applied in the hierarchy as a special type of supplier. Updated the Hooks mechanism from greghaskins#77 to allow the passing of RunNotifier and Description around, and hooked in a private version of the rules handling code from JUnit which adapts from Hooks to Statement objects. Demonstrated the whole thing with Spring, Mockito and JUnit native rules, and managed to reverse some of the changes to exception handling brought by greghaskins#77
9ed966f
to
00ed8a6
Compare
Looking at this again, I'm actually willing to merge it mostly unchanged. We definitely still have some confusion about the difference between After looking at it for a while, I think I do understand the difference in propagation you're describing. The trouble, however, is the rules already describe how propagation should work (class vs method), and we're layering another system on top of that. I propose just removing If we take that out, I'm comfortable merging the rest as-is. After this I do plan to re-organize some packages and such. There's probably a home for all the global JUnit functions in a common place, based on where that shakes out. I'll probably also touch up the documentation and maybe tweak a few tests to add clarity. There will of course be more significant changes as I push to decouple from JUnit (longer term), but I can't say exactly what that would look like yet. I'm thinking either |
@greghaskins - the more I think about my reasoning for If you have some minor clean-up glitches on your mind, I'm happy to tackle them a bit too. I appreciate you will want to do some general post-merge curation of your own. In my team, we use Pull Requests for Code Review and people give line-by-line feedback where it's needed, so I'm happy to receive per-line _could you play with that a bit_s if you want me to do the leg-work. I'll ping this thread when the I'd really like to keep the JUnit automagic that's also in this MR, and I notice you're not asking me to remove it. From the current docs, is there anything you'd like me to add regarding the caveats. I'm thinking that writing a separate article - linked from the |
I like the sound of There were only a couple small things I saw, I can add those as line comments here in the PR. None of them were deal-breakers, IMHO. More along the lines of "maybe play with this a bit" as you said. I'm game to keep the automagic in and on by default. I think as we split out the JUnit specific pieces, that will make obvious sense for anyone using the JUnit runner (which is today the only runner). Splitting out the details about how this works definitely seems like a good move. That would be a great first step toward the broader goal of better, proper docs instead of just a giant |
import java.util.function.Predicate; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* Collection of hooks. It is a linked list, but provides some helpers for |
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 get a compiler warning about serialVerisionUID
on the Hooks
class:
The serializable class Hooks does not declare a static final serialVersionUID field of type long Hooks.java /spectrum/src/main/java/com/greghaskins/spectrum/model line 20
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. I've put one in. I don't see that warning, but it's no big deal.
* @param object the JUnit test object - this will never be recreated | ||
*/ | ||
static void applyRules(Object object) { | ||
RuleContext context = new RuleContext<>(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.
I get a compiler warning about raw types here:
RuleContext is a raw type. References to generic type RuleContext should be parameterized Rules.java /spectrum/src/main/java/com/greghaskins/spectrum/internal/junit line 33
Maybe add @SuppressWarnings("rawtypes")
to the local variable?
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 went further and just propagated generic types down into the code - it turns out that the type things happen to be can be captured, making this notionally type safe.
I say notionally, since it kind of was, and it kind of doesn't matter, and it's kind of all just Object
:)
|
||
{ | ||
final Set<File> filesSeen = new HashSet<>(); | ||
describe("A parent spec", () -> { |
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.
This suite was a little hard to follow the first couple times through reading it. Maybe the assertions could be more direct, something like assertTrue(theTempFolder.exists())
and/or assertThat(filesSeen, not(hasItem(theTempFolder)))
.
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.
With less to test, this was a bit clearer. However, I've done some renames of everything and I think it's probably better...
* @param <T> type of the object | ||
* @return a supplier of the rules object | ||
*/ | ||
public static <T> Supplier<T> applyRules(final Class<T> rulesClass) { |
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.
As discussed, we'll rename this one for better clarity. I'm still not sure where the best place for this entry point is, though. Not the worst if it's in root Spectrum
, but as this is something I'll be marking as both experimental and JUnit-specific, it might make sense just letting people use Rules.applyRules
directly, or maybe something like JUnit.applyRules
. I'm flexible though.
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.
Renamed junitMixin
- it shall stay here because the junit
package is presently internal
and should stay that way. I think Spectrum
is quite a good Facade, but I appreciate you'll want to restructure and move packages about, so you'll have more freedom if I leave this alone.
I've addressed the comments. Please read my comments and please re-review. |
…ation. Renamed the mixin method. Simplfied some of the code and reduced compiler warnings.
This appears to work for Mockito and TemporaryFolder as evidenced. It should work for Class rules too and needs roadtesting with Spring.
It should ultimately solve #15 #49 and #34