Five Unit Tests Tips #3: Parametrized test methods
The following is a trick I don’t use very often, but when I do need it, it comes in very handy. It’s a trick that many developers aren’t aware of, even though it’s been possible to do with JUnit at least since version 3.
Sometimes you want to have a lot of tests that are almost the same, but that contain different arguments. For example, for a yahtzee calculator, you might want to have the following tests:
checkRollScoredAsCategoryGives(1, 2, 3, 4, 5, "straight", 15);
checkRollScoredAsCategoryGives(1, 2, 3, 4, 4, "straight", 0);
checkRollScoredAsCategoryGives(3, 3, 3, 4, 4, "full house", 17);
checkRollScoredAsCategoryGives(1, 2, 3, 4, 4, "full house", 0);
checkRollScoredAsCategoryGives(4, 4, 4, 4, 4, "full house", 0);
This is possible with JUnit, but a little trick. You have to construct a test suite manually and add special subclasses of TestCase to it:
public static Test suite() {
TestSuite suite = new TestSuite(
YahtzeeScoreTest.class.getName());
suite.addTest(checkRollScoredAsCategoryGives(
1, 2, 3, 4, 5, "small straight", 15));
suite.addTest(checkRollScoredAsCategoryGives(
1, 2, 3, 4, 4, "small straight", 0));
suite.addTest(checkRollScoredAsCategoryGives(
3, 3, 3, 4, 4, "full house", 17));
suite.addTest(checkRollScoredAsCategoryGives(
1, 2, 3, 4, 4, "full house", 0));
suite.addTest(checkRollScoredAsCategoryGives(
4, 4, 4, 4, 4, "full house", 0));
return suite;
}
private static Test checkRollScoredAsCategoryGives(
int die1, int die2, int die3, int die4, int die5,
final String category, final int expectedResult) {
final int[] dice = new int[] { die1, die2, die3, die4, die5 };
return new TestCase("Rolling for " + category) {
@Override
public void runTest() throws Throwable {
YahtzeeScoreboard board = new YahtzeeScoreboard();
board.scoreRoll(category, dice);
assertEquals(expectedResult, board.getTotalScore());
}
};
}
The code creates an anonymous inner subclass of TestCase that instead of calling all methods annotated with @Test just executes our specific test. These tests are collected in a normal JUnit TestSuite.
The test is plain JUnit and will run in Maven or your favorite IDE, just as any other tests. The fact that the suite is named after the test class will let Eclipse know where to go when you double click on the test from the test runner.
This trick can be very useful for tests of logic that calculates a results or validates input.
Comments:
Mario Gleichmann - Feb 12, 2009
Nice idea,
when confronted with a quite similar problem, i remembered FIT as a valuable tool for testing different ‘input’ combinations, coming up with a little TestCase extension that behaves like FITs ColumnFixture but only in the context of JUnit.
You may want to take a look for an example as well as the extended TestCase at
http://gleichmann.wordpress.com/2007/11/21/jfit…
Greetings
Mario
jhannes - Feb 25, 2009
In this example, I agree with you that the technique isn’t too useful in this particular instance. The places I’ve used this has been for things like String validation rules, complex calculations with many simple input data but many special rules etc. The example is meant to illustrate the technique, but it might be a poor example.
Johannes Brodwall - Feb 10, 2009
Good reference, Andre. Personally, I’ve never liked TestNG much, but it does solve this problem in a built in fashion. You should be able to do what I show with JUnit 3.8.1.
[andreb] - Feb 10, 2009
Hi Johannes.
What you’re doing here is supported by TestNG, using the @DataProvider annotation. Unfortunately, the project I’m currently working on is still using JUnit 3.8.1, so I’m definitely going to give your tip a try.
André
[Me] - Feb 24, 2009
I prefer
@Test public void checkSmallStraight() { checkRollScoredAsCategoryGives(1, 2, 3, 4, 5, "straight", 15); checkRollScoredAsCategoryGives(1, 2, 3, 4, 4, "straight", 0); } @Test public void checkFullHouse() { checkRollScoredAsCategoryGives(3, 3, 3, 4, 4, "full house", 17); checkRollScoredAsCategoryGives(1, 2, 3, 4, 4, "full house", 0); checkRollScoredAsCategoryGives(4, 4, 4, 4, 4, "full house", 0); }