JUnit: A Little Beyond @Test, @Before, @After
Now, recent JUnit versions come with a handy way for running the same test multiple times, with variations. What we need is the pair of annotations @RunWith and @Parameters:
import javax.annotation.Nonnull;
import java.util.Collection;
import it.tidalwave.imageio.ExpectedResults;
import it.tidalwave.imageio.NewImageReaderTestSupport;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(value=Parameterized.class)
public class NEFImageReaderImageTest extends ImageReaderTestSupport
{
public NEFImageReaderImageTest (final @Nonnull ExpectedResults expectedResults)
{
super(expectedResults);
}
@Parameters
public static Collection<Object[]> expectedResults()
{
// discuss about this later
}
}
@RunWith is a powerful extension point with JUnit, as it lets you change the default test runner. Parameterized is, as the name says, a parameterized test runner, which runs the same test multiple times. It searches for a static method, annotated with @Parameters, that must provide a Collection of Object arrays. Each item in the Collection matches a test run; i.e. if the Collection contains 10 items, the test will be run 10 times. Each element in the Collection is a set of parameters; Parameterized now will search for a constructor of the test class that accepts arguments, and passes to it the objects in the array. Thus, if the arrays in the Collection are made of three items, the test constructor must accept three objects. In my case, things are simpler since I have a single parameter which is an instance of the ExpectedResults class: it holds all the expected results that will be verified.
The actual body of the test is implemented once and for all in the base class ImageReaderTestSupport:
public class ImageReaderTestSupport
{
private final ExpectedResults expectedResults;
public ImageReaderTestSupport (final @Nonnull ExpectedResults expectedResults)
{
this.expectedResults = expectedResults;
}
@Test
public void testImage()
{
// do al the stuff, assert against attributes of expectedResults
}
}
This is a sketch of code that creates a collection of ExpectedResults:
@Parameters
public static Collection<Object[]> expectedResults()
{
return fixed
(
// D1x
ExpectedResults.create("http://www.rawsamples.ch/raws/nikon/d1x/RAW_NIKON_D1X.NEF").
image(4028, 1324, 3, 16, "d3d3b27908bc6f9ed97d1f68c9d7a4af").
thumbnail(160, 120),
// D1
ExpectedResults.create("http://www.rawsamples.ch/raws/nikon/d1/RAW_NIKON_D1.NEF").
image(2012, 1324, 3, 16, "69c3916e9a583f7e48ca3918d31db135").
thumbnail(160, 120),
// D2X v1.0.1
ExpectedResults.create("http://s179771984.onlinehome.us/RAWpository/images/nikon/D2X/1.01/_DSC0733.NEF").
image(4320, 2868, 3, 16, "0cb29a0834bb2293ee4bf0c09b201631").
thumbnail(160, 120).
thumbnail(4288, 2848),
// D2Xs v1.0.0
ExpectedResults.create("http://s179771984.onlinehome.us/RAWpository/images/nikon/D2Xs/1.00/DSC_1234.nef").
image(4320, 2868, 3, 16, "37d5aa7aab4e2d4fd667efb674f558ed").
thumbnail(160, 120).
thumbnail(4288, 2848),
// D3
ExpectedResults.create("http://www.rawsamples.ch/raws/nikon/d3/RAW_NIKON_D3.NEF").
image(4288, 2844, 3, 16, "fadead8af5aefe88b4ca8730cfb7392c").
thumbnail(160, 120).
thumbnail(4256, 2832),
// D3x
ExpectedResults.create("http://www.rawsamples.ch/raws/nikon/d3x/RAW_NIKON_D3X.NEF").
image(6080, 4044, 3, 16, "2a0cfc36cea7c3346b8d39355bf786e6").
thumbnail(160, 120).
thumbnail(6048, 4032).
issues("JRW-221"),
);
}
}
As you can see, I'm using the fluent interface pattern to have readable code, and each instance specifies the URL of the test file, the size of the image, the number of colors and bits per sample, the MD5 of the raster, the size of the thumbnail(s), the Jira code of the issues covered by this test, etc. I'm also working on some more methods that, using reflection, are able to inspect metadata and assert single item values.
The static method fixed() is a very simple facility that converts the inlined array of ExpectedResults into the Collection of Object arrays that Parameters wants:
@Nonnull
protected static Collection<Object[]> fixed (final @Nonnull ExpectedResults ... er)
{
final List<Object[]> result = new ArrayList<Object[]>();
for (final ExpectedResults e : er)
{
result.add(new Object[]{ e });
}
return result;
}
This approach drastically reduced the number of LOC in my tests and allowed me to add more files and checks. I'm even evaluating to create the ExpectedResults instances with a Groovy script, that would basically make it possible to use a configuration file for declaring test files and expected results.
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)






