Programming Tutorials Browser Tutorials Articles Struts Tutorials Hibernate Tutorials

  Tutorial: JUnit best practices - JavaWorld December 2000

JUnit best practices - JavaWorld December 2000

Tutorial Details:

JUnit best practices
JUnit best practices
By: By Andy Schneider
Techniques for building resilient, relocatable, multithreaded JUnit
Unit is a typical toolkit: if used with care and with recognition of its idiosyncrasies, JUnit will help to develop good, robust tests. Used blindly, it may produce a pile of spaghetti instead of a test suite. This article presents some guidelines that can help you avoid the pasta nightmare. The guidelines sometimes contradict themselves and each other -- this is deliberate. In my experience, there are rarely hard and fast rules in development, and guidelines that claim to be are misleading.
We'll also closely examine two useful additions to the developer's toolkit:
A mechanism for automatically creating test suites from classfiles in part of a filesystem
A new TestCase that better supports tests in multiple threads
When faced with unit testing, many teams end up producing some kind of testing framework. JUnit, available as open source, eliminates this onerous task by providing a ready-made framework for unit testing. JUnit, best used as an integral part of a development testing regime, provides a mechanism that developers can use to consistently write and execute tests. So, what are the JUnit best practices?
Do not use the test-case constructor to set up a test case
Setting up a test case in the constructor is not a good idea. Consider:
public class SomeTest extends TestCase
public SomeTest (String testName) {
super (testName);
// Perform test set-up
}
}
Imagine that while performing the setup, the setup code throws an IllegalStateException . In response, JUnit would throw an AssertionFailedError , indicating that the test case could not be instantiated. Here is an example of the resulting stack trace:
junit.framework.AssertionFailedError: Cannot instantiate test case: test1 at
junit.framework.Assert.fail(Assert.java:143) at
junit.framework.TestSuite$1.runTest(TestSuite.java:178) at
junit.framework.TestCase.runBare(TestCase.java:129) at
junit.framework.TestResult$1.protect(TestResult.java:100) at
junit.framework.TestResult.runProtected(TestResult.java:117) at
junit.framework.TestResult.run(TestResult.java:103) at
junit.framework.TestCase.run(TestCase.java:120) at
junit.framework.TestSuite.run(TestSuite.java, Compiled Code) at
junit.ui.TestRunner$12.run(TestRunner.java:429)
This stack trace proves rather uninformative; it only indicates that the test case could not be instantiated. It doesn't detail the original error's location or place of origin. This lack of information makes it hard to deduce the exception's underlying cause.
Instead of setting up the data in the constructor, perform test setup by overriding setUp() . Any exception thrown within setUp() is reported correctly. Compare this stack trace with the previous example:
java.lang.IllegalStateException: Oops at bp.DTC.setUp(DTC.java:34) at
junit.framework.TestCase.runBare(TestCase.java:127) at
junit.framework.TestResult$1.protect(TestResult.java:100) at
junit.framework.TestResult.runProtected(TestResult.java:117) at
junit.framework.TestResult.run(TestResult.java:103)
...
This stack trace is much more informative; it shows which exception was thrown ( IllegalStateException ) and from where. That makes it far easier to explain the test setup's failure.
Don't assume the order in which tests within a test case run
You should not assume that tests will be called in any particular order. Consider the following code segment:
public class SomeTestCase extends TestCase {
public SomeTestCase (String testName) {
super (testName);
}
public void testDoThisFirst () {
...
}
public void testDoThisSecond () {
}
}
In this example, it is not certain that JUnit will run these tests in any specific order when using reflection. Running the tests on different platforms and Java VMs may therefore yield different results, unless your tests are designed to run in any order. Avoiding temporal coupling will make the test case more robust, since changes in the order will not affect other tests. If the tests are coupled, the errors that result from a minor update may prove difficult to find.
In situations where ordering tests makes sense -- when it is more efficient for tests to operate on some shared data that establish a fresh state as each test runs -- use a static suite() method like this one to ensure the ordering:
public static Test suite() {
suite.addTest(new SomeTestCase ("testDoThisFirst";));
suite.addTest(new SomeTestCase ("testDoThisSecond";));
return suite;
}
There is no guarantee in the JUnit API documentation as to the order your tests will be called in, because JUnit employs a Vector to store tests. However, you can expect the above tests to be executed in the order they were added to the test suite.
Avoid writing test cases with side effects
Test cases that have side effects exhibit two problems:
They can affect data that other test cases rely upon
You cannot repeat tests without manual intervention
In the first situation, the individual test case may operate correctly. However, if incorporated into a TestSuite that runs every test case on the system, it may cause other test cases to fail. That failure mode can be difficult to diagnose, and the error may be located far from the test failure.
In the second situation, a test case may have updated some system state so that it cannot run again without manual intervention, which may consist of deleting test data from the database (for example). Think carefully before introducing manual intervention. First, the manual intervention will need to be documented. Second, the tests could no longer be run in an unattended mode, removing your ability to run tests overnight or as part of some automated periodic test run.
Call a superclass's setUp() and tearDown() methods when subclassing
When you consider:
public class SomeTestCase extends AnotherTestCase {
// A connection to a database
private Database theDatabase;
public SomeTestCase (String testName) {
super (testName);
}
public void testFeatureX () {
...
}
public void setUp () {
// Clear out the database
theDatabase.clear ();
}
}
Can you spot the deliberate mistake? setUp() should call super.setUp() to ensure that the environment defined in AnotherTestCase initializes. Of course, there are exceptions: if you design the base class to work with arbitrary test data, there won't be a problem.
Do not load data from hard-coded locations on a filesystem
Tests often need to load data from some location in the filesystem. Consider the following:
public void setUp () {
FileInputStream inp ("C:\\TestData\\dataSet1.dat");
...
}
The code above relies on the data set being in the C:\TestData path. That assumption is incorrect in two situations:
A tester does not have room to store the test data on C: and stores it on another disk
The tests run on another platform, such as Unix
One solution might be:
public void setUp () {
FileInputStream inp ("dataSet1.dat");
...
}
However, that solution depends on the test running from the same directory as the test data. If several different test cases assume this, it is difficult to integrate them into one test suite without continually changing the current directory.
To solve the problem, access the dataset using either Class.getResource() or Class.getResourceAsStream() . Using them, however, means that resources load from a location relative to the class's origin.
Test data should, if possible, be stored with the source code in a configuration management (CM) system. However, if you're using the aforementioned resource mechanism, you'll need to write a script that moves all the test data from the CM system into the classpath of the system under test. A less ungainly approach is to store the test data in the source tree along with the source files. With this approach, you need a location-independent mechanism to locate the test data within the source tree. One such mechanism is a class. If a class can be mapped to a specific source directory, you could write code like this:
InputStream inp = SourceResourceLoader.getResourceAsStream (this.getClass (), "dataSet1.dat");
Now you must only determine how to map from a class to the directory that contains the relevant source file. You can identify the root of the source tree (assuming it has a single root) by a system property. The class's package name can then identify the directory where the source file lies. The resource loads from that directory. For Unix and NT, the mapping is straightforward: replace every instance of '.' with File.separatorChar .
Keep tests in the same location as the source code
If the test source is kept in the same location as the tested classes, both test and class will compile during a build. This forces you to keep the tests and classes synchronized during development. Indeed, unit tests not considered part of the normal build quickly become dated and useless.
Name tests properly
Name the test case TestClassUnderTest . For example, the test case for the class MessageLog should be TestMessageLog . That makes it simple to work out what class a test case tests. Test methods' names within the test case should describe what they test:
testLoggingEmptyMessage()
testLoggingNullMessage()
testLoggingWarningMessage()
testLoggingErrorMessage()
Proper naming helps code readers understand each test's purpose.
Ensure that tests are time-independent
Where possible, avoid using data that may expire; such data should be either manually or programmatically refreshed. It is often simpler to instrument the class under test, with a mechanism for changing its notion of today. The test can then operate in a time-independent manner without having to refresh the data.
Consider locale when writing tests
Consider a test that uses dates. One approach to creating dates would be:
Date date = DateFormat.getInstance ().parse ("dd/mm/yyyy");
Unfortunately, that code doe


 

