Introduction

JUnit is a framework for Java unit tests written by Erich Gamma and Kent Beck.

Writing JUnit tests will help you in the implementation of your code. By writing tests first, you are encouraged to program to an interface instead of a specific implementation. This results in a more loosely coupled system that is easier to change. It also allows more effective use of mock objects in testing.

On a fuzzier note, writing tests also encourages better code as you have to think more carefully about writing code that is able to be unit tested. This implies that your code units (classes) should:

  • have a well defined (single) function
  • encapsulate non-public information
  • expose only necessary functionality
  • not rely on other classes wherever possible

and many more such statements that can be found in most 'good design principles' blurb.

Types of Test

A JUnit test is designed to exercise the functionality of a code unit, e.g. a class. If you want to write a test that exercises more than one class in the system, you are writing functional tests. It is this distinction that makes the use of mock objects key. The mock objects are used to provide stub classes for all classes in the system except the class you wish to test. This allows you to show that any tests which fail are the result of the class itself being change and not because of a side-effect somewhere else in the system.

-- PeterShillan - 19 Mar 2003

Naming conventions.

Maven likes you to keep unit tests in a separate directory structure to the production code. These are called src/java and test/java by default. The package structures can mirror each other, so that unit tests belong to the same package as the code under test. At the same time, it is easy to separate the test and production code as needed. Best of all is the ability to unit test package methods but without the tests living in the same directory as the classes. After all, package methods can be used by other classes you might not have originally thought of and so should be tested as thorougly as the public methods.

Best Practices

For more detail on these see here.

Write tests first

This seems counter-intuitive but is actually the most fundemental JUnit practise.

It is very difficult to test your own code. This is due to the fact that you wrote it. You know how it is supposed to work and it is all too easy to subconsciously code to your implementation when writing the tests. This will lead to tests that pass but which do not exercise the code effectively. Writing the test first means you don't know anything of the implementation and cannot fall in to this trap.

It is true to say that there must be something available to test. That something will be an interface and stub implemenation class that you write after your test and which is based on your design. You should find that, if you write an interface against which you will run your tests, you will have a number of positive effects:

  • Loose coupling
  • Easier to use Mock Objects
  • Design by contract becomes more straightforward

If anyone else is a JUnit novice like me, there's a very good tutorial about using JUnit with Support.Eclipse: http://www.3plus4software.de/eclipse/junit_en.html - Elizabeth Auden, 25/04/03

Test all execution paths

All paths a class can follow should be executed by your unit test. If any portion of your code has not been tested, it cannot be said to work. To help in this, you can use a tool such as Gretel. This can be integrated into an Ant build using GretAnt. This tool will provide a report showing exactly which lines code has been tested and which have not.

More conveniently, look at the Clover report generated as part of the Maven build. Thos provides similar functionality. -- NoelWinstanley 29/09/03

Start clean, every time

Unit tests should start off with no assumptions. A test should be entirely self contained that is, require nothing to be setup that it does not setup itself. When a test has completed, it should clean up after itself such that the system is in the same state as before the test began.

Run the tests with every build

Unit tests should be run on every build (though not necessarily every compile). Ant is very happy with this arrangement and can easily run all unit tests in your project and gives a nice report showing results.

Use mock objects

Mock Objects are skeleton objects that provide just enough behaviour to allow a class to run while ensuring a certain level of isolation within your unit test. A unit test should test only the class it is designed for and no other. If other classes under development are used by the class to be tested (parameters, member variables etc) then by passing in a mock object instead you can not only ensure this isolation but the mock object can be checked as part of the test to ensure the class being tested called only and exactly what you expected upon this object.

Mock object tool support is available via MockMaker. This can be run from the command-line and integrates with IDEs such as Support.Eclipse.

Mock Objects: A Cautionary tale

Many people who advocate mock objects would mock everything up to and including databases. This is an approach which will never end. It is far more sensible to, using the database example, have a database abstraction layer which you test against a real database and then mock the abstraction than to attempt to mock a database. Such resources are usually far too complex to be mocked effectively.

Implement a 'do nothing' constructor

The constructor should only call super() and nothing else. It should not be used to setup the test ... the setUp() method is the place to do that.

One common mistake made by newbies is to assume that only one test object is created in the course of running test. This isn't right - a new test object is created for each test Method. Furthermore, setUp() and tearDown() are called before and after every test. -- NoelWinstanley 29/09/03

Never assume a test order

The testXXX() methods in your class should never be order dependant.

Avoid writing test cases with side effects

Tests should be repeatable N times and should be independant.

Call a superclass's setUp() and tearDown() methods when subclassing

You may decide to have a common superclass for some tests further down the line.

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

Remember ... every developer on the project will be running your tests.

However, it can be really useful to load data from a file as part of testing. Store these datafiles as resources on the classpath, and use Class.getResource() or Class.getResourceAsStream() to access them. Provided the resources are bundled with the classes, these methods will succeed in any situation -- NoelWinstanley 29/09/03

Ensure that tests are time-independent

They should be runnable at any time and should not have to pause for 'magic number' seconds before continuing.

Consider locale when writing tests

This is important when using dates and developers may be in different locales.

