Original posted Address: http://www.ibm.com/developerworks/cn/java/j-junit4.html
October 13, 2005 JUnit is Java? The language de facto standard unit test library. JUnit 4 is the most landmark release of the library in three years. Its new feature simplifies testing by using tags in Java 5 (annotation) instead of using subclasses, reflection, or naming mechanisms to identify tests. In this article, the persistent Code tester, Elliotte Harold, takes JUnit 4 as an example detailing how to use this new framework in your own work. Note that this article assumes that the reader has experience with JUnit.
JUnit, developed by Kent Beck and Erich Gamma, is almost certainly the most important Third-party Java library that has been developed to date. As Martin Fowler said, "In the field of software development, there has never been so little code that has played such an important role." JUnit guides and facilitates the prevalence of testing. Because Junit,java code becomes more robust and more reliable, bugs are less than ever. JUnit (which is inspired by Smalltalk's Sunit) derives many xUnit tools that apply the advantages of unit testing to various languages. NUnit (. NET), Pyunit (Python), CppUnit (c + +), Dunit (Delphi), and other tools affect the testing of programmers on a variety of platforms and languages.
However, JUnit is just a tool. The real advantage comes from the ideas and techniques that JUnit uses, not the framework itself. Unit testing, test-first programming, and test-driven development are not all implemented in JUnit, and any programming that compares GUI must be done with Swing. The last update of JUnit itself was almost three years ago. Although it has proven to be more robust and durable than most frameworks, bugs are found, and more importantly, Java is evolving. The Java language now supports generics, enumerations, variable-length parameter lists, and annotations, which bring new possibilities for reusable framework design.
JUnit's stagnation has not been defeated by programmers who want to scrap it. Challenger includes Bill Venners's Artima Suiterunner and Beust of Cedric TestNG. These libraries have some notable features, but they are not up to JUnit's visibility and market share. None of them has extensive out-of-the-box support in products such as Ant, Maven, or Eclipse. So Beck and Gamma started developing a new version of JUnit that took advantage of the new features of Java 5, especially the annotations, making unit testing simpler than using the original junit. In Beck's words, "the theme of JUnit 4 is to encourage more developers to write more tests by simplifying junit further." "JUnit 4, while maintaining backward compatibility with the existing JUnit 3.8 test suite, is still committed to the most significant improvement in Java unit testing since JUnit 1.0."
Note: the improvement of the framework is quite cutting-edge. Although the big outlines of JUnit 4 are clear, the details can still be changed. This means that this article is a preemptive look at JUnit 4, not its final effect.
Test method
All previous versions of JUnit used naming conventions and reflection to locate tests. For example, the following code test 1+1 equals 2:
Import Junit.framework.TestCase;
public class Additiontest extends TestCase {
private int x = 1;
private int y = 1;
public void Testaddition () {
int z = x + y;
Assertequals (2, z);
}
|
In JUnit 4, tests are identified by @Test annotations, as follows:
Import Org.junit.Test;
Import Junit.framework.TestCase;
public class Additiontest extends TestCase {
private int x = 1;
private int y = 1;
@Test public void Testaddition () {
int z = x + y;
Assertequals (2, z);
}
|
The advantage of using annotations is that you no longer need to name all the methods Testfoo (), Testbar (), and so on. For example, the following methods can also work:
Import Org.junit.Test;
Import Junit.framework.TestCase;
public class Additiontest extends TestCase {
private int x = 1;
private int y = 1;
@Test public void Additiontest () {
int z = x + y;
Assertequals (2, z);
}
|
The following method can also work:
Import Org.junit.Test;
Import Junit.framework.TestCase;
public class Additiontest extends TestCase {
private int x = 1;
private int y = 1;
@Test public void addition () {
int z = x + y;
Assertequals (2, z);
}
|
This allows you to follow the naming convention that best suits your application. For example, some of the examples I've introduced use the convention that the test class uses the same name as the class being tested for its test methods. For example, List.contains () is tested by Listtest.contains (), List.add () by Listtest.addall (), and so on.
The TestCase class can still work, but you no longer need to extend it. As long as you annotate the test method with @Test, you can put the test method into any class. But you need to import junit. Assert class to access various Assert methods, as follows:
Import Org.junit.Assert;
public class Additiontest {
private int x = 1;
private int y = 1;
@Test public void addition () {
int z = x + y;
Assert.assertequals (2, z);
}
|
You can also use the new feature (static import) in JDK 5 to make it as simple as the previous version:
Import static org.junit.Assert.assertEquals;
public class Additiontest {
private int x = 1;
private int y = 1;
@Test public void addition () {
int z = x + y;
Assertequals (2, z);
}
|
This approach makes it easy to test the protected method because the test case class can now extend the class that contains the protected method.
|
Back to the top of the page |
|
SetUp and Teardown
The JUnit 3 test run program, test Runner, automatically invokes the SetUp () method before running each test. This method typically initializes fields, turns on logging, resets environment variables, and so on. For example, here is the SetUp () method from the Xsltransformtest in XOM:
protected void SetUp () {System.seterr (New PrintStream ()) (New
Bytearrayoutputstream ());
InputDir = new File ("data");
InputDir = new File (InputDir, "XSLT");
InputDir = new File (inputdir, "input");
} |
In JUnit 4, you can still initialize fields and configure the environment before each test method runs. However, the method of completing these operations is no longer required to be called a setUp (), as long as it is indicated by a @Before annotation, as follows:
@Before protected void Initialize () {system.seterr (new PrintStream) (New
Bytearrayoutputstream ());
InputDir = new File ("data");
InputDir = new File (InputDir, "XSLT");
InputDir = new File (inputdir, "input");
} |
You can even annotate multiple methods with @Before, which run before each test:
@Before protected void Findtestdatadirectory () {
inputdir = new File ("data");
InputDir = new File (InputDir, "XSLT");
InputDir = new File (inputdir, "input");
}
@Before protected void Redirectstderr () {system.seterr (new PrintStream) (New
Bytearrayoutputstream ());
} |
The Purge method is similar to this. In JUnit 3, you use the teardown () method, which is similar to the method I use in XOM for tests that consume a lot of memory:
protected void teardown () {
doc = null;
System.GC ();
|
For JUnit 4, I can give it a more natural name and annotate it with @After:
@After protected void Disposedocument () {
doc = null;
System.GC ();
|
As with @Before, you can annotate multiple purge methods with @After, which run after each test.
Finally, you no longer need to explicitly invoke initialization and cleanup methods in the superclass, so long as they are not overwritten, the test run program will automatically call these methods for you as needed. @Before methods in a superclass are called before the @Before method in a subclass, which reflects the order in which the constructor calls are made. @After method runs in the opposite direction: A method in a subclass is invoked before a method in a superclass. Otherwise, the relative order of multiple @Before or @After methods is not guaranteed.
the initialization of the suite scope
JUnit 4 also introduces a new feature not in JUnit 3: The Class-wide setUp () and teardown () method. Any method that is @BeforeClass annotated will run exactly once before the test method in the class is run, and any method with @AfterClass annotations will run just once after all the tests in that class have been run.
For example, suppose each test in a class uses a database connection, a network connection, a very large data structure, or other resources that are more expensive to initialize and schedule. Do not re-create it before each test, you can create it once, and restore it once. This method will make some test cases run much faster. For example, when I test error handling in code that invokes a Third-party library, I usually prefer to redirect System.err before the test starts so that the output is not disturbed by the expected error message. Then I restore it at the end of the test, as follows:
This class tests a lot of the error conditions, which
//Xalan annoyingly logs to System.err. This hides System.err
//Before all test and restores it after the each test.
Private PrintStream Systemerr;
@BeforeClass protected void Redirectstderr () {
systemerr = System.err;//Hold on to the original value
System.set ERR (New PrintStream ()) (New Bytearrayoutputstream ());
}
@AfterClass protected void teardown () {
//Restore the original value
System.seterr (systemerr);
} |
There is no need to do this before and after each test. But be sure to take this feature very seriously. It may violate the independence of the test and introduce unexpected confusion. If a test changes the object that the @BeforeClass initializes in a way, it may affect the results of other tests. It is possible to introduce sequential dependencies into the test suite and hide bugs. As with any optimization, this is done only after the profiling and benchmarking tests prove that you have a real problem. That said, I've seen more than one test suite run so long that it doesn't run as often as it needs to, especially if you need to build a lot of network and database connectivity tests. (for example, the LimeWire test suite runs longer than two hours.) To speed up these test suites so that programmers can run them more often, all you can do is reduce bugs.
|
Back to the top of the page |
|
Test exception
Exception testing is the biggest improvement in JUnit 4. The old-style exception test puts a try block in the code that throws the exception, and then adds a fail () statement at the end of the try block. For example, the method tests 0 in addition to throwing a arithmeticexception:
public void Testdivisionbyzero () {
try {
int n = 2/0;
Fail ("divided by zero!");
}
catch (ArithmeticException success) {
assertnotnull (success.getmessage ());
}
} |
This approach is not only unsightly, but also an attempt to challenge the code coverage tool because there are always some code that is not executed, regardless of whether the test passes or fails. In JUnit 4, you can now write code that throws an exception and use a comment to declare that the exception is expected:
@Test (expected=arithmeticexception.class) public
void DivideByZero () {
int n = 2/0;
} |
If the exception is not thrown (or a different exception is thrown), then the test will fail. However, if you want to test the details of an exception or other properties, you still need to use the old Try-catch style.
|
Back to the top of the page |
|
Ignored Tests
Maybe you have a test run for a very long time. This is not to say that the test should run faster, but rather that the work it does is fundamentally more complex or slow. Tests that require access to a remote network server typically fall into this category. If you are not doing something that might interrupt this type of test, you may want to skip the long-running test method to shorten the compile-test-debug cycle. Or maybe it's a test that failed because it was beyond the scope of your control. For example, the Xinclude test suite tests the automatic recognition of some Unicode encodings that are not yet supported by Java. Instead of being forced to stare at those red wavy lines, such tests can be annotated as @Ignore, as follows:
Java doesn ' t yet support
//The UTF-32BE and Utf32le encodings
@Ignore public void Testutf32be ()
throws Par Singexception, IOException, xincludeexception {
file input = new file (
"Data/xinclude/input/utf32be.xml"
);
Document doc = builder.build (input);
Document result = Xincluder.resolve (doc);
Document Expectedresult = Builder.build (
new File (OutputDir, "Utf32be.xml")
);
Assertequals (Expectedresult, result);
} |
The test run program will not run these tests, but it will indicate that the tests have been skipped. For example, when a text interface is used, an "I" (representing ignore) is output, not the time that is experienced for passing the test output, nor is the failed test output "E":
$ java-classpath.: Junit.jar org.junit.runner.JUnitCore
nu.xom.tests.XIncludeTest
junit version 4.0rc1
..... I...
time:1.149
OK (7 tests) |
But be careful. There may be some reason for the initial writing of these tests. If these tests are ignored forever, they expect the code to be tested to be interrupted, and such interrupts may not be detected. Ignoring tests is just a stopgap, not a real solution to any problem.
|
Back to the top of the page |
|
Time Test
Test performance is one of the most painful aspects of unit testing. JUnit 4 does not completely solve the problem, but it helps with this problem. The test can be annotated with a time-out parameter. If the test runs longer than the specified number of milliseconds, the test fails. For example, if the test takes more than half a second to find all the elements in a previously set document, the test fails:
@Test (timeout=500) public void Retrieveallelementsindocument () {
doc.query ("//*");
|
In addition to simple benchmark tests, time testing is also useful for network testing. When a remote host or database that a test attempts to connect to is down or slows down, you can ignore the test so that you do not block all other tests. Good test suites execute fast enough that programmers can run these tests after a significant change in each test, possibly running dozens of times a day. Setting a time-out makes this more feasible. For example, if the parsing http://www.ibiblio.org/xml takes more than 2 seconds, the following test will timeout:
@Test (timeout=2000) public
void Remotebaserelativeresolutionwithdirectory ()
throws IOException, parsingexception {
builder.build ("Http://www.ibiblio.org/xml");
|
|
Back to the top of the page |
|
The new assertion
JUnit 4 adds two assert () methods for the comparison array:
public static void Assertequals (object[] expected, object[] actual) public
static void Assertequals (String message, O Bject[] expected,
object[] Actual)
|
The two methods compare arrays in the most direct way: If the array is of the same length and each corresponding element is the same, two arrays are equal, otherwise unequal. The case of an array empty is also considered.
|
Back to the top of the page |
|
Need to add to the place
JUnit 4 is basically a new framework, not an upgraded version of the old framework. JUnit 3 Developers may find features that are not previously available.
The most obvious omission is the GUI test run program. If you want to see a pleasing green wavy line when the test passes, or if you see an anxious red wavy line when the test fails, you need an IDE with integrated JUnit support, such as Eclipse. Either Swing or AWT test run programs are not upgraded or bundled into JUnit 4.
The next surprise is that there is no longer any difference between failure (the expected error that the Assert method detects) and the error (unexpected error that the exception indicates). Although the JUnit 3 test run program can still differentiate between these cases, the JUnit 4 run programs will no longer be able to differentiate.
Finally, JUnit 4 does not have a suite () method, which is used to build a test suite from multiple test classes. In contrast, a variable-length parameter list is used to allow an indeterminate number of tests to be passed to the test run program.
I'm not too happy to eliminate the GUI test run program, but other changes seem likely to add to the simplicity of JUnit. Just consider how many documents and FAQs are currently specifically designed to explain these points, and then consider for JUnit 4 that you no longer need to explain these points.
|
Back to the top of the page |
|
Compiling and running JUnit 4
Currently, there is no library version of JUnit 4. If you want to experience a new version, you need to get it from the CVS repository on SourceForge. The Branch (branch) is "Version4" (see Resources). Note that a lot of documents are not upgraded and still refer to doing things in the old-fashioned 3.x way. Java 5 is necessary for compiling JUnit 4 because JUnit 4 uses a large number of annotations, generics, and other features of the Java 5 language level.
Since JUnit 3, the syntax for running tests from the command line has changed a little. You are now using the Org.junit.runner.JUnitCore class:
$ java-classpath.: Junit.jar org.junit.runner.JUnitCore
testa testb testc ...
JUnit version 4.0rc1
time:0.003
OK (0 tests) |
compatibility
Beck and Gamma strive to maintain forward and backward compatibility. The JUnit 4 test run program can run JUnit 3 tests without making any changes. Simply pass the fully qualified class name of each test you want to run to the test run, just as you would for a JUnit 4 test. Running a program is smart enough to tell which test class depends on which version of JUnit and call it appropriately.
Backwards compatibility is a bit more difficult, but you can also run JUnit 4 tests in the JUnit 3 test run program. This is important, so tools with integrated junit support, such as Eclipse, can handle JUnit 4 without the need for updates. To enable JUnit 4 tests to run in JUnit 3 environments, you can wrap them in junit4testadapter. Adding the following method to your JUnit 4 test class should suffice:
public static Junit.framework.Test Suite () {return
new Junit4testadapter (Assertiontest.class);
} |
But because Java is more volatile, JUnit 41 points are not backwards compatible. JUnit 4 is completely dependent on the Java 5 feature. It will not compile or run for Java version 1.4 or earlier.
|
Back to the top of the page |
|
Prospect
JUnit 4 is far from over. Many important aspects are not mentioned, including most of the documentation. I do not recommend converting your test suite to annotations and JUnit 4 now. Even so, development is still fast, and the JUnit 4 Outlook is promising. Although Java 2 programmers still need to use JUnit 3.8 for the foreseeable future, programmers who have moved to Java 5 should quickly consider making their test suites fit for the new framework for matching.
Resources
Learn
You can refer to the English version of this article at the DeveloperWorks Global site.
Pragmatic units testing in Java with JUnit (Andy Hunt and Dave thomas,pragmatic programmers,2003 years): A good introduction to unit testing.
JUnit recipes:practical Methods for programmer testing (J. B. rainsberger,manning,2004 year): One of the most widely cited and referenced JUnit books. The
Testng:cedric beust Framework, based on the test style now used in JUnit 4, uses the annotation first.
"TestNG makes Java unit testing Easy" (Filippo diotalevi,developerworks,2005 January): Introduced TestNG.
Incremental development with ANT and JUnit (Malcolm davis,developerworks,2000 year November): explores the advantages of unit testing, using Ant and JUnit, and sample code.