The latest and greatest JUnit features… expand your toolset
November 11, 2010 Leave a comment
Junit has been around for some time and it is still a favorite of mine, well it became my favorite after it moved to annotations instead of having to extend that pesky TestCase class. Modern Junit tests are small concise and extremely handy when it comes to testing your code. Let’s look at some of the latest features that Junit offers and see how they can help you write better code with Test Driven Development paradigm.
Categories
Each test method and test class can be annotated as belonging to a category:
public static class SomeUITests {
@Category(UserAvailable.class)
@Test
public void askUserToPressAKey() { }
@Test
public void simulatePressingKey() { }
}
@Category(InternetConnected.class)
public static class InternetTests {
@Test
public void pingServer() { }
}
To run tests in a particular category, you need to set up a test suite. In JUnit 4, a test suite is essentially an empty annotated class. To run only tests in a particular category, you use the @Runwith(Categories.class) annotation, and specify what category you want to run using the @IncludeCategory annotation
@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { AccountTest.class, ClientTest.class })
public class LongRunningTestSuite {}
You can also ask JUnit not to run tests in a particular category using the @ExcludeCategory annotation
@RunWith(Categories.class)
@ExcludeCategory(SlowTests.class)
@SuiteClasses( { AccountTest.class, ClientTest.class })
public class UnitTestSuite {}
assertThat
Two years ago, Joe Walnes built a new assertion mechanism on top of what was then JMock 1. The method name was assertThat, and the syntax looked like this:
assertThat(x, is(3));
assertThat(x, is(not(4)));
assertThat(responseString, either(containsString("color")).or(containsString("colour")));
assertThat(myList, hasItem("3"));
More generally:
assertThat([value], [matcher statement]);
Advantages of this assertion syntax include:
- More readable and typeable: this syntax allows you to think in terms of subject, verb, object (assert “x is 3″) rather than
assertEquals, which uses verb, object, subject (assert “equals 3 x”) - Combinations: any matcher statement
scan be negated (not(s)), combined (either(s).or(t)), mapped to a collection (each(s)), or used in custom combinations (afterFiveSeconds(s)) - Readable failure messages. Compare
assertTrue(responseString.contains("color") || responseString.contains("colour"));
// ==> failure message:
// java.lang.AssertionError:
assertThat(responseString, anyOf(containsString("color"), containsString("colour")));
// ==> failure message:
// java.lang.AssertionError:
// Expected: (a string containing "color" or a string containing "colour")
// got: "Please choose a font" - Custom Matchers. By implementing the
Matcherinterface yourself, you can get all of the above benefits for your own custom assertions. - For a more thorough description of these points, see Joe Walnes’s original post.
We have decided to include this API directly in JUnit. It’s an extensible and readable syntax, and it enables new features, like assumptions and theories.
Some notes:
- The old assert methods are never, ever, going away. Developers may continue using the old
assertEquals,assertTrue, and so on. - The second parameter of an
assertThatstatement is aMatcher. We include the Matchers we want as static imports, like this:import static org.hamcrest.CoreMatchers.is;or:
import static org.hamcrest.CoreMatchers.*;
Assumptions
Ideally, the developer writing a test has control of all of the forces that might cause a test to fail. If this isn’t immediately possible, making dependencies explicit can often improve a design.
For example, if a test fails when run in a different locale than the developer intended, it can be fixed by explicitly passing a locale to the domain code.
However, sometimes this is not desirable or possible.
It’s good to be able to run a test against the code as it is currently written, implicit assumptions and all, or to write a test that exposes a known bug. For these situations, JUnit now includes the ability to express “assumptions”:
import static org.junit.Assume.*
@Test public void filenameIncludesUsername() {
assumeThat(File.separatorChar, is('/'));
assertThat(new User("optimus").configFileName(), is("configfiles/optimus.cfg"));
}
@Test public void correctBehaviorWhenFilenameIsNull() {
assumeTrue(bugFixed("13356")); // bugFixed is not included in JUnit
assertThat(parse(null), is(new NullDocument()));
}
Theories
@RunWith(Theories.class)
public class UserTest {
@DataPoint public static String GOOD_USERNAME = "optimus";
@DataPoint public static String USERNAME_WITH_SLASH = "optimus/prime";
@Theory public void filenameIncludesUsername(String username) {
assumeThat(username, not(containsString("/")));
assertThat(new User(username).configFileName(), containsString(username));
}
}
This makes it clear that the user’s filename should be included in the config file name, only if it doesn’t contain a slash. Another test or theory might define what happens when a username does contain a slash.UserTest will attempt to run filenameIncludesUsername on every compatible DataPoint defined in the class. If any of the assumptions fail, the data point is silently ignored. If all of the assumptions pass, but an assertion fails, the test fails.
Theories match data points by type, so if you have a theory that take an int as an argument and you have 5 int @Datapoints, your theory will run a total of 5 times. This gets more complicated if you add a second parameter that is a double since you will get every possible permutation ran for the combination of the number of total parameters.
I’d hate to wrap up a discussion about Junit without mentioning TestNG. Both TestNG and Junit are competing testing frameworks but they are not mutually exclusive from one another. I use both in my projects normally since I can take advantage of features one has that the other ones does not. That being said, I usually lean more toward Junit during development since it is the most widely recognized and understood by more people I work with. Both are great testing frameworks so it makes it difficult to pick on over the other so I just don’t.
