Welcome!

Java Authors: Al Mannarino, Christopher Keene, Anatoly Krivitsky, Pat Romanski, Jason Weathersby

Related Topics: Java

Java: Article

Generating JUnit Tests for Legacy Java Applications

How to maintain apps you didn't write

If we run the characterization tests against the original classes, the classes will pass with flying colors (well, green), as shown in Figure 3. This is to be expected: characterization tests will always work against the unmodified code under test, because the tests are based on what the code actually does-good, bad, or indifferent.

Since the JPetStore Domain is designed with JavaBeans, many of these tests are simple exercises of getters and setters. Other more interesting tests peer into the business logic as Listing 5 shows.

JUnit Factory realized that if it sets the ListPrice on a single CartItem and then calls getTotalPrice(), it will get a value equal to ListPrice. In other words, if there's only one item in the shopping cart, the total price for everything in the cart is the same as the price of that one item.

Very Interesting - But a Bug!
Listing 6 shows another "interesting" characterization test.

The test is telling us that if the line items are set to null, the system will throw a runtime NullPointerException. Remember, a characterization test documents what the code under test actually does: JUnit Factory isn't offering an opinion of the correctness of that behavior.

Let's look at Listing 7, which shows bits of the code under test.

Class Order instantiates lineItems then initializes it to an empty ArrayList(). As written, other parts of the class simply assume that lineItems will never be null. But since there is a public setLineItems method, the non-null assumption seems rash. Being prudent programmers, we decide to fix this latent bug before it crawls out of the woodwork: the fix is to apply "lazy instantiation" to the lineItems field, as Listing 8 shows.

To finish the job, we make sure that the class is calling its own getter method throughout rather than accessing the field directly. (A good practice under any circumstances!)

If we clear the tests (by deleting the agitar/test folder) and regenerate, JUnit Factory will return us one fewer test.

Looking back, we see that JUnit Factory uncovered the missing null-guard just by analyzing the code. It also discovered the relationship between list price of an item and the total price of an order, all without being told. Hmmm...I wonder what it could do if we could give JUnit Factory a clue as to how the domain objects are supposed to work?

Generate Better Tests with Realistic Test Data
In the context of the JPetStore application, Items are magical objects that appear fully formed from the database (via Data Access Objects). JPetStore is a consumer-facing shopping cart application: the application never creates an Item itself. All it does is wrap Items in CartItems, which go into the shopping Cart.

We could generate more useful tests by providing a realistic set of Item objects rather than let JUnit Factory create them willy-nilly. To create realistic objects, we can write a Test Data Helper.

Essentially, by implementing the TestHelper "marker" interface, we create a simple factory that provides well-formed objects. When JUnit Factory needs an instance of a particular class, it looks to see if there are any helper methods available that return objects of that class. If any of these helper objects will work for the test JUnit Factory is trying to generate, it will give the helper objects priority over test objects it creates from scratch.

Using our inside knowledge of what kind of items appear in our database, we can write a TestHelpers class that provides realistic Item objects for our tests to use, as Listing 9 shows.

With TestHelpers in place, the generated tests will reference our factory methods, instead of creating objects from whole cloth. Listing 10 is an example.

Instead of making something up, the testToString method calls the toString method on one of our TestHelper classes. The toString form is a concatenation of the item ID and the product ID. Just by glancing at the generated test, we can see that it is characterizing the code correctly - thanks in part to our TestHelpers class that provided realistic data for the tests.

Extend the Business Logic with Confidence
Now that we have a set of characterization tests, we can get to work on the business end of the application.
  •   We can study each test suite alongside the code under test to help us better understand how the legacy application actually works.
  •   Along the way, if we note "suspicious" or "unexpected" behavior in a test, we can dig deeper to see if there's a latent bug that may complicate maintenance.
  •   We can extend and refactor the application with confidence, since our characterization tests will alert us of unintended consequences.
  •   When we do decide to change the behavior of the application, we can regenerate the characterization tests as a new baseline for future changes.

Characterization tests are unit tests, but they should not be all the unit tests. We should still write a smaller set of custom tests, particularly as we create new classes and methods. But, with JUnit Factory in our tool belt, we can focus our time on custom tests that exercise key features, while the tool generates the bulk of the boring, but valuable, characterization tests.

More Stories By Ted Husted

Ted Husted (http://husted.com/ted/) is a software engineer and an active member of several open source projects hosted by the Apache Software Foundation, including Struts and iBATIS. His books include JUnit in Action, Struts in Action, and Professional JSP Site Design. Ted is also a consultant for Agitar Software, Inc.

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
Alexander Klimuk 01/14/08 06:37:02 PM EST

This article resembles the video of Alberto Savoia from Agitar, which is available as of Sep 2007 at 'theserverside.com':
http://www.theserverside.com/news/thread.tss?thread_id=46931#239986

Well, a bit different wording, and that's all.

Why haven't you just placed a link to that video and discussion on TSS site?

Regards,
Alexander Klimuk