Exploration Through Example

Example-driven development, Agile testing, context-driven testing, Agile programming, Ruby, and other things of interest to Brian Marick
191.8 167.2 186.2 183.6 184.0 183.2 184.6

Fri, 15 Apr 2005

Clams got legs!

Not exactly, but two species of octopus walk - nay, scurry - on two legs. Video here.

## Posted at 10:53 in category /misc [permalink] [top]

Design-Driven Test-Driven Design (Part 4)

For background, see the table of contents in the right sidebar.

The story so far: I got a Fit test describing a user workflow through a medical records system. I made part of that workflow work using a Model View Presenter style and Mock Views. I'm now ready to start building the real views that will talk to the Macintosh's GUI framework.

Here's my normal practice when driving coding with Fit business-facing examples: I constantly want to make the next cell of the Fit table green. When I know that will take too long for comfort, I divide-and-conquer the problem by breaking the interval into shorter ones punctuated by xUnit green bars. There are some disadvantages to treating xUnit tests as secondary to business-facing tests, but that's the way I do it.

For this task, I'm doing something similar. In the previous episode, I got to a green Fit cell, creating two Presenter objects, two Mock Views, and two abstract View classes along the way. Now I'm going to flesh out the meaning of statements like "choosing Betsy with owner Rankin". The interaction designer's wireframe sketch shows that done through a list. But when I make that list, a host of questions pop up, like "When you add a new case, is it automatically selected in the list?" Talking about properties of particular UI elements seemed like a job for unit-size tests.

It was also painfully obvious that I needed more UI. There's a place in the wireframe to select a case, but no place to add one. For the sake of expediency, I hung that UI off the side of what I was given, well aware that I'll probably change it later. Because I've never programmed to this Macintosh UI framework, I made a simplified UI for my first leap into the unknown. The end result looked like the picture on the right.

I now worked through the Fit workflow, adding Macintosh detail to each step. At first, I began each step with a spike so I could find out what kind of messages flow from the UI to my Views. (Yes, I could have read all the documentation - think of the spike as guiding me through the reference manuals.) For my UI's Views to be really thin, there has to be a one-to-one correspondence between messages from the UI and the messages flowing to the Presenter. For example, here's what happens when a user presses TAB or ENTER in a text field:


public void animalNameEntered(Object sender) { /* IBAction */
  myPresenter().animalNameEntered(animalNameField.stringValue());
}

As I learned more, I got bolder about unit-testing behavior into existence with a Mock View and only then making that behavior work with the real View. Sometimes I regretted that, discovering that my assumption about what the UI must do was wrong.

It was a little harder than usual to muster enthusiasm for the junit tests. For example, when I wanted to make the Presenter send a "highlight row 0" message to the table, I wrote a test that had the Presenter send that message to the Mock View and then make checks like these:

    assertTrue(inpatientView().wasARowHighlighted());
    assertEquals(0, inpatientView().getHighlightedRowIndex());

... and then I had to go make the Mock View remember the message was called so that my test could ask about it. Seems like a lot of work to drive one silly message from the View to the UI. (Perhaps a mock-builder would help?)

This bothers me because I have a principle that if programmers are finding testing annoying, that's a problem to fix. One way to fix it is to make the work provide value to new people.

Consider: I was working on the code that handles entering a patient name, entering an owner name, and clicking the "add case" button. At that point in implementation, it was natural for me to ask an imaginary UI designer about error cases. What should happen if you hit the button before typing in an animal name? What happens if you hit the button twice? Etc. "We" decided not to code up some of the error cases right away. But some we did. Suppose my designer had said that pressing the Add Case button without both fields filled in should produce an error. Here's a table that might record our conversation:

behavior when Add Case is pressed
animal field owner field   result? attention set to? animal field now has? owner field now has?
typed in typed in   new case added to case list case list, on the new case nothing nothing
typed in

left empty

  error owner field same as before "owner?", selected

left empty

typed in   error animal field "animal name?", selected same as before

left empty

left empty

  error animal field "animal name?", selected "owner name?"

The reason I like this is the reason I like being systematic. When you're constructing unit tests one at a time, it's easy to overlook a situation like both fields being empty. Suppose I'd only thought of the first three tests, in that order. My checking code would almost certainly first check for an empty owner name. So the behavior for that situation would also be the behavior for the situation where neither name was given. That's actually bad. Because the owner entry field follows the animal entry field, there's a fair chance the user would respond to the error by entering the owner name, hitting Enter, then getting annoyed by another error message. It's better for her to be directed to the first field in the tab order.

