Abstract
Test-Driven Development (TDD), a core Extreme Programming (XP) practice, creates fluid, clear code with fewer bugs. JUnit is great for testing non-WO Java code, but for WebObjects applications you need WOUnitTest, which allows you to easily test enterprise objects.
Refactor Without Fear
by Apoorva Muralidhara
In early 2000, as I was joining Codefab, I read Martin Fowler’s Refactoring: Improving the Design of Existing Code and Kent Beck’s Extreme Programming Explained: Embrace Change. I was captivated by the idea of reworking code in small steps while preserving behavior, instead of throwing it away and rewriting it from scratch. Fowler and
Beck explained that refactoring necessitated automated unit tests. So I decided to try test-first programming (now called Test-Driven Development (TDD)), merciless refactoring, and pair programming. Over the past six years, I’ve found that TDD does indeed create code that’s better designed and more maintainable, with higher quality.
Automated unit tests set up objects, call methods, and make assertions that pass or fail. Many test GUI’s use a green bar to indicate that all tests pass, and a red bar to show that at least one test has failed. “Keep the bar green to keep the code clean,” as the JUnit site says–all test must pass at all times in the committed version of the code.
The TDD coding cycle is “red-green-refactor”:
Red: Write an automated unit test, and watch it fail, because the functionality has not been implemented. Writing the test first checks that the desired new behavior isn’t already there, and that you’re actually testing it. It also means that the code you’re about to write already has a client that exercises it, and which documents its intended use. And it forces you to do the thinking about this method’s interface and expected behavior before writing it–you decide what needs to be done before deciding how to do it. When I first heard of “test first,” it sounded very strange–but after just a few days the idea of writing the test after the code started to sound strange. Note that in a static language like Java, you’ll have to write stub versions of methods just to get the code to compile so that the test fails.
Green: Write just enough code to make the test pass. Here’s one advantage of writing the test first–the change from red to green means something important. It shows that your production code actually does what you asserted it should do. All tests should now be green, so you can integrate your changes with the repository head and commit it. One XP practice is Continuous Integration–commit as often as possible, and avoid “integration hell.”
Refactor: All tests pass. Refactor to remove what Martin Fowler calls “code smells,” including Long Method and Duplicated Code smells. After each small modification, following the patterns in Fowler’s Refactoring, rerun the tests to ensure you haven’t broken anything. You can commit whenever the test bar is green, so commit often. I highly recommend Fowler’s book, which you can read like a novel (or short story collection), but which will transform your thinking about the actual process of coding.
Keep the code clear with explanatory method and variable names. Good design emerges incrementally, sans overengineering–because you refactor mercilessly, but only implement what you need at the moment. Your unit tests allow you to refactor without fear. Your code is no longer static, brittle because you’re afraid to break anything by touching it. It’s dynamic, fluid, adapting to new requirements and better design.
WO-friendly Unit Testing
In 2000, my pair programming partner and I used JUnit as best as we could to test our WebObjects applications. JUnit is a great tool for testing ordinary Java code, but WebObjects applications have, for example, editing contexts, applications, and sessions, which JUnit, in its innocence, knows nothing about. So we tested WO apps from the outside by firing direct actions.
But the closer your tests are to the code they test, and the less dependent they are on other code, the more effectively they isolate and catch bugs. We needed a way to test smaller pieces of code, from the inside of a WebObjects application.
Most WebObjects applications use a database, but it’s best not to do so in your tests. The database round trip slows down your tests: if each test takes half a second to run, then six hundred tests–not high for a TDD project–will take five minutes, destroying agility. If you
*must* connect to the database, use an empty test copy with no rows, and populate and clear it at the beginning and end of each test run, so its state is predictable, and the setup code makes it clear what data your test needs. Unfortunately, we didn’t know that six years ago. So our tests ran against the “real” (development) database, and often broke when the data changed.
Then two Codefab developers, Christian Pekeler and Shin Ogino, created WOUnitTest, which Christian has maintained and greatly enhanced over the last few years.
WOUnitTest extends JUnit to make it more WebObjects-friendly. Instead of subclassing JUnit’s TestCase, you subclass WOUTTestCase.
You can test an EO’s validation without saving it with one of the following methods:
assertValidForDelete()
assertValidForInsert()
assertValidForSave()
assertValidForUpdate()
assertInvalidForDelete()
assertInvalidForInsert()
assertInvalidForSave()
assertInvalidForUpdate()
But what if you’re writing a method that creates or fetches EO’s? How do you test this code without using a database? To solve this problem of external dependencies, Tim Mackinnon, Steve Freeman, and Philip Craig developed Mock Objects, introduced in their paper at XP2000, Endo-Testing: Unit Testing with Mock Objects[PDF]. Like Lewis Carroll’s Mock Turtle, which is no longer a real turtle, a mock object pretends to be an object in the production code, but it fakes the functionality which depends on an external system–in this case the database.
We WO developers are fortunate–we use a well-designed object layer, EOF, to hit the database. (Try testing without a database in PHP!) WOUTTestCase gives you a mock object, MockEditingContext, that subclasses EOEditingContext, but doesn’t actually access the database. MockEditingContext has a createSavedObject() method which inserts an object into the receiver with a non-temporary global id, as if it’s been saved. It has an objectsWithFetchSpecification() method that “fetches” from its registeredObjects() instead of hitting the database. To get the provided mock editing context from a WOUTTestCase subclass, just call mockEditingContext(). And if you must hit the database, use editingContext() will give you a real editing context.
WOUTTestCase is aware of WebObjects in other ways–for example, If you need a WOComponent instance, just call pageWithName().
If you expect your production code to return an NSArray of objects, with order irrelevant, you could use JUnit’s assertEquals(), adjusting the expected value’s order so the test passes. But this risks spurious test failures when something changes that affects order, which you don’t care about anyway. Or, you could sort the array your production method returns, so that the order is predictable. But this causes you to sort objects whose order you don’t really care about. The best solution is to use an assert method that doesn’t care about order, and Christian provides these in WOUTTestCase:
assertArraysEqualAsBags()
assertArraysNotEqualAsBags()
OstrichWorld: An Example
Suppose an ostrich farm needs a WebObjects application so they can vend their flightless birds to the online masses as convenient household pets. Your EOModel will include a Ostrich entity with a name attribute, and you need a Ostrich.createOstrich() method.
In OstrichTest.java, don’t hit the database–just use mockEditingContext():
import com.webobjects.foundation.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
import com.ostrichworld.eo.*;
public class OstrichTest extends WOUTTestCase {
public void testCreateOstrichCreatesOstrichWithNewName() {
String newName = "Brunella";
assertEquals(0, EOUtilities.objectsMatchingKeyAndValue(mockEditingContext(), "Ostrich", "name", newName).count());
Ostrich newOstrich = Ostrich.createOstrich(mockEditingContext(), newName);
assertEquals(1, EOUtilities.objectsMatchingKeyAndValue(mockEditingContext(), "Ostrich", "name", newName).count());
}
}
Then you can implement Ostrich.createOstrich() as follows. I hope you’re using EOGenerator to separate your auto-generated EOF Java files from your custom EOF subclasses:
package com.ostrichworld.eo;
import com.webobjects.foundation.*;
import com.webobjects.eocontrol.*;
import com.webobjects.eoaccess.*;
import com.codefab.wounittest.*;
public class Ostrich extends _Ostrich {
public static Ostrich createOstrich(EOEditingContext editingContext, String name) {
Ostrich ostrich = (Ostrich)EOUtilities.createAndInsertInstance(editingContext, "Ostrich");
ostrich.setName(name);
return ostrich;
}
}
Should an ostrich have a null name? Of course not! You could enforce this by setting the “allows null” flag to false on the Ostrich.name attribute in the EOModel. Or you could check this is in a validation method on Ostrich.
If you want to do this in validateForSave(), you can use assertValidForSave() and assertInvalidForSave():
String name;
Ostrich ostrich;
protected void setUp() throws Exception {
super.setUp();
ostrich = (Ostrich)mockEditingContext().createSavedObject("Ostrich");
name = "Bartholomew";
}
public void testValidateNameForSave() {
ostrich.setName(null);
assertInvalidForSave(ostrich);
ostrich.setName(name);
assertValidForSave(ostrich);
}
To do this in validateName(), you can use the same setUp(), but unfortunately there’s no assert method that calls validateValueForKey() for you–you’ll have to do it yourself. I’d use two tests for this:
public void testNullNameIsInvalid() {
try {
ostrich.validateValueForKey(null, "name");
fail("Null name was valid!");
} catch (NSValidation.ValidationException exception) {
assertTrue(true);
}
}
public void testNonNullNameIsValid() {
ostrich.validateValueForKey(name, "name");
}
Yes, I know the second test doesn’t contain an assert. What it’s implicitly asserting is that calling validateValueForKey() doesn’t throw an exception.
These are extremely simple examples, but you can set up many different objects with mockEditingContext().createSavedObject(), link them with addObjectToBothSidesOfRelationshipWithKey(), and assert many things with WOUTTestCase’s methods.
Getting WOUnitTest
Once you have JUnit, download WOUnitTest from Sourceforge and unarchive it.
The WOUnitTest directory you’ve downloaded includes a README.txt, which explains how to build and install the WOUnitTest framework. Then you just add the WOUnitTestFramework and you’re ready to write subclasses of WOUTTestCase, as in the examples above.
Run your WebObjects application, and fire the direct action “ut” (“unit test”) to bring up a Web interface for running your tests, or “uta” (“unit test all”) to run all tests. Live by the green bar; die by the red bar.
Conclusion
WOUnitTest makes it easier to do test-driven development of WebObjects applications, leading to more maintainable code with fewer bugs. The most important thing it gives you that plain JUnit doesn’t is an easy way–actually several easy ways–to test EO’s without accessing the database. Go forth and develop–test-first!