Parameterized Tests in JUnit 4.x
Parameterized tests is one of the features that have come along in the JUnit 4.x releases that I don’t find myself using very often but when I do, I find tremendously useful. In a nutshell, parameterized tests allow you to run the same test over and over again using different values. I’ve found this approach very useful when you are testing a function and have a table of data defining the input value(s) and the expected result. That being said, parameterized tests can be used for testing more than just pure “functions.”
This post walks through a detailed example of using JUnit’s parameterized tests.
The Scenaio Under Test: Boxing the Compass
The example I’m going to use is that of turning a compass bearing in degrees into a directional compass point. I’m definitely not an expert in cartography or navigation techniques so if you happen to be one, please let me know if I’ve made any mistakes in the terminology.
Client code will want to provide a bearing in degrees and the number of directional points on the compass (see the Wikipedia articles on Boxing the Compass and Compass Rose if you are interested in a better description of this) and get back the compass point of that bearing. In other words, if you have a bearing of 65.23 degrees and an eight-point compass rose, that corresponds to a compass point of Northeast. In code, this looks like (using JUnit/Hamcrest):
assertThat(CompassPoint.fromBearing(65.32, CompassRose.EIGHT_POINT), is(CompassPoint.NE)); assertThat(CompassPoint.fromBearing(343, CompassRose.EIGHT_POINT), is(CompassPoint.N)); assertThat(CompassPoint.fromBearing(343, CompassRose.SIXTEEN_POINT), is(CompassPoint.NNW));
As I begin implementing the code to solve this problem, all I have is the table of compass points on the Boxing the Compass page. I don’t know what the actual algorithm is that I will be using. Since I have a table of data describing the desired and a function that I need to implement, JUnit’s parameterized tests seem like a good choice to help me use TDD to drive out the algorithm’s implementation.
To start out, I created the shell of the CompassPoint and CompassRose. I’ve chosen to use enums for the implementation. That implementation detail isn’t important for this example, but in retrospect, it somewhat interesting on its own as it ends up roughly being a form of the strategy pattern using enums, albeit a tightly couple one. Also for the example, I’m only showing eight and sixteen-point data to save space as the additional data points for four and 32-point don’t add anything to the example. The code we will create will work with four and 32-point compass roses as well.
Enough of the background. Here is the shell of the code we start with.
public enum CompassPoint { N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW; public enum CompassRose { EIGHT_POINT(8), SIXTEEN_POINT(16); private int points; private CompassRose(int points) { this.points = points; } } public String abbreviation() { return name(); } public static CompassPoint fromBearing(double bearing, CompassRose compassRose) { // TODO This is the algorithm we want to implement return null; } }
Defining the Parameterized Test
TestNG is the framework that made parameterized tests popular to the mainstream, or if not the mainstream at least to me. JUnit added their own JUnit’s parameterized testing capabilities in version 4, which at this point has been around for a while. Implementing parameterized tests is relatively straightforward but they are definitely more involved than writing a simple test case. There are five components, or steps, that you need to consider.
- Annotate your test class with @RunWith(Parameterized.class)
- Create a public static method annotated with @Parameters that returns a Collection of Objects that make up your test data set.
- Create a public constructor that takes in what is equivalent to one “row” of your test data.
- Create an instance variable for each “column” of test data.
- Create your tests case(s) as you normally would using the instance variables as the source of the test data. The test case will be invoked once per each row of data.
I’ll step through each one of these with our concrete example.
@RunWith(Parameterized.class)
Parameterized is a custom JUnit Runner. Simply add the @RunWith(Parameterized.class) annotation to your test class to have JUnit run it as a parameterized test.
@RunWith(Parameterized.class) public class CompassPointTest {
@Parameters Method
When your test class has the @RunWith(Parameterized.class) annotation, JUnit will looks for a public static method annotated with @Parameter. As I mentioned above, think of this method as returning your test data set. The method must have a return type of Collection<. If you think of your test data as a table of values, each element in the Collection is a row and each index in the Object array is a column.
For our example, I copied the table at Boxing the Compass into a spreadsheet and manipulated it to get it into the format needed by JUnit. The result is.
@Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { // { SIXTEEN_POINT, 0, N }, // center { SIXTEEN_POINT, 11.24, N }, // high { SIXTEEN_POINT, 11.25, NNE }, // low { SIXTEEN_POINT, 22.5, NNE }, // center { SIXTEEN_POINT, 33.74, NNE }, // high { SIXTEEN_POINT, 33.75, NE }, // low { SIXTEEN_POINT, 45, NE }, // center { SIXTEEN_POINT, 56.24, NE }, // high { SIXTEEN_POINT, 56.25, ENE }, // low { SIXTEEN_POINT, 67.5, ENE }, // center { SIXTEEN_POINT, 78.74, ENE }, // high { SIXTEEN_POINT, 78.75, E }, // low { SIXTEEN_POINT, 90, E }, // center { SIXTEEN_POINT, 101.24, E }, // high { SIXTEEN_POINT, 101.25, ESE },// low { SIXTEEN_POINT, 112.5, ESE },// center { SIXTEEN_POINT, 123.74, ESE },// high { SIXTEEN_POINT, 123.75, SE }, // low { SIXTEEN_POINT, 135, SE }, // center { SIXTEEN_POINT, 146.24, SE }, // high { SIXTEEN_POINT, 146.25, SSE }, // low { SIXTEEN_POINT, 157.5, SSE }, // center { SIXTEEN_POINT, 168.74, SSE }, // high { SIXTEEN_POINT, 168.75, S }, // low { SIXTEEN_POINT, 180, S }, // center { SIXTEEN_POINT, 191.24, S }, // high { SIXTEEN_POINT, 191.25, SSW }, // low { SIXTEEN_POINT, 202.5, SSW }, // center { SIXTEEN_POINT, 213.74, SSW }, // high { SIXTEEN_POINT, 213.75, SW }, // low { SIXTEEN_POINT, 225, SW }, // center { SIXTEEN_POINT, 236.24, SW }, // high { SIXTEEN_POINT, 236.25, WSW }, // low { SIXTEEN_POINT, 247.5, WSW }, // center { SIXTEEN_POINT, 258.74, WSW }, // high { SIXTEEN_POINT, 258.75, W }, // low { SIXTEEN_POINT, 270, W }, // center { SIXTEEN_POINT, 281.24, W }, // high { SIXTEEN_POINT, 281.25, WNW }, // low { SIXTEEN_POINT, 292.5, WNW }, // center { SIXTEEN_POINT, 303.74, WNW }, // high { SIXTEEN_POINT, 303.75, NW }, // low { SIXTEEN_POINT, 315, NW }, // center { SIXTEEN_POINT, 326.24, NW }, // high { SIXTEEN_POINT, 326.25, NNW }, // low { SIXTEEN_POINT, 337.5, NNW }, // center { SIXTEEN_POINT, 348.74, NNW }, // high { SIXTEEN_POINT, 348.75, N }, // low { SIXTEEN_POINT, 360, N }, // center { EIGHT_POINT, 0, N }, // center { EIGHT_POINT, 22.4, N }, // high { EIGHT_POINT, 22.5, NE }, // low { EIGHT_POINT, 45, NE }, // center { EIGHT_POINT, 67.4, NE }, // high { EIGHT_POINT, 67.5, E }, // low { EIGHT_POINT, 90, E }, // center { EIGHT_POINT, 112.4, E }, // high { EIGHT_POINT, 112.5, SE }, // low { EIGHT_POINT, 135, SE }, // center { EIGHT_POINT, 157.4, SE }, // high { EIGHT_POINT, 157.5, S }, // low { EIGHT_POINT, 180, S }, // center { EIGHT_POINT, 202.4, S }, // high { EIGHT_POINT, 202.5, SW }, // low { EIGHT_POINT, 225, SW }, // center { EIGHT_POINT, 247.4, SW }, // high { EIGHT_POINT, 247.5, W }, // low { EIGHT_POINT, 270, W }, // center { EIGHT_POINT, 292.4, W }, // high { EIGHT_POINT, 292.5, NW }, // low { EIGHT_POINT, 315, NW }, // center { EIGHT_POINT, 337.4, NW }, // high { EIGHT_POINT, 337.5, N }, // "low" { EIGHT_POINT, 60, N }, // center }); }
Each row is data for a single test case. For example, the first row maps to the case "on a 16 point compass rose, 0 maps to North."
The elements in each individual Object array will map the the parameters to the test's constructor. I'll explain that next.
Constructor and Instance Variables
For each element the the Collection returned by our @Parameters, JUnit will instantiate a new test class and call the constructor using the value or values in that element. In our case, each element contains an array of Objects but the framework is flexible so a single String or int works equally as well.
Take an example row in our test data: { SIXTEEN_POINT, 56.24, NE }. The first column is an input CompassRose value. The second column is the input bearing in degrees. And the third column is the expected output Compass point. Our constructor take these values and stores them in corresponding instance variables that the test will access when it executes.
private CompassRose compassRose; private double degrees; private CompassPoint expectedCompassPoint; public CompassPointTest(CompassRose compassRose, double degrees, CompassPoint expectedCompassPoint) { this.compassRose = compassRose; this.degrees = degrees; this.expectedCompassPoint = expectedCompassPoint; }
The Test Case
We finally arrive at our test case. The test case definition is no different than your standard JUnit test case. This test will be executed once for each element in your data set. The values that drive the test case are the instance variables populated via the constructor that JUnit calls.
@Test public void compassDirectionShouldMatchDegrees() { CompassPoint point = CompassPoint.fromBearing(degrees, compassRose); assertThat(point, is(expectedCompassPoint)); }
During the first execution of this test method, compassRose = SIXTEEN_POINT, degrees = 0, and expectedCompassPoint = N given the data supplied by the method with the @Parameters annotation.
On a side note, you can make the error messages provide additional information about what the actual data was that cause the test to fail. When the example above fails, the default JUnit output looks like the following.
java.lang.AssertionError: Expected: is <N> got: <NE> at org.junit.Assert.assertThat(Assert.java:778) ....
Notice that you don't know what data actually cause the test to fail. You can leverage the description that can be provided to the assertThat (or any other) assertion method to enhance the message.
@Test public void compassDirectionShouldMatchDegrees() { CompassPoint point = CompassPoint.fromBearing(degrees, compassRose); assertThat(describeExpectation(), point, is(expectedCompassPoint)); } private String describeExpectation() { return "On a(n) " + compassRose + " compass rose, " + degrees + " degrees should translate to " + expectedCompassPoint; }
Now the error message has more context on the data that caused the failure.
java.lang.AssertionError: On a(n) EIGHT_POINT compass rose, 60.0 degrees should translate to N Expected: is <N> got: <NE> at org.junit.Assert.assertThat(Assert.java:778) ...
The Entire Test
Here is the entire test class for reference.
package com.davidehringer.geo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static com.davidehringer.geo.CompassPoint.CompassRose.*; import static com.davidehringer.geo.CompassPoint.*; import java.util.Arrays; import java.util.Collection; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import com.davidehringer.geo.CompassPoint.CompassRose; @RunWith(Parameterized.class) public class CompassPointTest { private CompassRose compassRose; private double degrees; private CompassPoint expectedCompassPoint; public CompassPointTest(CompassRose compassRose, double degrees, CompassPoint expectedCompassPoint) { this.compassRose = compassRose; this.degrees = degrees; this.expectedCompassPoint = expectedCompassPoint; } @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { // { SIXTEEN_POINT, 0, N }, // center { SIXTEEN_POINT, 11.24, N }, // high { SIXTEEN_POINT, 11.25, NNE }, // low { SIXTEEN_POINT, 22.5, NNE }, // center { SIXTEEN_POINT, 33.74, NNE }, // high { SIXTEEN_POINT, 33.75, NE }, // low { SIXTEEN_POINT, 45, NE }, // center { SIXTEEN_POINT, 56.24, NE }, // high { SIXTEEN_POINT, 56.25, ENE }, // low { SIXTEEN_POINT, 67.5, ENE }, // center { SIXTEEN_POINT, 78.74, ENE }, // high { SIXTEEN_POINT, 78.75, E }, // low { SIXTEEN_POINT, 90, E }, // center { SIXTEEN_POINT, 101.24, E }, // high { SIXTEEN_POINT, 101.25, ESE },// low { SIXTEEN_POINT, 112.5, ESE },// center { SIXTEEN_POINT, 123.74, ESE },// high { SIXTEEN_POINT, 123.75, SE }, // low { SIXTEEN_POINT, 135, SE }, // center { SIXTEEN_POINT, 146.24, SE }, // high { SIXTEEN_POINT, 146.25, SSE }, // low { SIXTEEN_POINT, 157.5, SSE }, // center { SIXTEEN_POINT, 168.74, SSE }, // high { SIXTEEN_POINT, 168.75, S }, // low { SIXTEEN_POINT, 180, S }, // center { SIXTEEN_POINT, 191.24, S }, // high { SIXTEEN_POINT, 191.25, SSW }, // low { SIXTEEN_POINT, 202.5, SSW }, // center { SIXTEEN_POINT, 213.74, SSW }, // high { SIXTEEN_POINT, 213.75, SW }, // low { SIXTEEN_POINT, 225, SW }, // center { SIXTEEN_POINT, 236.24, SW }, // high { SIXTEEN_POINT, 236.25, WSW }, // low { SIXTEEN_POINT, 247.5, WSW }, // center { SIXTEEN_POINT, 258.74, WSW }, // high { SIXTEEN_POINT, 258.75, W }, // low { SIXTEEN_POINT, 270, W }, // center { SIXTEEN_POINT, 281.24, W }, // high { SIXTEEN_POINT, 281.25, WNW }, // low { SIXTEEN_POINT, 292.5, WNW }, // center { SIXTEEN_POINT, 303.74, WNW }, // high { SIXTEEN_POINT, 303.75, NW }, // low { SIXTEEN_POINT, 315, NW }, // center { SIXTEEN_POINT, 326.24, NW }, // high { SIXTEEN_POINT, 326.25, NNW }, // low { SIXTEEN_POINT, 337.5, NNW }, // center { SIXTEEN_POINT, 348.74, NNW }, // high { SIXTEEN_POINT, 348.75, N }, // low { SIXTEEN_POINT, 360, N }, // center { EIGHT_POINT, 0, N }, // center { EIGHT_POINT, 22.4, N }, // high { EIGHT_POINT, 22.5, NE }, // low { EIGHT_POINT, 45, NE }, // center { EIGHT_POINT, 67.4, NE }, // high { EIGHT_POINT, 67.5, E }, // low { EIGHT_POINT, 90, E }, // center { EIGHT_POINT, 112.4, E }, // high { EIGHT_POINT, 112.5, SE }, // low { EIGHT_POINT, 135, SE }, // center { EIGHT_POINT, 157.4, SE }, // high { EIGHT_POINT, 157.5, S }, // low { EIGHT_POINT, 180, S }, // center { EIGHT_POINT, 202.4, S }, // high { EIGHT_POINT, 202.5, SW }, // low { EIGHT_POINT, 225, SW }, // center { EIGHT_POINT, 247.4, SW }, // high { EIGHT_POINT, 247.5, W }, // low { EIGHT_POINT, 270, W }, // center { EIGHT_POINT, 292.4, W }, // high { EIGHT_POINT, 292.5, NW }, // low { EIGHT_POINT, 315, NW }, // center { EIGHT_POINT, 337.4, NW }, // high { EIGHT_POINT, 337.5, N }, // "low" { EIGHT_POINT, 60, N }, // center }); } @Test public void compassDirectionShouldMatchDegrees() { CompassPoint point = CompassPoint.fromBearing(degrees, compassRose); assertThat(describeExpectation(), point, is(expectedCompassPoint)); } private String describeExpectation() { return "On a(n) " + compassRose + " compass rose, " + degrees + " degrees should translate to " + expectedCompassPoint; } }
If you run the test, you will notice that JUnit will actually execute an instance of the test for each row in your test data. Unfortunately, the name of the test will simply be the index of the element in the Collection. As far as I know the test name cannot be customized to provide better context. If you are generating specifications from your test cases, this would be highly desirable since the index number means nothing from a specification perspective. If you use Eclipse, you'll see something similar to the following.
The Implementation
At this point, I've covered everything you need to know about parameterized tests in JUnit. But from a TDD process perspective, we still have to implement the code that will make the tests pass. I'm not going to walk through the process for figuring out the algorithm and implementing the code since that is beyond the scope of this article. But I will show you the final code since I know you are dying to know how to convert a bearing to a compass point. Thanks goes out to my brother Geoff for helping me actually get the algorithm right!
package com.davidehringer.geo; import org.apache.commons.lang.Validate; public enum CompassPoint { N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW; public enum CompassRose { EIGHT_POINT(8, new CompassPoint[] { N, NE, E, SE, S, SW, W, NW }), SIXTEEN_POINT( 16, new CompassPoint[] { N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW }); private int points; private CompassPoint[] lookupTable; private CompassRose(int points, CompassPoint[] lookupTable) { this.points = points; this.lookupTable = lookupTable; } public CompassPoint getPoint(int index) { if (index >= lookupTable.length) { throw new IllegalArgumentException("Invalid index: " + index + ". Max index is " + (lookupTable.length - 1)); } return lookupTable[index]; } } public String abbreviation() { return name(); } public static CompassPoint fromBearing(double bearing, CompassRose compassRose) { Validate.isTrue(bearing >= 0); Validate.isTrue(bearing <= 360); double shift = 360.0 / compassRose.points / 2.0; double normalizedBearing = (bearing + shift); int index = (int) (normalizedBearing / (360.0 / compassRose.points)) % compassRose.points; return compassRose.getPoint(index); } }
As shown previously, for the client, it is as simple as the following snippet.
CompassPoint point = CompassPoint.fromBearing(14.65, CompassRose.EIGHT_POINT); String abbr = point.abbreviation(); ...
In Closing
While not new, JUnit's parameterized tests are a very useful tool to have in your testing toolbox.
August 23rd, 2011 at 8:32 pm
Have you able to figure out how to customized the test name?
Cheers,
Harry
August 16th, 2012 at 3:40 am
This guide is very good, it actually made me understand how to use Parameterized properly in my tests. The test name thing is a bit of a problem though.
I have not had time to really use it in production, but I have created a couple of proof of concept parameterized tests using the CallbackParams runner. Check out the guides at http://callbackparams.org/
Using that runner, the parameterized tests will get “pretty” names in the test-report.
October 20th, 2012 at 7:49 am
Oh man. This guide just saved my life in my computer science degree JUnit exercises
Thank you so much. Until I read your article only had found examples with parameterized standard objects like “strings” and “integers”, but not one with our own created objects.
Much better well explained than my teacher pdf basic guide.
Thanks!
December 13th, 2012 at 12:49 pm
hi,
there is an easier way. check http://zohhak.googlecode.com it lets you write:
@TestWith({
“SIXTEEN_POINT, 11.24, N”,
“EIGHT_POINT, 22.5, NE”,
“EIGHT_POINT, 292.5, NW”
})
public void compassDirectionShouldMatchDegrees(CompassRose compassRose, double degrees, CompassPoint expectedCompassPoint) {
…
}
February 5th, 2013 at 3:01 pm
It would be nice if you could @Parameters annotate and Enum directly. I had an Enum in place but had to write this method:
@Parameters
public static Collection getProducts() {
ProductPage[] products = ProductPage.values();
Object[][] data = new Object[products.length][];
int i=0;
for (ProductPage pp : products) {
data[i++] = new Object[]{pp.name(), pp.url, pp.title};
}
return Arrays.asList(data);
}
April 22nd, 2014 at 11:02 am
Utilize a good topcoat soon after filling out your current manicure to help close up not to mention get rid of a person’s manicure, not to mention submit an application narrow touchup applications all other morning to hold ones own gloss because of chipping and peeling.
April 22nd, 2014 at 11:03 am
The way to Restyle a good Mink CoatFur jackets own for ages been overpriced luxurious things that keep hold of many importance when any overcoat on their own is not any for a longer period in vogue.
April 22nd, 2014 at 11:14 am
actuality a queen” (ProSieben, 20.15 Uhr)
echte louis vuitton taschen http://www.lili-marlene-dortmund.de/command/echte-louis-vuitton-taschen.html
April 22nd, 2014 at 11:15 am
Der erste Satz ist damit korrekt, Steht aber throughout the kefamily roomerlei Snzusammenhang zu weiteren.
k枚ln louis vuitton http://www.klaus-gruppe.de/premium/k枚ln-louis-vuitton.html
April 22nd, 2014 at 11:16 am
But if your dog’s fur topcoat has experience of quite a few harmful odours that will should be taken off, there’s an easy effortless option for taking away any aroma.
April 22nd, 2014 at 11:25 am
pass away ARD und ihre Kontrollgremien haben offensichtlich Schwierigkeiten, see-through zu agieren. “Mehrere Arbeitsgruppen sback ind have always been Sachen Transparenz Werk. er or him Sommer sollen erste Ergebnisse pr盲sentiert werden,
louis vuitton brille http://www.klaus-gruppe.de/premium/louis-vuitton-brille.html
April 22nd, 2014 at 12:17 pm
To enable some jacket to transport typically the downwards brand, the application requires a minimum of 50 per cent downward.
April 22nd, 2014 at 12:18 pm
When dried Bottle of spray a lot of jackets for normal greyish primer ontop in this.
April 22nd, 2014 at 12:19 pm
As you seek dental lab layers, one ‘re normally searching for a cleanse, skilled clinical coating through purses to carry the modest additional items perhaps a dog pen an/or a sheet of document or maybe notepad.
May 14th, 2014 at 5:00 am
You will be able of locating black purple apparel brightness orange layers ebony kitchen applications and even olive earth-friendly cocinero applications.
May 14th, 2014 at 5:00 am
My partner and i essentially consider being shown just how to train on a dress catch around kindergarten at the to start with evening.
July 11th, 2014 at 8:08 am
It’s not my first time to pay a visit this web page, i am visiting this site dailly and obtain fastidious information from here everyday.
October 20th, 2014 at 5:29 am
Yes! Finally someone writes about enumerator.
February 10th, 2015 at 9:56 am
あなたの手段の説明これでポスト Parameterized Tests in JUnit 4.x | Mindful Mischief をで本当に |実際には|で実際|真|真に} 良い気難しい、{すべて|一人一人がができる|のことができるように| 単に 知っているそれを意識して、どうもありがとう。
バーバリー アウトレット 公式 http://www.phuketthairestaurant.net/burberry.html
March 5th, 2015 at 4:42 am
Ryokan-sama had lived a size able part of his life as a hermit, although he was mostly remembered in certain circles for his calligraphy skills, and of course poetry, which according to those who knew much
better than my limited knowledge of the man, presented the
essence of Zen life. The seagulls were having a field day as the ferry moved away
from the jetty in a steady sideways motion,
the speed increased with the passing minutes and
soon we were out on the high sea. That said, I still had no
idea what public holiday it was, or if it was
one at all.
August 22nd, 2016 at 2:05 pm
最も激安い adidas アディダス レフェリーセット2 WOMEN MEN 770153 卸売価格,特価
August 2nd, 2018 at 9:05 pm
My brother indicated I may similar to this weblog. Your dog had been entirely correct. This post truly designed my personal working day.. website designing company You should not look at the way in which a great deal time frame I did expended just for this info! Thanks!
August 21st, 2018 at 9:46 pm
built concrete…
Parameterized Tests in JUnit 4.x | Mindful Mischief…