That particular example isn't a big deal, but sometimes the case you overlook is a very big deal indeed. By making a quick table, I not only increase the chance of thinking of all important cases, I also reduce the chance of overlooking a kind of result (like what gets highlighted).

Now, as it happens, my imaginary designer chose a different way to solve the problem. The Add Case button should be greyed out until both names are available. That behavior was driven by unit tests like these:

public void testCreationRequiresBothNames() {
    assertEquals(disallowed, inpatientView().getAddCaseCommands());

    inpatientView().animalNameEntered("animal data");
    assertEquals(disallowed, inpatientView().getAddCaseCommands());

    inpatientView().ownerNameEntered("owner data");
    assertEquals(nowAllowed, inpatientView().getAddCaseCommands());
}


public void testEnteringCaseErasesPreviousNames() {
    inpatientView().ownerNameEntered("owner");
    inpatientView().animalNameEntered("animal");
    inpatientView().addCasePressed();
    assertEquals(disallowedAgain, inpatientView().getAddCaseCommands());
    assertTrue(inpatientPresenter().hasNoAnimalName());
    assertTrue(inpatientPresenter().hasNoOwnerName());
    assertTrue(inpatientView().wasTheAnimalFieldNameEmptied());
    assertTrue(inpatientView().wasTheOwnerNameFieldEmptied());
}

So. I didn't think of making that table; I wrote jUnit tests instead. I'm tempted to keep coding. But the point of this exercise is not to produce a program, it's to learn stuff. So what I'll do is back up, write some Fit tests, and ask myself questions like these:

  • How much more painful is it to drive the code from those tests than from JUnit tests?
  • Is a conversation that turns into such Fit tests better for the customer than one that turns into JUnit tests?

(Note that throughout I'm assuming the hypothetical Good Fit Editor, one that makes creating and modifying tables as easy as creating and modifying a small RTF document with your editor of choice. We need a Good Fit Editor!)

As usual, you can find the current code in zip file. There are two separate projects - the core code and the Cocoa interface. I'd rather they were all in one directory structure, but I couldn't get IDEA and Xcode/IB to play nice together.

## Posted at 10:42 in category /fit [permalink] [top]

About Brian Marick
I consult mainly on Agile software development, with a special focus on how testing fits in.

Contact me here: marick@exampler.com.

 

Syndication

 

Agile Testing Directions
Introduction
Tests and examples
Technology-facing programmer support
Business-facing team support
Business-facing product critiques
Technology-facing product critiques
Testers on agile projects
Postscript

Permalink to this list

 

Working your way out of the automated GUI testing tarpit
  1. Three ways of writing the same test
  2. A test should deduce its setup path
  3. Convert the suite one failure at a time
  4. You should be able to get to any page in one step
  5. Extract fast tests about single pages
  6. Link checking without clicking on links
  7. Workflow tests remain GUI tests
Permalink to this list

 

Design-Driven Test-Driven Design
Creating a test
Making it (barely) run
Views and presenters appear
Hooking up the real GUI

 

Popular Articles
A roadmap for testing on an agile project: When consulting on testing in Agile projects, I like to call this plan "what I'm biased toward."

Tacit knowledge: Experts often have no theory of their work. They simply perform skillfully.

Process and personality: Every article on methodology implicitly begins "Let's talk about me."

 

Related Weblogs

Wayne Allen
James Bach
Laurent Bossavit
William Caputo
Mike Clark
Rachel Davies
Esther Derby
Michael Feathers
Developer Testing
Chad Fowler
Martin Fowler
Alan Francis
Elisabeth Hendrickson
Grig Gheorghiu
Andy Hunt
Ben Hyde
Ron Jeffries
Jonathan Kohl
Dave Liebreich
Jeff Patton
Bret Pettichord
Hiring Johanna Rothman
Managing Johanna Rothman
Kevin Rutherford
Christian Sepulveda
James Shore
Jeff Sutherland
Pragmatic Dave Thomas
Glenn Vanderburg
Greg Vaughn
Eugene Wallingford
Jim Weirich

 

Where to Find Me


Software Practice Advancement

 

Archives
All of 2006
All of 2005
All of 2004
All of 2003

 

Join!

Agile Alliance Logo