WebObjects

Alex Cone speaking at OpenBase Summit 2007

Wednesday, October 10th, 2007

CodeFab CEO and roving lecturer Alex Cone will be giving two talks at the OpenBase Developer Summit in Vail Colorado, which opens on October 7th, 2007. The sessions will be:

WebObjects, AJAX & iPhone Development on Tuesday at 11:00am. This talk will be presented with CodeFab alumnus Scott Lopatin and will include a demonstration of a store presentation framework developed to show how to optimize presentation for the iPhone browser. The framework source can be downloaded here. CodeFab has also created a general developer mailing list for iPhone development discussion and developers interested in exchanging ideas should sign up here.

WebObjects, WebServices & XML on Wednesday at 11:00am. This talk will include a demo of XML-based web services that access the new Google Checkout service. The web service implementation is provided as a general framework that can easily be added to an existing store. The framework source can be downloaded here.

Alex will also be informally discussing the WireHose WebObject framework with interested developers. WireHose has recently seen the release of version 4.0 and a mailing list for WireHose development topics can be subscribed to here.

CodeFab is delighted to be asked to participate in the OpenBase Summit 2007 and would like to thank Scott Keith and all the rest of the OpenBase crew and all the conference sponsors for putting together an excellent developer event!

Woo-woo! All aboard for WOWODC!

Thursday, June 7th, 2007

After a long period of relative quiet, the WebObjects world is exploding with new activity. Apple’s new WO czar, Pierre Frisch, will be dropping a very significant release on us all at WWDC that signals the return to a strongly supported product and is the first step down a new path of evolution. At the same time, the WO community has rallied to organize a the first WebObjects WOrld Developer Conference to be held on June 10th, the day before Apple’s WWDC.

Come join the CodeFab team at WOWODC and WWDC. CodeFab CEO and Alpha Geek Alex Cone will be speaking at WOWODC, with some very special help from Scott Lopatin. Also in San Francisco for WOWO and WWDC will be fellow ‘Fabbers Bill Hatch, Apoorva Muralidhara and Chuck Swiger.

The WOWODC schedule on Sunday June 10th is:

09:00am - 09:05am Welcome Pascal Robert, OS communications informatiques inc.
09:05am - 10:30am Migrating to Eclipse/WOLips Chuck Hill, Global Village Consulting
10:30am - 10:45am Break
10:45am - 11:45am XML and WebServices Alex Cone, CodeFab; and Scott Lopatin, CodeFab
11:45am - 1:00pm AJAX Mike Schrag, m Dimension Technology; and Alex Cone, CodeFab
1:00pm - 1:30pm Lunch
1:30pm - 2:45pm Project Wonder's Best Features Anjo Krank, LogicUnited; and Mike Schrag, m Dimension Technology
2:45pm - 4:00pm Direct2Web Anjo Krank, LogicUnited
4:00pm - 4:15pm Break
4:15pm - 6:15pm Experts Panel Chuck Hill, Global Village Consulting; Alex Cone, CodeFab; Jake MacMullin, BBC; Mike Schrag, m Dimension Technology; and Anjo Krank, LogicUnited

WOPopUpButtons in a WORepetition

Thursday, October 19th, 2006

Abstract

In a WOForm in a WOComponent, you can repeat a dynamic element like WOPopUpButton by wrapping it in a WORepetition, and still extract the selected objects during binding synchronization by using a custom set accessor.

Getting selected objects from WOPopUpButtons in a WORepetition
by Apoorva Muralidhara

Here’s a useful trick that allows you to repeat form elements on a page with a WORepetition and extract the selected objects with very little code. This simple idea is the missing link that allows you to use WORepetition to get rid of more duplication in WOComponents.

PizzaRobot: An Example

A visionary businessman plans to install robotic pizza kiosks in New York, Los Angeles, and Atlanta in early 2007. These fully automated kitchens will make and toss dough, add fresh mozzarella and toppings, and bake a fresh pizza to the customer’s specifications. After obtaining a first round of funding, PizzaRobot hires you to develop a pizza ordering application in WebObjects.

The First Story

The first customer story card says:

“The user can specify up to eight toppings for a pizza pie. There will be eight topping dropdowns. Topping order doesn’t matter, and duplicate toppings will be treated as if chosen once.

“The initial list of toppings will be as follows: anchovy, artichoke, arugula, broccoli romanesco, chorizo, escargot, ginger, habanero, ham, kebab, kielbasa, leek, mayonnaise, onion, paneer, pepperoni, prosciutto, rabbit, ramp, tofu”

EOModel and Database Preliminaries

You create three entities and corresponding tables—Pizza, Topping, and PizzasToppings—with a flattened many-to-many relationship “toppings” from Pizza to Topping, using PizzasToppings as the correlation table. EOGenerator gives you the Pizza methods

public NSArray toppings()
public void setToppings(NSArray value)
public void addToToppings(Topping topping)
public void removeFromToppings(Topping topping)

