Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Get Acquainted with the New Advanced Features of JUnit 4 : Page 3

Learn how to migrate from JUnit 3.8 to JUnit 4. Discover version 4's new features, including extensive use of annotations, and find out the status on IDE integration.

Advanced Tests
Now I'll demonstrate some advanced features of JUnit 4. Listing 1 is a new test class, AdvancedTest, that extends AbstractParent.

Advanced Fixture
Both classes use the new annotations @BeforeClass and @AfterClass as well as @Before and @After. The main differences between these annotations are shown in Table 2.

Table 2. @BeforeClass/@AfterClass vs. @Before/@After

@BeforeClass and @AfterClass @Before and @After
Only one method per class can be annotated. Multiple methods can be annotated. Order of execution is unspecified. Overridden methods are not run.
Method names are irrelevant Method names are irrelevant
Runs once per class Runs before/after each test method
@BeforeClass methods of superclasses will be run before those of the current class. @AfterClass methods declared in superclasses will be run after those of the current class. @Before in superclasses are run before those in subclasses. @After in superclasses are run after those in subclasses.
Must be public and static. Must be public and non static.
All @AfterClass methods are guaranteed to run even if a @BeforeClass method throws an exception. All @After methods are guaranteed to run even if a @Before or @Test method throws an exception.

@BeforeClass and @AfterClass can be very useful if you need to allocate and release expensive resources only once. In our example the AbstractParent starts and stops the entire test system using these annotations on startTestSystem() and stopTestSystem() methods. And it initializes and cleans the system using @Before and @After. The child class AdvancedTest also uses a mixture of these annotations.

It is not good practice to have System.out.println in your test code, but in this case it helps to understand the order these annotations are called. When I run AdvancedTest I get:

Start test system //@BeforeClass of parent Switch on calculator //@BeforeClass of child Initialize test system //First test Clear calculator Initialize test system //Second test Clear calculator Clean test system Initialize test system //Third test Clear calculator Clean test system Initialize test system //Forth test Clear calculator Clean test system Switch off calculator //@AfterClass of child Stop test system //@AfterClass of parent

As you can see @BeforeClass and @AfterClass are only called once, meanwhile @Before and @After are called for each test.

Timeout Tests
In the previous example I wrote a test case for the squareRoot() method. Remember that there is a bug in this method which causes it to loop indefinitely. I want this test to exit after 1 second if there is no result. That's what the timeout parameter does. This second optional parameter of the @Test annotation (the first one was expected), causes a test to fail if it takes longer than a specified amount of clock time (milliseconds). When I run the test I get:

There was 1 failure: 1) squareRoot(junit4.AdvancedTest) java.lang.Exception: test timed out after 1000 milliseconds at org.junit.internal.runners.TestMethodRunner.runWithTimeout(TestMethodRunner.java:68) at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:43) FAILURES!!! Tests run: 4, Failures: 1

Parameterized Tests
In Listing 1 I tested the squareRoot <<it is the square method not the squareRoot>> method by creating several test methods (square2, square4, square5), which do exactly the same thing, parameterized by some variables. This copy/paste technique can now be optimized using a parameterized test case (see Listing 2).

The test case in Listing 2 uses two new annotations. When a class is annotated with @RunWith, JUnit will invoke the class referenced to run the tests instead of the default runner. To use a parameterized test case, you need to use the runner org.junit.runners.Parameterized. To know which parameters to use, the test case needs a public static method (here data() but the name is irrelevant) that returns a Collection and is annotated with @Parameters. You also need a public constructor that takes these parameters.

When running this class, the output is:

java org.junit.runner.JUnitCore junit4.SquareTest JUnit version 4.1 .......E There was 1 failure: 1) square[6](junit4.SquareTest) java.lang.AssertionError: expected:<48> but was:<49> at org.junit.Assert.fail(Assert.java:69) FAILURES!!! Tests run: 7, Failures: 1

There are seven tests executed (the seven dots '.'), as if seven individual square methods were written. Note that we have a failure in our test because the square of 7 is 49, not 48.

To run several test classes into a suite in JUnit 3.8 you had to add a suite() method to your classes. With JUnit 4 you use annotations instead. To run the CalculatorTest and SquareTest you write an empty class with @RunWith and @Suite annotations.

package junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ CalculatorTest.class, SquareTest.class }) public class AllCalculatorTests { }

Again, the @RunWith annotation is telling JUnit to use the org.junit.runner.Suite. This runner allows you to manually build a suite containing tests from many classes. The names of these classes are defined in the @Suite.SuiteClass. When you run this class, it will run CalculatorTest and SquareTest. The output is:

java -ea org.junit.runner.JUnitCore junit4.AllCalculatorTests JUnit version 4.1 ...E.EI.......E There were 3 failures: 1) subtract(junit4.CalculatorTest) java.lang.AssertionError: expected:<9> but was:<8> at org.junit.Assert.fail(Assert.java:69) 2) divide(junit4.CalculatorTest) java.lang.AssertionError at junit4.CalculatorTest.divide(CalculatorTest.java:40) 3) square[6](junit4.SquareTest) java.lang.AssertionError: expected:<48> but was:<49> at org.junit.Assert.fail(Assert.java:69) FAILURES!!! Tests run: 11, Failures: 3

It may not be obvious but JUnit 4 uses runners extensively. If @RunWith is not specified, your class will still be executed with a default runner (org.junit.internal.runners.TestClassRunner). The original Calculator class doesn't explicitly declare a runner, so therefore it uses the default. A class containing a method with @Test has a @RunWith by implication. In fact, you could add the following code to the Calculator class and the output would be exactly the same.

import org.junit.internal.runners.TestClassRunner; import org.junit.runner.RunWith; @RunWith(TestClassRunner.class) public class CalculatorTest { ... }

In the case of the @Parameterized and @Suite I needed a special runner to execute my test cases. That's why I explicitly annotated them.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.