Comments
Guillaume Jeudy replied on Wed, 2009/08/26 - 12:41pm
Fabrizio,
Have you tried testNG? It has a similar feature and seems easier to use than the example you posted here.
See @DataProvider annotation. The test runs also display much nicer in eclipse IDE than what you posted in this article. It shows the test method with arguments passed.
More over it has multi-threaded testing support though I haven't tried @DataProvider option combined with multi-threaded testing.
Anew Hope replied on Wed, 2009/08/26 - 8:43pm
@Fabrizio
One thing to note is that @RunWith is experimental in JUnit. I just checked the latest version of JUnit (4.7) and the javadoc is consistent with the following link:
http://junit.sourceforge.net/javadoc/org/junit/runner/RunWith.html
"...We added this feature late in development. While it seems powerful we expect the runner API to change as we learn how people really use it. Some of the classes that are currently internal will likely be refined and become public."
So I wouldn't rely on this for a large amount of testing. Also, the explanation of how JUnit finds constructors based on the number of arguments doesn't appear to be correct. I think a JUnit expects there to be only one constructor otherwise you'll get a "Testcase: initializationError" at least in JUnit 4 (standard in Netbeans 6.7.1).
I like your modification to Parameterized and hope they accept the changes.
Casper Bang replied on Thu, 2009/08/27 - 6:04am
Hmm, could one not also write a generator extension that hooks in front of JUnit's preprocessor? Then something like this would be possible:
@interface JUnitGenerator{
// Marker
}
At compile time, let a simple AnnotationProcessor (recognizing the marker interface JUnitGenerator) generate the @Test methods via TreeTranslator. JUnit will see things as if you handcoded each and every test method.
I realize code generation is a touchy issue, but I always found it handy for testing (although admitedly I've never done it exactly like this).
Ignacio Coloma replied on Thu, 2009/08/27 - 3:01am
in response to:
Anew Hope
Denes Csepely replied on Fri, 2009/08/28 - 3:59am
in response to:
Ignacio Coloma
Fabrizio Giudici replied on Fri, 2009/08/28 - 8:53am
in response to:
Denes Csepely
@anewhope. Yes, I know this thing is not part of a stable API. But it would not be hard to apply changes after JUnit eventually changes the API.
@casperbang. Very nice suggestion! No, I don't get worried by code generators :-) indeed there are already a couple in jrawio. Your suggestion sounds as a nice alternative to Groovy - indeed this brings me to a recurrent thought, that every time I think Groovy is useful for a problem, I later found (or somebody later suggests) an equivalent way to do things with Java ;-)
Peter Sellars replied on Sun, 2009/10/11 - 2:15am
in response to:
Fabrizio Giudici
Hi Fabrizio - did you submit a patch for this? Was it accepted or rejected? It seems useful to have a name for reference rather than just an index? Something like this could be an extended Parameterized class - such as IdentifedParamerterizedClass.
Am curious as I am going to use your patch for an upcoming presentation on this feature of JUnit. Will acknowledge you in my presentation and link to this page. Am also going to look at using a file/or group of files to contain the test data.
john green green replied on Mon, 2009/10/26 - 3:27am
Mateo Gomez replied on Tue, 2012/04/17 - 12:28am
in response to:
Fabrizio Giudici
hey Farbrizio, great job on this for answering the question back.Thanks!
mexican dessert recipes
Matt Coleman replied on Fri, 2013/01/11 - 1:06am
great test...i so appreciate the before and after of it
buffalo search engine optimization