Utilize JUnit's assert/fail methods and exception handling for clean test code

Do not write code such as:

try {
  var.foo();
}
catch(SomeException e) {
  fail("error");
}

JUnit will trap this exception itself and provide an error report. Also, use the variations of assert() to make assertions more readable (e.g. assertEquals(), assertTrue() and opposed to assert(x==y)).

Its also good to use the assert methods that take a string as first parameter. This string is printed when the assertion fails. This makes the output more descriptive, but also makes the code self-documenting - other developers can see what your test method is checking for -- Noel Winstanley 29/09/03

Document tests in javadoc

You have to write the code so document the test here too. This will avoid your test plan getting out of sync.

Avoid visual inspection

Tests should run automatically with no user intervention.

Keep tests small and fast

Developers will be tempted to 'skip' running the test each time if they take too long.

Use the reflection-driven JUnit API

JUnit can detect what methods are test methods if you follow its conventions. Doing so will stop your tests getting out of step.

Test thread safety

If a class is used in a multi-threaded environment, ensure thread safety is tested.

Testing Hints

Databases

One thing I've found works well is to use an in-process java database for testing against HSQLDB. In the setup method initialize the database, by running an SQL script to create tables and populate with test data; and then setup the connection to the db. The tests can then exercise code as usual, without the need for an exernal database. For examples, see http://www.astrogrid.org/maven/build/datacenter/xref-test/org/astrogrid/datacenter/queriers/sql/SqlQuerierTest.html

Axis

Maybe this is more of an integration testing hint
It is possible to run an Axis web service 'in-process' - i.e. in the same process as the calling code. This still goes through the rigmarole of converting all data from Java to XML and back again (which is what we want to exercise), but removes the need for an external server.

This is really handy - you can test interactions between client-side delegate and server-side service code without having to set up and deploy the axis web service somewhere first.

What you need:

  • The wsdd file that is used to deploy the web service conventionally.
  • The classes & libraries used by the web service need to be present on the classpath.

How to do it.

In the 'setUp' method of the unit test, you need to 'deploy' the web service locally. This is done using the axis AdminClient .
 String[] args = {"-l","local:///MyServiceName", "/file/path/to/myservice.wsdd"}; 
 org.apache.axis.client.AdminClient.main(args); 

You can then create a delegate / client that will talk to the local web service by passing in "local:///MyServiceName" as the service endpoint. The delegate can then be used as normal.

Of course, you'll need to set up any other resources, etc that the web service depends upon. For an exammple, see http://www.astrogrid.org/maven/build/datacenter/xref-test/org/astrogrid/datacenter/DatacenterTest.html

Axis also seems to support a 'java:' protocol - anyone know how this differs from 'local:'?

Use Inheritance and Static Helper classes.

Unit test classes don't need to extend the junit.framework.TestCase class directly. Spend a bit of time designing your test hierarchy. Try to factor common functionality into an abstract base test class that extends junit.framework.TestCase. Then make each of your unit tests extend your base test class. Don't cut and paste stuff when writing new test classes - put it in the base class, and make the new test class extend this.

Candidates for refactoring are repeated initialization code (e.g. set up db connection, initialize xml parser, load properties, etc) and repeated test patterns (e.g. code to check equality of xml documents, validate against a schema, etc)

Test Methods can delegate testing to other methods

Apologies if this is stunningly obvious. Avoid repeating the same test pattern in a sequence of tests. Instead, factor out the pattern into a separate method, and call this method from each of your tests with different parameters. e.g.
public class MyTest extends TestCase {
  public void testA() {
    MyFoo foo = new Foo();
    doTest(foo);
  }

  public void testB() {
    MyFoo foo = new Foo("a different kind of foo");
    doTest(foo);
  }

  public void testC() {
    MyFoo foo = new Foo();
    foo.setPling();
    doTest(foo);
  }
  . . .
  protected void doTest(MyFoo foo) {
    assertNotNull(foo);
    assertTrue(foo.mumble());
    ....
  }
}
This pattern comes into its own for data-driven testing.

Use JUnit assertions in non-test code

A little controversial, this one, as Java 1.4 already provides an 'assert' keyword. However, 'assert' doesn't provide the rich set of assertion methods of JUnit, and the default reported messages aren't as clear.

The assertion methods used in JUnit are defined as static methods in the junit.framework.Assert class. By importing this class into your own code, you may use the static methods to verify program logic in your production code.

When a JUnit assertion fails, a junit.framework.AssertionFailedError object is thrown. As this is an unchecked exception, it can be left to bubble up to the top of the code and halt execution. Alternately, these can be caught at a point where a sensible recovery can be made.

One drawback of using JUnit assertions is that the checks will always be executed. In comparison, assertions expressed using the 'assert' keyword can be turned on and off using a command-line flag.

Edit | Attach | Print version | History: r10 < r9 < r8 < r7 < r6 | Backlinks | Raw View | WYSIWYG | More topic actions
Topic revision: r10 - 2003-11-12 - 08:45:46 - TonyLinde
 
AstroGrid Service Click here for the
AstroGrid Service Web
This is the AstroGrid
Development Wiki

This site is powered by the TWiki collaboration platformCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback