JUnit.
Nothing beats a bit of code for clarification. I'll use an example that I can use to illustrate different test cases throughout the article: a calculator. The sample calculator is very simple, inefficient, and even has a few bugs; it only manipulates integers and stores the result in a static variable. Substract method does not return a valid result, multiply is not implemented yet, and it looks like there is a bug on the squareRoot method: It loops infinitely. These bugs will help illustrate the efficiency of the tests in JUnit 4. You can switch this calculator on and off and you can clear the result. Here is the code:
package calc; public class Calculator { private static int result; // Static variable where the result is stored public void add(int n) { result = result + n; } public void substract(int n) { result = result - 1; //Bug : should be result = result - n } public void multiply(int n) {} //Not implemented yet public void divide(int n) { result = result / n; } public void square(int n) { result = n * n; } public void squareRoot(int n) { for (; ;) ; //Bug : loops indefinitely } public void clear() { // Cleans the result result = 0; } public void switchOn() { // Swith on the screen, display "hello", beep result = 0; // and do other things that calculator do nowadays } public void switchOff() { } // Display "bye bye", beep, switch off the screen public int getResult() { return result; } }
Migrating a Test Class
Now I'll take a simple test class already written in JUnit 3.8 and migrate it to JUnit 4. This class has some flaws: It does not test all the business methods and it looks like there is a bug in the testDivide method (8/2 is not equal to 5). Because the implementation of multiply is not ready, its test is written but ignored.
The differences between the two frameworks are highlighted in bold (see Table 1).
Table 1. CaculatorTest in JUnit 3.8 and JUnit 4).
TestDivide produces an error because assert ensures that 8/2 does not equal 5. TestSubstract produces a failure because 10-2 should be equal to 8 but there is a bug in the implementation and it returns 9.TestDivide produces an error because assert ensures that 8/2 does not equal 5. TestSubstract produces a failure because 10-2 should be equal to 8 but there is a bug in the implementation and it returns 9.
Advanced Tests
Now I'll demonstrate some advanced features of JUnit 4. Listing 1 is a new test class, AdvancedTest, that extends AbstractParent.
Advanced:
As you can see @BeforeClass and @AfterClass are only called once, meanwhile @Before and @After are called for each test.As you can see @BeforeClass and @AfterClass are only called once, meanwhile @Before and @After are called for each test.:
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.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.:
package junit4; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ CalculatorTest.class, SquareTest.class }) public class AllCalculatorTests { }
RunnerRunner
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.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.
import org.junit.internal.runners.TestClassRunner; import org.junit.runner.RunWith; @RunWith(TestClassRunner.class) public class CalculatorTest { ... }
Tools Integration (or Lack Thereof)
As I'm writing this article, JUnit 4 integration in IDEs is not yet perfect. In fact if you try to run the test classes we’ve just seen, they will not work in any IDE because they are not recognized as being test classes. For forward compatibility JUnit 4 comes with an adapter (junit.framework.JUnit4TestAdapter) that you have to use in a suite() method. Here is the code you have to add in every class to make them understandable by IDEs, Ant, and JUnit 3.8 text runner:
Intellij IDEAIntellij IDEA
public static junit.framework.Test suite() { return new JUnit4TestAdapter(CalculatorTest.class); }
As you can see, the parameterized test case (SquareTest) is represented as seven individual tests.
Ant Integration
The junit task currently only supports JUnit 3.8 style tests, meaning you also have to wrap your JUnit 4 tests in a JUnit4TestAdapter in order to run them in Ant. The <junit> task is used in exactly the same way as in JUnit 3.8:
JUnit: Losing Market or Coming Back Strong?JUnit: Losing Market or Coming Back Strong?
<!-- Test --> <target name="test" depends="compile"> <junit fork="yes" haltonfailure="yes"> <test name=" junit4.AllCalculatorTests"/> <formatter type="plain" usefile="false"/> <classpath refid="classpath"/> </junit> </target>
With this new version, JUnit is back on track. It has new APIs and now uses annotations, making it easier to develop test cases. In fact, the JUnit developers have started to think of new, future annotations. For example, you could add a @Prerequisite annotation for a test case that depends on prerequisites (e.g. you need to be online to execute this test); or add a @Repeat annotation that would specify the number of repetitions along with a timeout (e.g. repeat a test five times to make sure there is a real timeout problem); or even add a platform parameter to the @Ignore annotation (e.g. @Ignore(platform=macos), which would ignore a test only if you run on a MacOS platform). As you can see, JUnit is still alive with a promising future.