Read Tutorial at: Click here to view the tutorial

Rate Tutorial:
JUnit best practices - JavaWorld December 2000

View Tutorial:
JUnit best practices - JavaWorld December 2000

Related Tutorials:

JSP best practices
Follow these tips for reusable and easily maintainable JavaServer Pages
 
Test networked code the easy way
Test networked code the easy way
 
Axis-orizing objects for SOAP
Axis-orizing objects for SOAP
 
Very interesting article
Very interesting article
 
Top 15 Ant Best Practices
Top 15 Ant Best Practices Ant, building and deploying Java applications required a hodgepodge of platform-specific scripts, makefiles, proprietary IDEs, or manual processes. Now, nearly every open source Java project uses Ant. A great number of companie
 
Advanced Installer for Java
Advanced Installer for Java The quick, simple and powerful msi authoring tool Advanced Installer is a Windows Installer authoring tool which enables developers and system administrators to easily build reliable MSI packages that meet the latest Micr
 
Control your test-environment with DbUnit and Anthill
The inception of the Extreme Programming methodology has brought test-driven development and continuous integration into mainstream Java development practices. Applying these techniques to Java server-side development can quickly become a nightmare if you
 
End-to-end internationalization of Web applications
End-to-end internationalization of Web applications Going beyond the JDK A typical Web application workflow involves a user loading one of your Webpages into her browser, filling out HTML form parameters, and submitting data back to the server. The ser
 
