Wed, 30 Mar 2005
Design-Driven Test-Driven Design (Part 3)
For background,
see
the table of contents in the right
sidebar.
The story so far: the way methods have clustered in a Fit DoFixture
has persuaded me that I need to make some new classes. Heaven help
me, the image that comes to mind is viral particles budding off a
cell. (The image on the right shows flu viruses.) I also know that
I want to move toward the Model
View Presenter pattern, because it should let me get most of
the UI code safely under test. And I'm driving my work using
scripts / tests of the sort a User Experience (UX) designer might
write.
The first time I worked through this script, I created a single
class per method-cluster. For the methods that referred to the
"patient detail interaction context", I created a
PatientDetailContext class. The fixture would send it commands
like chooseToEnterANewRecord and query results with
methods like thePatientDetailContextNames .
The problem was that no test ever drove me to
split that one class into the View and Presenter classes. If you
think about it, that makes sense. In order to preserve design flexibility and keep detail from obscuring the
essence, the test script I'm working from is
deliberately vague about the details of the user interface. Since
Model-View-Presenter calls for the View to do little
more than relay
low-level GUI messages over to the Presenter, the programmatic
interface between the two (which I think of as like the mitotic
spindle in cell division - what's with me today?) has to name
a bunch of detail that the script leaves out. Where can that detail
come from?
I think it has to come from my knowledge of my end
goal. So in this do-over, I'll start by assuming such low-level
messages. But rather than jump into two classes right away, I'll
start by budding out the Presenter and have the DoFixture act as
the View (the self-shunt
pattern).
Let's take two steps in the script to work from:
choose patient
|
Betsy
|
with owner
|
Rankin
|
now
|
the
|
patient detail
|
context names
|
Betsy
|
|
Here would be the object structure and pattern of communication for
the first line:
A portion of the DoFixture acts as a kind of mock View for the Inpatient
Presenter, pretending to respond to user actions. Its implementation
of choosePatientWithOwner looks like this:
public void choosePatientWithOwner(String animalName, String ownerName) {
inpatientPresenter.setAnimalNameText(animalName);
inpatientPresenter.setOwnerNameText(ownerName);
inpatientPresenter.pressChoosePatient();
}
|
Those look rather like UI messages. The Inpatient Presenter knows
that when "Choose Patient" is clicked, it should tell the Patient
Detail Presenter about it. The Patient Detail Presenter in turn has
to update its View. Its View is, again, the DoFixture. So the Presenter calls a
new method on the DoFixture, setPatientName :
// patient detail view
private String patientName;
public void setPatientName(String patientName) { // called by presenter
this.patientName = patientName;
}
|
Now when the next test line claims that "now the patient
detail context names Betsy", Fit calls a query method that returns
the right result:
public String thePatientDetailContextNames() {
return patientName;
}
|
That works (given the right code in the presenters). Having gotten
green, I can now refactor. There are two uglinesses in this code.
Here's the top of the file that declares
the InpatientPresenter :
package com.testingthought.humble.ui;
import com.testingthought.humble.fixtures.dofixtures.InteractionDesign;
|
The Presenter isn't a testing class - it lives with other UI classes
- but it depends on a testing class.
That's easily fixed by creating an
interface InpatientView that's in the UI package,
having the Presenter depend on it, and having the DoFixture
implement it.
-
The DoFixture is now talking about detail like text entry fields
and button presses. That kind of thing obscures what I think an
interaction design
DoFixture ought to be: a facade over a
collection of cooperating presenters and views. It shouldn't do
work itself - it should just tell other objects to do work.
(In general, I'm starting to think of DoFixtures as adapters that translate
between object-speak and procedural-speak. In procedural-speak,
you don't want to manage specific pointers to objects. Instead, you
want to refer to them in a looser way. For example, sometimes you
want to refer
to them implicitly, trusting the reader or DoFixture to know which
one you meant.)
By an amazing stroke of luck, I can now extract the View-ish methods
into full-fledged mock View objects that implement my new
interface. They will:
take interaction design actions and turn them into GUI-level
messages to the Presenter.
accumulate GUI-level messages from the Presenter and remove
detail irrelevant to an interaction design.
Like this:
I won't show the code itself, but you can find it in a
zip
file. As before, the build.xml file in the top
level runs the Fit tests. To read it, start with the
DoFixture's choosePatientWithOwner method and trace
through the calls.
We've now achieved the View/Presenter separation. The mock Views do
conversions. The "from" part of the conversion comes from the Fit
tests, but where does the "to" part of the conversion come from?
What tests directly drive the the decision about what messages to send
from Views to Presenters? Well, what always drives the coding of
classes distant from the "top" of the system? - unit tests.
That's kind of an interesting inversion. Usually, we think of the
code that handles UI widgets as being on the outside of the system,
the part closest to the user. But here that code is utility code,
not really different from database access code. By burying utility code
behind a layer (be it a persistence layer or a DoFixture derived
from an interaction design), we let people thinking about larger
issues ignore the grotty technology that lies behind the business
value of the system.
Next: interlacing Views and mock Views. Plus, I finally get around
to learning to program Cocoa.
## Posted at 08:21 in category /fit
[permalink]
[top]
|