Pramatr Blog

A collection of articles from pramatr.com on technology, security, software and anything we find interesting

Unit Test Assertions: What Could Possibly Go Wrong?

Posted by pramatr on November 6th, 2008

Lots of my development effort is taken up writing unit tests. These tests form a nice warm security blanket for the code that I’ve developed. If the code is broken, typically these tests are the first feedback that something is wrong. Although some people fear the little red bar, it’s become a common part of development. So with this in mind, it’s vitally important that when a test fails, is crystal clear what really went wrong.

Over the past few weeks, I’ve been reading xUnit Test Patterns (review to follow in another post), which covers making tests easier to write, understand and maintain. One important issue it discusses, is the use of assertion methods.

Assertions are a fundamental part of writing unit tests, and are typically very simple to use. Within each test, the expected outcome is expressed using assertions, each of which is boolean expression. The assertion should evaluate to true, so if the evaluation of the assertion instead returns false, the test will be failed. JUnit provides the Assert class to handle various types of common assertion. Although Assert provides a number of very simple methods, I’ve seen these frequently used inappropriately, causing unit test failures to be unnecessarily difficult to track down.

Assertion Selection

When reviewing unit tests, one of the typical activities is to ensure that assertions are being used appropriately. When developers first start to write unit tests, assertions are often used inappropriately and as such reviews can become very time consuming. Tools play a major part in development and can automate many of these kind of manual review activities. PMD is one such tool, and it provides a rule set to deal with the common JUnit problems.

One of the most common selection problems is the use of the stated outcome assertion instead of the equality assertion. When the equality assertion evaluates to false, it includes information about the two objects that should have been equal in the failure message. This information allows a developer to quickly get an idea what went wrong in the test. If the developer had used a stated outcome, (such as assertTrue), no information would have been recorded about the object state. This typically leads to extra log statements being added, or the developer has to being debugging the test.

Ensuring that the appropriate assertion is used within each unit test, is a very simple thing to do, but it gives very clear feedback about failures and allows developers to quickly track their cause.

// assertTrue("code worked", true);
// this should be removed as it serves no purpose

// assertTrue("code didn't work", false);
// Fails a test with the given message.
fail("code didn't work");

// assertTrue("actual is not empty", !actual.isEmpty());
// Asserts that a condition is false.
assertFalse("actual is not empty", actual.isEmpty());

// assertTrue("object is null", actual == null);
// Asserts that an object is null.
assertNull("object is null", actual);

// assertTrue("object is not null", actual != null);
// Asserts that an object isn't null.
assertNotNull("object is not null", actual);

// assertTrue("objects are the same", expected == actual);
// Asserts that two objects refer to the same object.
assertSame("objects are the same", expected, actual);

// assertTrue("objects are not the same", expected != actual);
// Asserts that two objects do not refer to the same object.
assertNotSame("objects are not the same", expected, actual);

// assertTrue("objects are equal", expected.equals(actual));
// Asserts that two objects are equal.
assertEquals("objects are equal", expected, actual);

Assertion Configuration

The equality assertion method provides a very simple contract, requiring the expected and the actual value. Although the contract is simple, one of the most common problems I have encountered, is that the arguments passed in to the method are in the wrong order (e.g. the expected value argument is actually the actual value and vice versa).

Although this is a trivial issue to correct, I have seen developers waste large amounts of time trying to work out what’s wrong with the code when a unit test fails. The developer immediately start to scratch their head as the values don’t seem to make sense. Without reviewing the unit test, they dive into the code to solve the problem.

I have seen this happen countless times, and every time it happens, the developer in question can’t believe they didn’t have a look at the unit test first. The equality assertion isn’t difficult to use, you just have to remember; expected, actual…. expected, actual…. expected, actual…. expected, actual….

// assertEquals("objects are equal", actual, expected);
// Asserts that two objects are equal.
assertEquals("objects are equal", expected, actual);

Assertion Messages

xUnit Test Patterns provides an great discussion about the assertion message pattern. Most developers I’ve spoken to, do add assertion messages to their tests, and I also personally find them very useful. I completely agree with Meszaros when he says;

A well-crafted Assertion Message makes it very easy to determine which assertion failed and exactly what the symptoms were when it happened.

The assertion message should spell out exactly what the author of the test was expecting to happen. When the test fails, the message should provide enough detail for the developer to understand exactly what the problem was. The time taken to write a well-crafted assertion message, is more than paid back when the assertion fails!

// assertTrue(actual.startsWith(expected));
// Asserts that a condition is true.
assertTrue( "Actual should start with expected but actual was '"
+ actual + "' and expected was '" + expected + "'", actual.startsWith(expected));

Conclusion

If you are writing unit tests, spend a couple of minutes reading over the reference manual to see what assertion methods you have to work with. xUnit Test Patterns breaks down the types of assertion into; Single Outcome, Stated Outcome, Expected Exception, Equality, Fuzzy Equality. Understand what each assertion does and more importantly, understand when and where to use assertion. Test failures become much easier to track down, when it’s clear what went wrong. Use the right assertion, use the assertion correctly and give it a meaningful message. It really is a simple as that, what could possibly go wrong?

  • Pramatr
    @Asgeir S. Nilsen: Thanks for the tip, I have looked at this briefly but I haven't really played with it enough to have an opinion ;-). I does look handy though, I'm interested to see how useful it is.
  • Asgeir S. Nilsen
    Nice article. One suggestion to get more readable messages is to use JUnit 4.4's assertThat method together with hamcrest matchers.


    You can then state an assertion as





    assertThat(actual, equalTo(expected));





    and get a very nice error message back if the assertion fails.
  • Pramatr
    The message versions are very much personal preference. The book I mentioned does discuss this issue a little. If you are running this through Eclipse and following TDD, I can completely understand your point. One assertion per test, why do I need a message? Lots of the tests I've had to maintain however, haven't been this well written. The message therefore helps example the maintainer understand what's going on.

    I do prefer your chosen TDD approach :-), it's pretty hard convincing everyone it's the way to go however.
  • Dean
    I rarely use the message versions of the assertions. I use very meaningful names everywhere, I run all my tests in Eclipse where I get a clickable stack trace, I use TDD so I know what I just changed since the last test run, and I use the expected vs. actual assertions where possible. As a result, I rarely need more information. Not writing the messages reduces clutter, saves a lot of time, and requires zero maintenance of the messages!
  • Adam Peters
    xUnit Test Patterns sounds like an interesting read. Thanks for the links to the site, I'd be quite interested to see your review!
  • Pramatr
    That's a fair point B. I was only really thinking of JUnit when I wrote this. I guess the general point was, read the manual and pass the values in the correct order ;-).
  • B
    The xUnit frameworks may use 'expected','actual' by convention, but other frameworks also use 'actual','expected' (i.e. testNG). My group actually uses 'actual' as the first argument for our unit test framework. We find it easier to remember the order alphabetically =).


    Nice article. I enjoyed the read.
  • mde
    Good point about the 'expected' versus 'actual.' Most of the time people seem to get it the wrong way around. I just got through looking at some tests for Dojo where it was initially hard to figure what had failed because the test writer had it backward.
  • Andreas Miztk
    I can't tell you how much code I've had to clean up when everything is treated as an assertTrue(). People find something that works and stick with it, plain and simple!
blog comments powered by Disqus