Monday, 27 June 2011

Migrating tests from junit 3 to junit 4

For some legacy reason, the project that I am working on has been using junit 3 for all the unit and integration tests since day 1. Last week, I decided to do something different that could cheer up my mind and a step forward to more flexibilities in testing.

It could be an relative easy task, but it turned out to cost me a whole day to complete the migration of 700+ test classes.

1) Based on the suggestions on http://stackoverflow.com/questions/264680/best-way-to-automagically-migrate-tests-from-junit-3-to-junit-4 , I performed the initial easy-task by the following regular expressions:


// Add @Test
Replace:
^[ \t] +((publicpublic abstract) +void +test)
With:
@Test\n $1
Regular Expression: on
Case sensitive: on
File name filter: *Test.java, *TestCase.java

// Remove double @Test's on already @Test annotated files
Replace:
^[ \t]+@Test\n[ \t]+@Test
With:
@Test
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java


// Remove all empty setUp's
Replace:
^[ \*]+((publicprotected) +)?void +setUp\(\)[^\{]*\{\s*(super\.setUp\(\);)?\s*\}\n([ \t]*\n)?
With: nothing
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java

// Add @Before to all setUp's
Replace:
^([ \t]+@Override\n)?[ \t]+((publicprotected) +)?(void +setUp\(\))
With:
@Before\n public void setUp()
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java

// Remove double @Before's on already @Before annotated files
Replace:
^[ \t]+@Before\n[ \t]+@Before
With:
@Before
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java


// Remove all empty tearDown's
Replace:
^[ \*]+((publicprotected) +)?void +tearDown\(\)[^\{]*\{\s*(super\.tearDown\(\);)?\s*\}\n([ \t]*\n)?
With: nothing
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java

// Add @After to all tearDown's
Replace:
^([ \t]+@Override\n)?[ \t]+((publicprotected) +)?(void +tearDown\(\))
With:
@After\n public void tearDown()
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java

// Remove double @After's on already @After annotated files
Replace:
^[ \t]+@After\n[ \t]+@After
With:
@After
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java


// Remove old imports, add new imports
Replace:
^([ \t]*import[ \t]+junit\.framework\.Assert;\n)?[ \t]*import[ \t]+junit\.framework\.TestCase;
With:
import org.junit.After;\nimport org.junit.Before;\nimport org.junit.Test;\nimport static org.junit.Assert.*;
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java


// Remove all extends TestCase
Replace:
[ \t]+extends[ \t]+(TestCase)[ \t]+\{
With:
{
Regular Expression: on
Case sensitive: on
File name filter:
*Test.java, *TestCase.java



// Look for import junit.framework;
Find:
import junit\.framework
Manually fix
Regular Expression: on
Case sensitive: on


// Look for ignored tests (FIXME, disabled, ...)
Find:
public[ \t]+void[ \t]+\w+test
Manually fix
Regular Expression: on
Case sensitive: on


// Look for dummy/empty tests
Find:
public[ \t]+void[ \t]+test[\w\d]*\(\s*\)\s*\{\s*(//[^\n]*)?\s*\}
Manually fix
Regular Expression: on
Case sensitive: on
2) The project might not compile without an error after step 1, mostly because we still need to do the static import of various assertion methods (e.g. assertEquals, assertTrue, etc.)

3) Since we use MockObjectTestCase from JMock with jUnit 3, we need to have a class level annotation of "@RunWith(JMock.class)", remove the "extends MockObjectTestCase", and manually define

private final Mockery context = new Mockery();
...
xyz = context.mock(XYZ.class);
context.checking(new Expectations(){{}});
4) We then have to replace the test suite with junit 4 declaration styles:

@RunWith(SomeDirectoryScanningSuite.class)
public class SomeTestSuite {
}
The "SomeDirectoryScanningSuite" has to extend "org.junit.runners.Suite", for example

public class SomeDirectoryScanningSuite extends Suite {

public SomeDirectoryScanningSuite(Class testSuite) throws InitializationError, ClassNotFoundException {
super(testSuite, new Class[]{SomeTest.class, AnotherTest.class});
}
}
There are many different ways of specifying what test classes should be included in the above example.

The example below is a test suite of a bunch of test suites/classes:

@RunWith(Suite.class)
@Suite.SuiteClasses({
SomeTest.class, // junit 4 annotated test class
SomeTestSuite.class // junit 4 test suite
})
public class TestsSuite {}

No comments:

Post a Comment