Monday, November 22, 2010

Clearer Unit Tests: Assert, Guard, and Throw


I hate the book “xUnit Test Patterns” for being over 800 pages long. I am convinced a good editor can turn any 800 page book into a 700 pager with the exact same content… that includes you Leo Tolstoy! The problem is that xUnit Patterns is still the definitive reference to test concepts and implementation. There is no better book on testing available, but the sheer weight and size the book means few have actually read it. Well I have, and I highlighted the damn thing just to prove it. In this post you’ll find 3 techniques to improve test clarity that, in my opinion, are underutilized in the development world today.


Custom Assertion Methods

I commonly see assertions repeated throughout a test case in sets. For instance, my current system deals in User objects, and you can see the same 3 lines of code sprinkled throughout a test case:


User user = ...
assertEquals("userId", user.getUserId());
assertEquals("fname", user.getFirstName());
assertEquals("lname", user.getLastName());


This is a form of test code duplication. It plain English, you want to say “make sure that the user we got back is the same one we expect”. This points to two concerns: an assertion (“make sure”) and equality (“the same”). It is temping to refactor using Extract Method, but this is a poor solution:


User user = ...
assertEquals("userId", "fname", "lname", user);

static assertEquals(String userid, String fname, String lname, User user) {
assertEquals("userId", user.getUserId());
assertEquals("fname", user.getFirstName());
assertEquals("lname", user.getLastName());
}


This isn’t bad, it just isn’t good. The test reads better, but the parameter list of the custom assertEquals is quite busy. It sort of half encapsulates equality. Equality is defined in the assertion method, but the attributes that contribute to equality are defined in at the place where assertEquals is invoked. A better solution is proper, canonical Assertion Method: two parameters and static.


User actualUser = ...
User expectedUser = ...
assertEquals(expectedUser, user);

static assertEquals(User expected, User actual) {
assertEquals(expected.getUserId(), user.getUserId());
assertEquals(expected.getFirstName(), user.getFirstName());
assertEquals(expected.getLastName(), user.getLastName());
}


I follow this pattern because it keeps my options open. The assertEquals method is static. When I need to move it to my AssertionUtils or CommonAssertions class, then it is just a keystroke away (Ctrl+Alt+V or F6). Also it is common to construct a factory method for a canonical, or expected user. Having assertEquals written with a (User, User) parameter list means that you can hide the expected data behind a factory method and not have it clutter up your test. For me, a custom assertion method is the cleanest solution. The test code is readable because it captures the equality concern without showing the equality implementation. Hey, it’s an abstraction!

Guard Assertion

In tests, decisions are the enemy of clarity. If you read a test method, any decision you come across is a detail that can clutter and obfuscate the meaning of the test. Mock object setup is classic clutter: “when x is called then return y” is a setup step that seldomly is a meaningful part of the test case. But assertions can contain decisions as well. Consider this test:


List users = ...
if (users != null) {
if (users.size() == 1) {
User actualUser = users.get(0);
assertEquals(expectedUser, actualUser);
} else {
fail("user list wrong size");
}
} else {
fail("null users");
}


On first glance it looks complicated. This is how we sometimes write production code… be defensive and always guard against null pointers and illegal state. But remember, tests are different. What do you want your test to do when there is a null pointer? Blow up with a null pointer exception, of course! Forget these fail() methods and just write it so it blows up:


List users = ...
assertEquals(1, users.size());
assertEquals(expectedUser, users.get(0));


It is shorter, and less to take in. But there are less decisions in the test as well. No deciding on sizes and nullability. If the user list of user is null then it blows up with a stack trace. Which is what you want in a test! The conversion from conditional statements to assertions is called a Guard Assertion. Use them to clarify your tests by eliminating unneeded conditionals and decisions.

Throw Everything

I blogged a while back about testing exceptions. Now it is time to say don’t test exceptions. Consider this approaches to handling exceptions in test methods:


public void testSomething() throws IOException,
LoggedException,
EnterpriseException {
...
}


This happens with fancy IDEs. A quick shortcut adds the exception to the method signature. But what’s the point? There is absolutely no reason a test shouldn’t just throw Exception. It is simpler, you can add it to your test method template, you never have to look at it again, and you never have to think about it. Throw Exception. It’s the simplest thing you can do. Another common occurrence is to see this type of exception code in a test method:


public void testSomething() {
try {
...
} catch (IOException ex) {
fail("Could not execute test");
}
}


Yes, it is important to fail the test when an exception occurs. But do you know what happens when fail executes? It throws an unchecked exception. This code suppresses the cause of the exception only to throw a different one. The try catch block is a form of an unneeded decision in your test case. It can be completely eliminated by just throwing Exception in the test method. This way your stack trace points to where the problem occurred, not where it was handled. Both these examples are simplified into the same result:


public void testSomething() throws Exception {
...
}

That’s it: 3 tips for clearer test code. And in far less than 800+ pages!