Writing good unit tests, Part 1: Follow your GUTs

Best practices and tools for high-quality test code

1 2 3 4 5 Page 5
Page 5 of 5

Real test coding

Of course, there is more to writing good unit tests than good style and code coverage. Therefore, let me reassemble and extend some rules Andy Schneider gave us in his December 2000 JavaWorld article entitled "JUnit best practices," complemented by some rules I personally find useful. (And you should remember that in 2000 Schneider was describing JUnit 3.2, and that TestNG wasn't released until about 2004!)

  • Do not use the test-case constructor to set up a test case. Test case setup should be part of your setUp() method (per test method or per test class) if it contains general initializations for your test. This also conforms to the famous DRY (don't repeat yourself) principle to avoid code duplication.

    I would like to go one step further and postulate that you should not initialize non-trivial objects (which may need things like remote resources) in static variables or static initializers. That could cause your unit test to crash during the class loading phase!

  • Call a superclass's setUp() and tearDown() methods when subclassing. If you don't, superclass behavior will be skipped. This rule is only related to JUnit 3.8.x (and below), because with JUnit 4 and TestNG you don't need to make your test a child class of a test framework class. You can easily check this rule with the static code analyzer Checkstyle. PMD, another representative of this category, has an even stronger built-in ruleset that deals with different problems that can occur with JUnit tests. With an Eclipse version that has Java 5 annotation support, you can use the @Override annotation to force the compiler to ensure that setUp() really overrides the superclass method (given that you have enabled the corresponding Eclipse settings).

  • Implement your tests as independent, isolated, and self-contained modules. For example, you can do this by using mocks or stubs wherever possible. Mock objects help you design and test the interactions between the objects in your programs. But good mocking is not a trivial task, even with the help of "expect-run-verify" libraries like EasyMock or JMock. (For a short introduction to the subject, see Simon Stewart's article on "Approaches to mocking.")

  • Avoid writing test cases with side effects.

  • Do not load data from hard-coded locations on a filesystem.

  • Name tests properly. This is a special case of what Kent Beck talks about in his book Implementation Patterns when it comes to writing, naming, and decomposing methods. Naming is always an essential subject -- for variables, for test methods, and especially for assertions. (The second half of this article will discuss writing proper assert statements in detail.)

  • Ensure that tests are time-independent. Where possible, avoid using data that may expire; such data (expired logon tokens, for example) should be programmatically refreshed. If this is not possible, you need a manual refresh, but that's a more fragile procedure.

  • Consider locale when writing tests. That's especially important when dealing with the Java classes Date, DateFormat, or Calendar.

  • If you're using JUnit, use its assert/fail methods and exception handling for clean test code. Writing good assertions and dealing with exception handling is an important topic for good unit tests.

Rules made to be broken?

While all of the above rules will help you write good unit tests, there are a couple of commonly accepted guidelines that I don't necessarily agree with:

  • Don't assume the order in which tests within a test suite will run. In other words, the conventional wisdom says that your tests should never have an order dependency.

    This probably doesn't need to be a strong rule as a core test design principle. If you're testing a moderately complex system, it may become crucial to have some kind of dependency mechanism like the one TestNG offers. In contrast, JUnit does not accept any compromise. Both sides of this debate were argued in the comments to Cedric Beust's blog post "Are dependent test methods really evil?" The debate on this topic will no doubt continue.

  • Keep tests in the same location as the source code. I have to say that I disagree with this rule. It's possible to follow it, but I find it better to have a parallel test class hierarchy in a directory called test (corresponding to src) that reflects your package structure. That way, you still have easy access to package private methods (and data) if needed without going the painful route of using reflection and setAccessible(true), or by providing a kind of PrivateFieldAccessor class. (For more on this topic, see "Subverting Java access protection for unit testing.")

Conclusion, and a look ahead

You've just learned how, in general, software quality is measured, and about the difference between external and internal quality. In this article, you looked at the internal quality of unit tests code. I have presented some best practice rules -- both from a more technical angle and from a process-oriented viewpoint. Special attention was given to the topic of code coverage as a measure of how well your tests perform with respect to executing your system under test code's different statements and branches.

In the second half of this article, I'll introduce you to how to write proper assert statements -- the basis of test verdicts -- and we'll go through different implementations for testing exceptions. Finally, I'll discuss code smells, refactorings, and test patterns.

Klaus P. Berg has a master's equivalent (Diplom) in electrical engineering and applied informatics from the University of Karlsruhe in Germany. He was an architect and implementor for projects at Siemens focused on Java GUI development with Swing and Java Web Start, and he also acted on the server side, creating Java-based intranet applications. Now he works as a senior engineer in the area of software quality, focusing on functional and performance testing, mainly for JEE software.

Learn more about this topic

Further reading

Testing tools

More from JavaWorld

1 2 3 4 5 Page 5
Page 5 of 5