Fabrizio Giudici is a Senior Java Architect with a long Java experience in the industrial field. He runs Tidalwave, his own consultancy company, and has contributed to Java success stories in a number of fields, including Formula One. Fabrizio often appears as a speaker at international Java conferences such as JavaOne and Devoxx and is member of JUG Milano and the NetBeans Dream Team. Fabrizio is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

JUnit: A Little Beyond @Test, @Before, @After

08.26.2009
| 26568 views |
  • submit to reddit

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.

Published at DZone with permission of Fabrizio Giudici, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

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:

    // @ImagesTest support 
    @interface JUnitGenerator{
        // Marker
    }
    @JUnitGenerator
    @interface ImagesTestGenerator{
        ImageTestParms[] value();
    }

    @interface ImageTestParms{
        String path();
        String md5();
        int w();
        int h();
    }
    
    // Test file
    @ImagesTestGenerator(
    {
        @ImageTestParms(path = "coke.jpg", md5 = "baa62439fb6e5183bd7c7be304dbf45f", w=1080, h=900),
        @ImageTestParms(path = "pepsi.jpg", md5="5e2c9f279cfed2de1260d71ff7b3d9c2", w=1600, h=1200),
        @ImageTestParms(path = "coffee.jpg", md5="efcf7c6bcfad3179c8876a8b184d0551",w=480, h=360)
    })
    public void ImagesTestTemplate(String path, String md5, int w, int h) {
        ImageReader ir = getImageReader(path);
        assertRaster(ir, md5);
        assertWidth(ir, w);
        assertHeight(ir, h);
    }

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.

-------------------------------------- 
All 3 tests passed.(0.066 s)
     ImageTest_coke passed (0.0 s)
     ImageTest_pepsi passed (0.0 s)
     ImageTest_coffee passed (0.0 s) 
-------------------------------------- 

 

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

@anewhope: the spring testing module relies on @RunWith, so hey better do not remove this in the short term :)

Denes Csepely replied on Fri, 2009/08/28 - 3:59am in response to: Ignacio Coloma

Yeah, Spring does and libs/frameworks provide their runwith classes too. If you need DI you use spring's runwith. If you need DI and want to leverage a runwith of any other framework u can't do that (or i missed something). You cannot specify two or more runwith therefore a workaround solution needed. I think the runwith is not as flexible as it would be.

Fabrizio Giudici replied on Fri, 2009/08/28 - 8:53am in response to: Denes Csepely

@gjeudy: Yes, I know about the existence of TestNG and that it offers more features than JUnit - in a way, one can say the latest JUnit versions are trying to catch up. The problem is that I'm already working with too many new libraries and products... trying to keep a limit at the moment. Probably I'll give it a try later in the year.

@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

The problem is that I'm already working with too many new libraries nike shoes russia and products... trying to keep a limit at the moment. Probably I'll give it a try later in the year.

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 

Cata Nic replied on Tue, 2013/09/03 - 3:49am

 This post should be promotted into a better way. I think the solution presented here is easy to understand and should be promoted at large scale.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.