JDemo
JDemo is the Java demonstration framework. Its concept is similar to the one of JUnit. As supplement to test driven software development, JDemo provides a new approach of demo driven development: When developing software, you write short code snippets (
 
Testing J2EE applications
Testing J2EE applications There are certain aspects of J2EE applications that people associate with end-to-end tests rather than object tests. These include page flow—or navigating a Web application—and using container services, such as security and
 
Struts best practices
Multiple options are available for solving problems with Struts. When deciding among these alternatives, the choice must be based on parameters such as the scale of work and availability of time.
 
Build scripts with Groovy and Ant
Build scripts with Groovy and Ant Summary In nearly all developers' toolboxes, Ant is the standard build tool for Java applications, thanks to its open, standard, and multiplatform structure. Though it represents a great improvement in automating produc
 
Practically Groovy: Unit test your Java code faster with Groovy
Why unit test with Groovy? What makes Groovy particularly appealing with respect to other scripting platforms is its seamless integration with the Java platform. Because it's based on the Java language (unlike other alternate languages for the JRE, which
 
Commons-Math: The Jakarta Mathematics Library
Commons-Math: The Jakarta Mathematics Library The Java programming language and the math extensions in Commons Lang provide implementations for only the most basic mathematical algorithms. Routine development tasks such as computing basic statistics or s
 
Lucene in Action
Lucene in Action Lucene is a gem in the open-source world--a highly scalable, fast search engine. It delivers performance and is disarmingly easy to use. Lucene in Action is the authoritative guide to Lucene. It describes how to index your data, includin
 
Proactive Patch Management -- A Policy-Based Approach
This BigAdmin article discusses the benefits of strategic patch management based on service management and maintenance policies.
 
Interoperability with Patterns and Strategies for Document-Based Web Services
In Part 2 of this article, we demonstrate interoperability for document-driven web services with Microsoft .NET (C#) using strategies discussed in Part 1.
 
New Technical Articles: 64-bit Programming on Solaris 10 OS for x86 Platforms
Four technical articles describe the new Sun Studio 10 software's 64-bit programming features on the Solaris 10 OS for x86 and AMD64 platforms. Important issues regarding the AMD64 ABI (Application Binary Interface), debugging, migration to 64-bits, and p
 
Solaris Patch Management: Recommended Strategy (pdf)
This Sun BluePrints article describes types of patches, patch interrelationships, and patch delivery collections, with recommended practices for maintaining properly patched and updated software. Included is an explanation of what goes into a Solaris soft
 
Configuring JumpStart Servers to Provision on Sun x86-64 Systems (pdf)
Solaris JumpStart technology provides a mechanism for fully automating the installation of the Solaris Operating System. This Sun BluePrints article describes how to modify existing JumpStart servers to support the deployment of the Solaris OS and Linux
 
Site navigation
 

 

Send your comments, Suggestions or Queries regarding this site at roseindia_net@yahoo.com.

Copyright © 2006. All rights reserved.