You populate the Topping table with the given values, using a SQL script containing insert statements that you check into your Subversion repository.

The PizzaPage Component

You create a PizzaPage.wo WOComponent, add a WOForm, and create a pizza:

protected Pizza pizza; 

public void awake() {
  super.awake();

  pizza = new Pizza();
}

The topping popups will require an array of all toppings, so you add

public NSArray allToppings() {
  return EOUtilities.objectsForEntityNamed(session().defaultEditingContext(), "Topping");
}

and an instance variable for iterating over the toppings:

public Topping topping;

Now you need to put eight WOPopUpButtons onto PizzaPage.

The Eightfold Way: The Cut-and-Paste Solution

You could, of course, do the following:

Add eight public instance variables to PizzaPage.java:

    public Topping selectedTopping1;
    public Topping selectedTopping2;
    public Topping selectedTopping3;
    public Topping selectedTopping4;
    public Topping selectedTopping5;
    public Topping selectedTopping6;
    public Topping selectedTopping7;
    public Topping selectedTopping8;

Add eight WOPopUp buttons to PizzaPage.html:


Add eight sets of bindings to PizzaPage.wod:

ToppingPopUpButton1: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping1;
}

ToppingPopUpButton2: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping2;
}

ToppingPopUpButton3: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping3;
}

ToppingPopUpButton4: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping4;
}

ToppingPopUpButton5: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping5;
}

ToppingPopUpButton6: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping6;
}

ToppingPopUpButton7: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping7;
}

ToppingPopUpButton8: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping8;
}

You want to ignore topping order and duplication, so you use an NSSet. Your save() method looks like this:

public ThankYouPage save()
{
  NSSet selectedToppings =
    new NSSet(new Topping[] {selectedTopping1, selectedTopping2,
      		 	      	    	       selectedTopping3, selectedTopping4,
					       selectedTopping5, selectedTopping6,
				    	       selectedTopping7, selectedTopping8});
  pizza.setToppings(selectedToppings.allObjects());

  session().defaultEditingContext().insertObject(pizza);
  session().defaultEditingContext().saveChanges();

  return (ThankYouPage)pageWithName("ThankYouPage");
}

Your Clone Army Smells of Duplication

This repetitive solution has Duplicated Code Smell, which Martin Fowler talks about in his book Refactoring. Your cut-and-pasted code clone army can get out of sync, and other developers will have to do a side-by-side comparison to perceive that there are eight nearly identical copies of everything. If RobotPizza asks for the noSelectionString to be “Choose a scrumptious topping” instead of ” — “, you’ll have to do change the string in eight places. And if they upgrade their auto-pie hardware to allow eleven toppings, you’ll have to cut-and-paste (or kill-and-yank, for us Emacs weenies) three more public instance variables (in PizzaPage.java), three more tags (in PizzaPage.html), and three more “ToppingPopUpButton” binding-set entries (in PizzaPage.wod), and not forget to add the three additional toppings to the Topping[] array in PizzaPage.save().

Express Yourself With a WORepetition

Like Madonna or N.W.A., you must “express yourself.” You, the human (or blog-reading extraterrestrial geek) writing the code, know that there are “eight topping popup buttons,” but you’re only telling WebObjects about them one at a time, without conveying their essential identity. To use more of WO’s expressive power, create a WORepetition with “count” bound to 8:

ToppingPopUpRepetition: WORepetition {
	count = 8;
}

You wrap this repetition around a single WOPopUpButton:

ToppingPopUpButton: WOPopUpButton {
	displayString = topping.name;
	item = topping;
	list = allToppings;
	noSelectionString = " --- ";
	selection = selectedTopping;
}

with

public Topping topping, selectedTopping;

in PizzaPage.java.

All seems to be in order. So what’s the problem?

The Solution: Render Unto Me the Pizza Toppings I Have Chosen

How do you get the selected toppings from the eight generated WOPopUpButtons? Ay, there’s the rub. Above I’ve bound “selection” to “selectedTopping,” but with only a single selectedTopping instance variable, the up to eight toppings chosen by the user will fall through your fingers like so many pebbles.

[I pause for a moment to increase the suspense.]

The simple, elegant solution illustrates a pattern for getting selections from dynamic elements in a WORepetition.

Keep the public instance variable selectedTopping—you need it so that WebObjects can resolve valueForKey(”selectedTopping”) on the PizzaPage component when first rendering the popups in appendToResponse().

Add an instance variable for the set of selected toppings. You’ll be adding to it, so it has to be an NSMutableSet:

public NSMutableSet selectedToppings;

Add a line to your awake() method:

    selectedToppings = new NSMutableSet();

Here’s the trick: Add a set accessor for selectedTopping that, instead of setting the selectedTopping instance variable, adds the selected topping to selectedToppings:

public void setSelectedTopping(Topping aSelectedTopping) {
  selectedToppings.addObject(aSelectedTopping);
}

Now your save method looks like this:

public ThankYouPage save()
{
  pizza.setToppings(selectedToppings());

  session().defaultEditingContext().insertObject(pizza);
  session().defaultEditingContext().saveChanges();

  return (ThankYouPage)pageWithName("ThankYouPage");
}

where

public NSSet selectedToppings() {
  return selectedToppings.immutableClone();
}

Simpler and cleaner. When the client wants you to modify the noSelectionString, you’ll only have to change it in one place. And when they finally manage to upgrade their pizzabots to handle eleven toppings, you’ll just have to change the “8″ to an “11.”

Musings on Managing Static Resources in WebObjects Applications

Saturday, June 10th, 2006

When we refer to “Static Resources” in dynamic web-based applications, we are referring to publicly visible content files that are not dynamically generated or produced by the software. Usually these are image, CSS , or stand-alone HTML files. There are several approaches to handling static resources in WebObjects, each with its own advantages and drawbacks. The three approaches discussed below are probably the most straightforward.

First Approach: Add Resources to your Xcode Project’s Application Server Build Target

This is perhaps the easiest approach. Adding one or more resources is accomplished in Xcode by using the ‘Project’ drop-down menu and clicking on ‘Add To Project’. Select the images or other files you wish to add on the first pop-up sheet. On the second pop-up sheet, under ‘Add To Targets’ check Application Server. Check the ‘Copy items…’ checkbox if the resources do not yet currently reside inside the project folder.

Once the project contains the resources and is aware of them, you can simply refer to them by name when doing bindings in WOBuilder. At the time of deployment the resource files are included inside the application “.woa” bundle which is installed in /Library/WebObjects/Applications. Inside the bundle the resources are in /Contents/Resources. Since the files are not in the webserver documents area, Apache (or another webserver) cannot vend them directly. The URL calls the WO application and the WOResourceManager finds the image (or other resource) and returns it.

Although this approach is easy, the WebObjects application is involved in generating each image URL and is also required to make a response for every image displayed. Also, in certain circumstances, the ability for the webserver to cache images is disabled (since the URL can change each time).


Second Approach: Add Resources to your Xcode Project’s Web Server Build Target

Adding resources is just as easy as the first approach - under ‘Add To Targets’ you just check Web Server.

WOResourceManager is still consulted when doing page generation to create the full path, since the WO template contains only the file name.

This second approach is Apple’s recommended default solution, though there are some issues I will discuss.

Where do the files go when you deploy? You have to do some special tricks when deploying to manage this (Called a “split install”). A special bundle is created, called the same as your application (i.e. “MyApp.woa”), that contains all the webserver resources but none of the executable code, and this goes in the WebServer Documents directory, in a directory called WebObjects. The resource files now have a static location that is publicly visible.

What do URLs look like?
“/WebObjects/MyApp.woa/Contents/Resources/header.jpg”

The main advantage of this approach is that the webserver serves images directly and the images can be locally cached. On the other hand, doing split installs takes some extra work and needs to be done carefully. Moreover, URLs are long (adding size to HTML files). Lastly, images are ‘filed’ in directories that correspond to a particular application and are more difficult to share (yes, you can create a framework to be loaded by multiple applications. But if any of the resources are used by non-WO content this gets unwieldy).

Third Approach: Store Resources Externally to your Project

In this approach, all images and other resources are kept external to the project and are not ‘managed’ by Xcode. They are stored in a subdirectory of the the webserver document root and are always accessed via the webserver.

On the up side, this approach results in the best performance of the three and it scales well. When the number of distinct image files becomes very large, you can further ensure good performance by creating a directory tree and distributing images across the tree. Images (and resources generally) can be shared between applications and with static html pages. Finally, when outside graphic designers are involved in a project they can manipulate image files directly without the need for someone to rebuild application bundles.

On the other hand, this third approach requires a bit more effort from developers. Unlike the other two approaches, the names of the resources do not show up WOBuilder. Potential problems can arise as developers enter image file names in component bindings, sometimes misspelling them. However, if the images are installed locally in the webserver document root, WOBuilder will render them immediately, so you get feedback right away if you make a typo.

The trick I use for this is to create a Static Resources directory adjacent to my project directory and create a symbolic link to it from the webserver document root. This allows me to do an update or commit to my source code control system from the directory containing the project and the resource directories to update or commit them both at the same time. Once the link is in place I can forget (on my development machine anyway) about updating the webserver content.

Summary

My personal favorite is approach number three. CodeFab’s development projects almost always get large and complex enough that there are resources that need to be shared beyond what a WO framework provides. Additionally we often end up with static HTML pages, CSS files, JavaScript files, etc. that do not really belong inside the project. Your personal mileage may vary.

All three of the approaches discussed here are in wide use. Based on the requirements of a particular application, we recommend that you chose one of them and stick with it. Especially on projects with multiple developers it is a good idea to make this system architecture decision early and make sure everyone understands it.

WOUnitTest: Refactor Without Fear

Monday, June 5th, 2006

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!