1. Why do I get an error (ClassCastException or LinkageError) using the GUI TestRunners?
(Submitted by: Scott Stirling)
JUnits GUI TestRunners use a custom class loader (junit.runner.TestCaseClassLoader) to dynamically reload your code every time you press the "Run" button so you don't have to restart the GUI to reload your classes if you recompile them. The default classloaders of the Java Virtual Machine do not dynamically reload changed classes. But JUnits custom class loader finds and loads classes from the same CLASSPATH used by the JVMs system classloader. So, by design, it "sits in front of" the system loader and applies a filter to determine whether it should load a given class or delegate the loading of a class to the system classloader. This filter is configured with a list of String patterns in a properties file called excluded.properties.
The excluded.properties file contains a numbered list (excluded.0, excluded.1, excluded.2, etc.) of properties whose values are patterns for packages. This file is packaged in junit.jar as junit/runner/excluded.properties. As of JUnit 3.7 and Java 1.4, its contents are:
#
# The list of excluded package paths for the TestCaseClassLoader
#
excluded.0=sun.*
excluded.1=com.sun.*
excluded.2=org.omg.*
excluded.3=javax.*
excluded.4=sunw.*
excluded.5=java.*
There are some conditions, discussed below, where the default exclusions are insufficient and you will want to add some more to this list and then either update the junit.jar file with your customized version or place your customized version in the CLASSPATH before junit.jar.
2. Why do I get a LinkageError when using XML interfaces in my TestCase?
(Submitted by: Scott Stirling)
The workaround as of JUnit 3.7 is to add org.w3c.dom.* and org.xml.sax.* to your excluded.properties.
It抯 just a matter of time before this fix becomes incorporated into the released version of JUnit's excluded.properties, since JAXP is a standard part of JDK 1.4. It will be just like excluding org.omg.*. By the way, if you download the JUnit source from its Sourceforge CVS, you will find that these patterns have already been added to the default excluded.properties and so has a pattern for JINI. In fact, here is the current version in CVS, which demonstrates how to add exclusions to the list too:
#
# The list of excluded package paths for the TestCaseClassLoader
#
excluded.0=sun.*
excluded.1=com.sun.*
excluded.2=org.omg.*
excluded.3=javax.*
excluded.4=sunw.*
excluded.5=java.*
excluded.6=org.w3c.dom.*
excluded.7=org.xml.sax.*
excluded.8=net.jini.*
This is the most common case where the default excluded.properties list needs modification. The cause of the LinkageError is related to using JAXP in your test cases. By JAXP I mean the whole set of javax.xml.* classes and the supporting org.w3c.dom.* and org.xml.sax.* classes.
As stated above, the JUnit GUI TestRunners' classloader relies on the excluded.properties for classes it should delegate to the system classloader. JAXP is an unusual case because it is a standard Java extension library dependent on classes whose package names (org.w3c.dom.* and org.xml.sax.*) do not begin with a standard Java or Sun prefix. This is similar to the relationship between javax.rmi.* and the org.omg.* classes, which have been excluded by default in JUnits excluded.properties for a while.
What can happen, and frequently does when using the JUnit Swing or AWT UI with test cases that reference, use or depend on JAXP classes, such as Log4J, Apache SOAP, Axis, Cocoon, etc., is that the JUnit class loader (properly) delegates javax.xml.* classes it "sees" to the system loader. But then the system loader, in the process of initializing and loading that JAXP class, links and loads up a bunch of org.w3c.dom/org.xml.sax classes. When it does so, the JUnit custom classloader is not involved at all because the system classloader never delegates "down" or checks with custom classloaders to see if a class is already loaded. At any point after this, if the JUnit loader is asked to load an org.w3c.dom/org.xml.sax class that it's never seen before, it will try to load it because the classs name doesn't match any of the patterns in the default exclude list. Thats when a LinkageError occurs. This is really a flaw in the JUnit classloader design, but there is the workaround given above.
Java 2 JVMs keep classes (remember, classes and objects, though related, are different entities to the JVM - Im talking about classes here, not object instances) in namespaces, identifying them by their fully qualified classname plus the instance of their defining (not initiating) loader. The JVM will attempt to assign all unloaded classes referenced by an already defined and loaded class to that class's defining loader. The JVM's classresolver routine (implemented as a C function in the JVM source code) keeps track of all these class loading events and "sees" if another classloader (such as the JUnit custom loader) attempts to define a class that has already been defined by the system loader. According to the rules of Java 2 loader constraints, in case a class has already been defined by the system loader, any attempts to load a class should first be delegated to the system loader. A "proper" way for JUnit to handle this feature would be to load classes from a repository other than the CLASSPATH that the system classloader knows nothing about. And then the JUnit custom classloader could follow the standard Java 2 delegation model, which is to always delegate class loading to the system loader, and only attempt to load if that fails. Since they both load from the CLASSPATH in the current model, if the JUnit loader delegated like it's supposed to, it would never get to load any classes since the system loader would always find them.
You could try to hack around this in the JUnit source by catching the LinkageError in TestCaseClassLoader's loadClass() method and then making a recovery call to findSystemClass() -- thereby delegating to the system loader after the violation has been caught. But this hack only works some of the time, because now you can have the reverse problem where the JUnit loader will load a host of org.w3c.dom/org.xml.sax classes, and then the system loader violates the loader contraints at some point when it tries to do exactly what I described above with JAXP because it doesn't ever delegate to its logical child (the JUnit loader). Inevitably, if your test cases use many JAXP and related XML classes, one or the other classloader will end up violating the constraints whatever you do.
3. Why do I get a ClassCastException when I use narrow() in an EJB client TestCase?
(Submitted by: Scott Stirling)
The solution is to prevent your EJB's interface classes from being loaded by the JUnit custom class loader by adding them to excluded.properties.
This is another problem inherent to JUnit's dynamically reloading TestCaseClassLoader. Similar to the LinkageErrors with JAXP and the org.xml.sax and org.w3c.dom classes, but with a different result.
Here's some example code:
Point point;
PointHome pointHome;
// The next line works in textui, but throws
// ClassCastException in swingui
pointHome = (PointHome)PortableRemoteObject.
narrow(ctx.lookup("base/PointHome"), PointHome.class);
When you call InitialContext.lookup(), it returns an object that was loaded and defined by the JVM's system classloader (sun.misc.Launcher$AppClassLoader), but the PointEJBHome.class type is loaded by JUnit's TestCaseClassLoader. In the narrow(), the two fully qualified class names are the same, but the defining classloaders for the two are different so you get the exception during the narrow because the JVM doesn't see them as being the same runtime class type.
Recall that in Java 2 an object's class (a.k.a. "runtime type") is identified in the JVM as the pair of <fully-qualified-classname;definingClassLoaderInstance> or (in shorter form) <C;L>. That is, the defining loaders identity is part of the runtime name identifying that class in the JVM. Also recall that the JVM will expect a class's defining loader to load all unloaded classes referenced by the classes it loads.
If interested for debugging purposes, you can find out more about which loader loaded which class by doing something like this:
System.out.println(ctx.lookup("base/PointEJBHome").getClass().getClassLoader());
System.out.println(PointEJBHome.class.getClassLoader());
You'll find when using the GUI TestRunners that the PointEJBHome type is defined by the JUnit TestCaseClassLoader and the object returned from InitialContext.lookup() was defined through the JVM's system class loader. When using the tex-based TestRunner they'll both have been loaded through the system loader.
If you use Ant's <batchtest> task to run your test cases and you have this problem, you can work around it by setting fork="true" on <batchtest>, which causes it to run each test in its own Java Virtual Machine separate from Ants launching JVM.
For further reading about the principles of Java dynamic classloading, the best resource is the short paper by Sheng Liang, the architect of the Java 2 classloader architecture: Dynamic Class Loading in the Java Virtual Machine, OOPSLA 1998 (http://java.sun.com/people/sl/).
4. Why do I get the warning "AssertionFailedError: No tests found in XXX" when I run my test?
Make sure the test contains one or more methods with names beginning with "test".
For example:
public void testSomething() {
}
5. Why do I see "Unknown Source" in the stack trace of a test failure, rather than the source file's line number?
The debug option for the Java compiler must be enabled in order to see source file and line number information in a stack trace.
When invoking the Java compiler from the command line, use the -g option to generate all debugging info.
When invoking the Java compiler from an Ant task, use the debug="on" attribute. For example:
<javac srcdir="${src}" destdir="${build}" debug="on" />
When using older JVMs pre-Hotspot (JDK 1.1 and most/all 1.2), run JUnit with the -DJAVA_COMPILER=none JMV command line argument to prevent runtime JIT compilation from obscuring line number info.
Compiling the test source with debug enabled will show the line where the assertion failed. Compiling the non-test source with debug enabled will show the line where an exception was raised in the class under test.
6. Why do I get a NoClassDefFoundError when trying to test JUnit or run the samples?
(Submitted by: J.B. Rainsberger and Jason Rogers)
Most likely your CLASSPATH doesn't include the JUnit installation directory.
Consider running WhichJunit (http://www.clarkware.com/software/WhichJUnit.zip)
to print the absolute location of the JUnit class files required to run and i
test JUnit and its samples.
If the CLASSPATH seems mysterious, read
http://java.sun.com/j2se/1.4/docs/tooldocs/findingclasses.html
7. Why does the "excluded.properties" trick not work when running JUnit's GUI from inside my favorite IDE?
(Submitted by: William Pietri)
Some IDEs come with a copy of JUnit, so your copy of JUnit in the
project classpath isn't the one being used. Replace the junit.jar
file used by the IDE with a junit.jar file containing a
custom excluded.properties and your bar will once again be green.
Best Practices:
1. When should tests be written?
Tests should be written before the code. Test-first programming is practiced by only writing new code when an automated test is failing.
Good tests tell you how to best design the system for its intended use. They effectively communicate in an executable format how to use the software. They also prevent tendencies to over-build the system based on speculation. When all the tests pass, you know you're done!
Whenever a customer test fails or a bug is reported, first write the necessary unit test(s) to expose the bug(s), then fix them. This makes it almost impossible for that particular bug to resurface later.
Test-driven development is a lot more fun than writing tests after the code seems to be working. Give it a try!
2. Do I have to write a test for everything?
No, just test everything that could reasonably break.
Be practical and maximize your testing investment. Remember that investments in testing are equal investments in design. If defects aren't being reported and your design responds well to change, then you're probably testing enough. If you're spending a lot of time fixing defects and your design is difficult to grow, you should write more tests.
If something is difficult to test, it's usually an opportunity for a design improvement. Look to improve the design so that it's easier to test, and by doing so a better design will usually emerge.
3. How simple is 'too simple to break'?
(Submitted by: J. B. Rainsberger)
The general philosophy is this: if it can't break on its own, it's too simple to break.
First example is the getX() method. Suppose the getX() method only answers the value of an instance variable. In that case, getX() cannot break unless either the compiler or the interpreter is also broken. For that reason, don't test getX(); there is no benefit. The same is true of the setX() method, although if your setX() method does any parameter validation or has any side effects, you likely need to test it.
Next example: suppose you have written a method that does nothing but forward parameters into a method called on another object. That method is too simple to break.
public void myMethod(final int a, final String b) {
myCollaborator.anotherMethod(a, b);
}
myMethod cannot possibly break because it does nothing: it forwards its input to another object and that's all.
The only precondition for this method is "myCollaborator != null", but that is generally the responsibility of the constructor, and not of myMethod. If you are concerned, add a test to verify that myCollaborator is always set to something non-null by every constructor.
The only way myMethod could break would be if myCollaborator.anotherMethod() were broken. In that case, test myCollaborator, and not the current class.
It is true that adding tests for even these simple methods guards against the possibility that someone refactors and makes the methods "not-so-simple" anymore. In that case, though, the refactorer needs to be aware that the method is now complex enough to break, and should write tests for it -- and preferably before the refactoring.
Another example: suppose you have a JSP and, like a good programmer, you have removed all business logic from it. All it does is provide a layout for a number of JavaBeans and never does anything that could change the value of any object. That JSP is too simple to break, and since JSPs are notoriously annoying to test, you should strive to make all your JSPs too simple to break.
Here's the way testing goes:
becomeTimidAndTestEverything
while writingTheSameThingOverAndOverAgain
becomeMoreAggressive
writeFewerTests
writeTestsForMoreInterestingCases
if getBurnedByStupidDefect
feelStupid
becomeTimidAndTestEverything
end
end
The loop, as you can see, never terminates.
4. How often should I run my tests?
Run all your unit tests as often as possible, ideally every time the code is changed. Make sure all your unit tests always run at 100%. Frequent testing gives you confidence that your changes didn't break anything and generally lowers the stress of programming in the dark.
For larger systems, you may just run specific test suites that are relevant to the code you're working on.
Run all your acceptance, integration, stress, and unit tests at least once per day (or night).
5. What do I do when a defect is reported?
Test-driven development generally lowers the defect density of software. But we're all fallible, so sometimes a defect will slip through. When this happens, write a failing test that exposes the defect. When the test passes, you know the defect is fixed!
Don't forget to use this as a learning opportunity. Perhaps the defect could have been prevented by being more aggressive about testing everything that could reasonably break.
6. Why not just use System.out.println()?
Inserting debug statements into code is a low-tech method for debugging it. It requires that output be scanned manually every time the program is run to ensure that the code is doing what's expected.
It generally takes less time in the long run to codify expectations in the form of an automated JUnit test that retains its value over time. If it's difficult to write a test to assert expectations, the tests may be telling you that shorter and more cohesive methods would improve your design.
7. Why not just use a debugger?
Debuggers are commonly used to step through code and inspect that the variables along the way contain the expected values. But stepping through a program in a debugger is a manual process that requires tedious visual inspections. In essence, the debugging session is nothing more than a manual check of expected vs. actual results. Moreover, every time the program changes we must manually step back through the program in the debugger to ensure that nothing broke.
It generally takes less time to codify expectations in the form of an automated JUnit test that retains its value over time. If it's difficult to write a test to assert expected values, the tests may be telling you that shorter and more cohesive methods would improve your design.
Extending JUnit:
1. How do I extend JUnit?
JUnit is a testing framework intended to be customized for specialized use. Browsing the JUnit source code is an excellent way to learn its design and discover how it can be extended.
Examples of JUnit extensions can be found in the junit.extensions package:
* TestDecorator
A decorator for Tests. You can use it as the base class for implementing
new test decorators that add behavior before or after a test is run.
* ActiveTestSuite
A TestSuite that runs each test in a separate thread and waits until all
threads have terminated.
* TestSetup
A TestDecorator to initialize and cleanup test fixture state once before
the test is run.
* RepeatedTest
A TestDecorator that runs a test repeatedly.
* ExceptionTestCase
A TestCase that expects a particular Exception to be thrown.
Kent Beck has mentioned that ExceptionTestCase likely does not provide
enough to be useful; it is just as easy to write the "exception test"
yourself. Refer to the Writing Tests section for guidance.
2. What kinds of extensions are available?
The JUnit home page has a complete list of available JUnit extensions
(http://www.junit.org/news/extension/index.htm).
Miscellaneous:
1. How do I integrate JUnit with my IDE?
The JUnit home page maintains a list of IDE integration instructions
(http: //www.junit.org/news/ide/index.htm).
2. How do I launch a debugger when a test fails?
Start the TestRunner under the debugger and configure the debugger so that it catches the junit.framework.AssertionFailedError.
How you configure this depends on the debugger you prefer to use. Most Java debuggers provide support to stop the program when a specific exception is raised.
Notice that this will only launch the debugger when an expected failure occurs.
3. Where can I find unit testing frameworks similar to JUnit for other languages?
XProgramming.com maintains a complete list of available xUnit testing frameworks (http://www.xprogramming.com/software.htm).