LinkedIn Facebook Twitter bemoko blog bemoko technical pages
Tel: +44 1256 581028 Email: info@bemoko.com

Bemoko blog

20

Mar

Quick step-by-step guide : Upgrading Junit 3.x to Junit 4.x

Posted By Ian Homer Friday, March 20, 2009. 2 Comments

Well we finally got around to upgrade all our unit tests to new features. About time really!

We use maven and the maven-surefire-plugin for running our unit tests during our build process. Let’s look at the steps I did to get migrate.

  • All our test cases extend from a single abstract test case class called AbstractLiveTestCase that in our Junit 3 integration extended from junit.framework.TestCase. For Junit 4, tests are described with annotations so we don’t need to extend this class anymore – so I (1) removed the “extends TestCase” from our AbstractLiveTestCase
  • The Junit 3 TestClass class used to provide all the handy assert functions. These are now provided as org.junit.Assert static methods so I went through each of our tests and (2) added “import static org.junit.Assert.*;” to the top of each class. With the handing auto source code formatting in Eclipse enabled, Eclipse automatically expanded the “org.junit.Assert.*” imports to the explicit ones needed for that class when I saved it.
  • And finally (3) Added @Test to each test method so that Junit would know what tests to run.

Basically that was it – surefire/maven/junit picked up the test cases in the new style – and in most cases that’s job done. However we wanted to go a little further.

We had also implemented in Junit 3 a handy feature that outputted a quick ERROR message in the system out of a test run so we could quickly see the failure. By default maven’s surefire simply tells you there was a failure, but you have to look at a file to see what it was. Bit of pain that, so in our Junit 3 extension we would provide more info on the screen when a failure occurred, for example we might see something like:

Running com.bemoko.live.platform.mwc.sites.LiveSiteTestCase
ERROR : *** Test Failure *** Site home not correct expected: but was: @ line 17 of com.bemoko.live.platform.mwc.sites.LiveSiteTestCase (com.bemoko.live.platform.BemokoTestListener)
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.01 sec <<< FAILURE!

In Junit 3 we did this by overriding the runTest method of the TestCase class. In Junit 4 we can use runners and listeners. To start with we set the @RunWith annotation on our AbstractLiveTestCase to indicate that all of our tests should run with a specified runner …

@RunWith(BemokoTestRunner.class)
public abstract class AbstractLiveTestCase {

The runner was defined to register a listener …

1
2
3
4
5
6
7
8
9
10
public class BemokoTestRunner extends BlockJUnit4ClassRunner {
	public BemokoTestRunner(Class<?> clazz) throws InitializationError {
		super(clazz);
	}
	@Override
	public void run(final RunNotifier notifier) {
		notifier.addListener(new BemokoTestListener());
		super.run(notifier);
	}
}

and the listener was defined to listen for failures …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class BemokoTestListener extends RunListener {
	private static Log log = LogFactory.getLog(BemokoTestListener.class);
	@Override
	public void testFailure(Failure failure) {
		Throwable t = failure.getException();
		if (t instanceof Exception) {
			log.error("*** Test Failure *** " + t.getMessage(), t);
		} else {
			if (t.getMessage() != null) {
				log.error("*** Test Failure *** " + t.getMessage() + " @ "
						+ getErrorLocation(t.getStackTrace()));
			} else {
				log.error("*** Test Failure *** ", t);
			}
		}
	}
	@Override
	public void testIgnored(Description description) {
		if (log.isWarnEnabled()) {
			log.warn("+++ Test ignored +++ " + description.getDisplayName());
		}
	}
	private String getErrorLocation(StackTraceElement[] st) {
		for (StackTraceElement ste : st) {
			if (ste.getClassName().startsWith("com.bemoko")
			    && !ste.getClassName().contains("AbstractLiveTestCase")) {
			  return "line " + ste.getLineNumber() + " of " + ste.getClassName();
			}
		}
		return "(line number and class not known)";
	}
}

in this way I can handle events from my tests in any way I see fit and much more elegant that the Junit 3 implementation I previously had.

Job done and we’re all sorted for our unit test framework for the foreseeable future.

Share and Enjoy:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks

Posted in: java

Comments

  • Craig Gallen

    Nov 7th, 2009

    Hi,
    Really useful little technique for logging test results. Thanks

  • Tom Castle

    Jan 5th, 2011

    Thanks! This was useful for automating my upgrade to JUnit 4. A couple of extra upgrades most will require is annotating any setUp methods with @Before, and tearDown methods with @After. Both of which previously overrided methods in TestCase.

Leave a Comment

Name (required)

Email (will not be published) (required)

Comment