-
Notifications
You must be signed in to change notification settings - Fork 66
Junit 5 #422
Conversation
* Refactor tests to use JUnit 5 (org.junit.jupiter.api.*) APIs * Convert existing Resources/Rules etc to JUnit 5 Extensions * Add base classes in com.cloudant.tests.base package for common test patterns eg remote-data-base-per-class, remote-dat abase-per-method * Convert parameterised tests to TestTemplates which use helper classes to provide parameters * Convert JUnit 4 Categories to JUnit 5 Tags * Upgrade gradle to 4.6 * Add custom JavaExec task to build.gradle to invoke JUnit Platform Console Launcher, due to lack of flexibility in current gradle support for JUnit Platform.
Regarding |
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.
Submitting this first part for now, I'm only about 20% of the way through though, so expect more to follow.
build.gradle
Outdated
@@ -28,8 +28,8 @@ subprojects { | |||
// If the version says "snapshot" anywhere assume it is not a release | |||
ext.isReleaseVersion = !version.toUpperCase(Locale.ENGLISH).contains("SNAPSHOT") | |||
|
|||
sourceCompatibility = 1.6 | |||
targetCompatibility = 1.6 | |||
sourceCompatibility = 1.8 |
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.
is it possible to set this property only for the java test compile?
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.
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
|
||
// This really requires Couch2.0 + text index support, but we don't have a way of expressing that | ||
@Category(RequiresCloudant.class) | ||
@RequiresCloudant |
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 thought one of the objectives of using JUnit 5 tags was being able to separate out different API level tests.
Then we can choose appropriate sets in the matrix for Couch 1.6 and Couch 2 as well as Cloudant.
For example this could be:
@Couch2
@Cloudant
since it applies to any Cloudant or Couch2
Is that intended for a later PR?
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.
Probably best done in a follow-on PR. I wanted to get the tags exactly the same so that I could check each of the matrix builds executes the correct number of tests, before thinking about changing the tags.
|
||
private Database db; | ||
private static Database db; |
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.
Why is this static if it is a @BeforeEach
?
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.
@tomblench think you missed this one
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.
Fixed in fe2ecb7 by extending TestWithDbPerTest
@Tag("RequiresCloudant") | ||
@Tag("RequiresDB") | ||
public @interface RequiresCloudant { | ||
} |
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.
newline
@Retention(RetentionPolicy.RUNTIME) | ||
@Tag("RequiresCloudant") | ||
@Tag("RequiresDB") | ||
public @interface RequiresCloudant { |
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.
Do you feel this level of indirection is preferable to tagging the tests directly? I guess it made replacing the categories easier :)
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.
These tags were straight out of the JUnit guide, so it's probably considered best practice.
@Tag("RequiresCloudant") | ||
@Tag("RequiresDB") | ||
public @interface RequiresCloudantService { | ||
} |
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.
newline
@Tag("RequiresCouch") | ||
@Tag("RequiresDB") | ||
public @interface RequiresCouch { | ||
} |
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.
newline
@Retention(RetentionPolicy.RUNTIME) | ||
@Tag("RequiresDB") | ||
public @interface RequiresDB { | ||
} |
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.
newline
byte[] bytesToDB = "binary data".getBytes(); | ||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB); | ||
Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", null, "1-abcdef"); | ||
assertThrows(IllegalArgumentException.class, new Executable() { |
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.
Since JUnit5 requires Java 8 anyway, can't we use Java8 features in the test code?
I think this would look a lot better as:
final byte[] bytesToDB = "binary data".getBytes();
final ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
assertThrows(IllegalArgumentException.class, () -> db.saveAttachment(bytesIn, "foo.txt", "text/plain", null, "1-abcdef"));
Even if we don't got that far, just executing the one line expected to throw inside the Executable
would be more clear.
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.
There are 49 instances of assertThrows
and wrapping the whole thing in an Executable
was easier, because:
- That was what the IDE auto-prompted for (lazy, I know)
- It's difficult to tell with each test which statement is the exact one responsible for throwing the exception, which i guess is one of the drawbacks of the old
@Throws
annotation.
So it takes time to understand each test and put the assertThrows
in the right place, but I agree that the way you have suggested doing it is a lot clearer and more expressive.
Possibly something to look at towards the end of the PR?
} | ||
|
||
@Test(expected = IllegalArgumentException.class) | ||
@Test() |
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.
redundant parentheses
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.
Done in 0b3645d
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.
Phase II, I've nearly made it halfway!
db = dbResource.get(); | ||
} | ||
@RequiresDB | ||
public class BulkDocumentTest extends TestWithDb { |
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.
Couldn't TestWithDb
reasonably include the @RequiresDB
annotation and avoid the need for it to be present throughout so many test classes. I'm assuming anything that extends TestWithDb
also RequiresDB
though I guess which DB could be important. Something to think about.
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.
It's a nice idea but we have test classes like CloudantClientTests
which extend TestWithDb
but only need the database for a couple of tests, so the whole class need not be marked as @RequiresDB
@@ -47,6 +48,8 @@ | |||
try { | |||
//a URL might be supplied, otherwise use the separate properties | |||
String URL = System.getProperty("test.couch.url"); | |||
System.out.println(System.getProperties()); |
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.
If you think this and its counterpart on the line below are necessary for debugging can you make it a JUL style thing so we can turn it off if we want. Or if it was just overlooked delete it.
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'd left some debugging in, especially for when test parameters weren't being passed in properly from gradle. This is no longer needed, so I've got rid of all of these in 96ff132
public static void setUp() throws Exception { | ||
account = clientResource.get(); | ||
db = dbResource.get(); | ||
// TODO before class? |
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.
Does this need doing?
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.
See 1d42697
Per the commit comment "mockWebServer needs to be fetched from extension before each test because the extension is a "per test" extension (overrides beforeEach/afterEach)".
It's a bit wasteful to stop/start the mock web server for each test when currently only one test uses it, but I think the overhead is negligible.
mockWebServer needs to be fetched from extension before each test because the extension is a "per test" extension (overrides beforeEach/afterEach)
ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB); | ||
Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", null, | ||
"1-abcdef"); | ||
} |
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.
Can we consider moving the code in the block above into a helper util method since it occurs several times in this test class?
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.
Probably outside the scope of what I'm doing, which is just translating the tests to junit5 without considering too closely how these tests are written.
@@ -93,13 +74,14 @@ public void findJsonObject() { | |||
assertNotNull(jsonObject); | |||
} | |||
|
|||
/* | |||
@Test |
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.
Is this commented because the test is failing? If so, should we instead use @Disabled
?
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.
The test wasn't commented out before so if it's failing now we need to fix it. In general though, I suppose this test need not use a Couch specific endpoint, which is perhaps what is pending some thought here?
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.
Fixed in 3e9d231
I'm seeing test classes that import |
@@ -49,14 +47,14 @@ | |||
*/ | |||
public class IndexLifecycleTest { |
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.
Something for a future PR to possibly add clarity in the logs: adding @DisplayName
for test classes e.g. @DisplayName("Index lifecycle test...")
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.
Probably future PR, but it would be useful
} | ||
private static final String TESTJSON = "{\"_id\":\"literal\"," + | ||
"\"_rev\":\"1-39933759c7250133b6039d94ea09134f\",\"foo\":\"Grüße 日本語 \uD834\uDD1E.\"}\n"; | ||
private static final String TESTJSON_ESCAPED = "{\"_id\":\"escaped\"," + |
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.
Shouldn't this have \\
similar to TESTSTRING_ESCAPED above?
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.
Thanks for spotting this inconsistency. These are actually used as "expected" values for assertions later, and the json deserialiser will always unescape these values, so they are correct in that regard.
However, the logic and naming of some variables is confusing so I've fixed that in e8a6acb
@@ -878,11 +872,10 @@ public void multiRequest() throws IOException { | |||
.build(); | |||
int i = 1; | |||
List<ViewResponse<String, Object>> responses = multi.getViewResponses(); | |||
assertEquals("There should be 3 respones for 3 requests", 3, responses.size()); | |||
assertEquals(3, responses.size(), "There should be 3 respones for 3 requests"); |
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.
respones -> responses
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.
Fixed in f378a13
@@ -999,15 +1013,15 @@ public void multiRequestMixedReduced() throws IOException { | |||
.build(); | |||
|
|||
List<ViewResponse<String, Object>> responses = multi.getViewResponses(); | |||
assertEquals("There should be 2 respones for 2 requests", 2, responses.size()); | |||
assertEquals(2, responses.size(), "There should be 2 respones for 2 requests"); |
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.
respones -> responses
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.
Fixed in f378a13
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
// Base class for tests which require a DB which is used throughout the lifetime of the test class | ||
public class TestWithDb { |
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.
What about having a TestWithDbPerClass
that would extend a base TestWithDb
class?
TestWithDbPerTest
would also extend TestWithDb
.
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.
@@ -0,0 +1,30 @@ | |||
package com.cloudant.tests.base; |
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.
Add copyright
@@ -0,0 +1,35 @@ | |||
package com.cloudant.tests.base; |
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.
Add copyright
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.
Last few comments, now I've completed a first pass.
@@ -93,13 +74,14 @@ public void findJsonObject() { | |||
assertNotNull(jsonObject); | |||
} | |||
|
|||
/* | |||
@Test |
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.
The test wasn't commented out before so if it's failing now we need to fix it. In general though, I suppose this test need not use a Couch specific endpoint, which is perhaps what is pending some thought here?
|
||
// Note Parameter(0) okUsable is inherited | ||
// because we fill in the args from the left, we can fill in the single argument "okUsable" |
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 comment is a bit confusing. AFAICT the boolean
value of okUsable
is provided in the Stream
of TestTemplateInvocationContext
s here and passed into the test method's parameters. I think what this comment is saying is that the resolution of the template parameters precedes the superclass's BeforeEach
execution so we can safely provide the boolean
this way i.e. the value is set before it is referenced by the superclass?
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've just removed it in 5d5abf5 because it was wrong and there's no comment needed here
assertEquals("The results list should be of length 0", 0, db.getViewRequestBuilder | ||
("example", "by_tag").newRequest(Key.Type.STRING, Object.class).keys | ||
("javax").build().getResponse().getKeys().size()); | ||
assertEquals(0, db.getViewRequestBuilder("example", "by_tag").newRequest(Key.Type.STRING, Object.class).keys("javax").build().getResponse().getKeys().size(), "The results list should be of length 0"); |
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.
It seems like some of these code replacements have produced very long lines. Perhaps at the end of the review process it is just worth applying the formatter to all the classes before the final merge?
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.
Good idea, i've added it to the TODOs
@@ -0,0 +1,60 @@ | |||
/* | |||
* Copyright © 2017 IBM Corp. All rights reserved. |
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 you said in the TODOs description for this PR there are a bunch of copyright updates to do.
import org.junit.jupiter.api.extension.Extension; | ||
import org.junit.jupiter.api.extension.ExtensionContext; | ||
|
||
public class MultiExtension implements Extension, AfterAllCallback, AfterEachCallback, |
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.
Remind me why this is necessary, was it that @RegisterExtension
doesn't currently guarantee an order and @ExtendWith
doesn't allow arguments to be passed?
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.
Yes, see junit-team/junit5#506 (comment)
Note some files have had line endings converted to UNIX in the process
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.
+1
Don't forget to remove the Jenkinsfile junit
branch name check.
Also I think it would be good to open one or more follow up issues for these points that didn't make it into this:
i) Improve test tagging
ii) Throws assertions in java 8 style
iii) AttachmentsTest refactor
iv) @DisplayName
Thanks
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.
Looks good. I'm also +1 on future work item for improving test tagging.
What
Upgrade tests to JUnit 5 (JUnit Jupiter)
How
Refactor tests to use JUnit 5 (
org.junit.jupiter.api.*
) APIsConvert existing Resources/Rules etc to JUnit 5 Extensions
Add base classes in
com.cloudant.tests.base package
for common testpatterns eg remote-data-base-per-class, remote-database-per-method
Convert parameterised tests to TestTemplates which use helper
classes to provide parameters
Convert JUnit 4 Categories to JUnit 5 Tags
Upgrade gradle to 4.6
Add custom JavaExec task to build.gradle to invoke JUnit Platform
Console Launcher, due to lack of flexibility in current gradle
support for JUnit Platform.
Testing
All tests pass, with current exceptions:
DesignDocumentTest
- number of tests run is exactly half on this branch. It appears that the existing code ran every test twice, maybe due to the use of@RunWith(Enclosed.class)
HttpProxyTest
- two test combinations currently disabled due to failure. Previously it was determined that all test combinations would pass if run in some orderings, but now these two parameter combinations always fail. Needs further investigation.TODO
Removeif (env.BRANCH_NAME == "master" || env.BRANCH_NAME == "junit5")
fromJenkinsfile
before merging.Re-visit java version requirements. 1.8 is required to build and run tests now, although production code should still only require 1.6Fixed in 62e82adUpdate copyrightsFormat source files which have been automatically updated to switch order of assert arguments, which now have long lines