Tag Archives: intro

Refactoring Demo Screencast

Four ways to Extract Method.

Refactoring is "improving the design of existing code". We want to do this quickly and safely, so that we're improving the code's design but not introducing bugs while we change it.

This series of videos shows four different ways of performing the refactoring "Extract Method":

The code we're improving is part of a program that lets a user unscramble sentence fragments. The part in bold below shows the code we're going to extract.

public class Board extends JPanel implements PropertyChangeListener {    
    // ...
    
    public String toString() {
        Component[] cards = this.getComponents();
        List sortedResult = Arrays.asList(cards);
        Collections.sort(sortedResult, new BoardComparator()); 
        
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < sortedResult.size(); i++)
            result.append(sortedResult.get(i).toString());
        
        return result.toString();            

    }
}
  1. Sloppy (Flash, 0.9M)
  2. Manual, but "by the book" (Flash, 1.1M)
  3. Automated, using the IDE's refactoring support (Flash, 0.9M)
  4. Automated, at full speed (Flash, 0.5M)

Sloppy

Manual

Automated

Automated, At Speed

[Recorded May, 2006. Tools: Eclipse, Macromedia Captivate, Audacity.]

Scrum Development on a Page

Scrum is an agile software process, centered around a self-organizing team and a 30-day development cycle. This page contains a one-page summary of its development process.

Scrum on a page. (Microsoft Word .doc format, Adobe .PDF format)

 

Related Articles: Extreme Programming on a Page.

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.

[Revised July, 2005. Previous version developed January, 2004. PDF format courtesy of Eric Chamberlain.]

Older version (2004) still available: PDF, DOC

Bill Wake is the author of the Refactoring Workbook, and Extreme Programming Explored. He works as a software coach and trainer. Contact him at William.Wake@acm.org or 1-804-519-6799.

XP on One Page

This mini-poster serves as a reminder about most aspects of XP.

Poster: XP on One Page (PDF)

Thanks to Laurent Bossavit, Alex Chaffee, George Dinwiddie, Alan Francis, Ed Hughes, Ron Jeffries, Paul Moore, Ilja Preuss, and Bob Worth. I appreciated their suggestions even if I didn’t always follow their advice. I’m certain I forgot others who wrote – thank you too!

Special thanks to Becky Kuhlman, who did the graphic design.

[Written February, 2002; revised March 26, 2002.]

The Test/Code Cycle in XP, Part 1: Model

This paper demonstrates the development of a small bibliographic system using Extreme Programming techniques. Part 1 shows development of the model; part 2 shows development of the associated user interface.

Specifically, it shows how unit tests and simple design work together while programming. Observe how the coding process occurs in small steps, just enough implementation to make each test run. There's a rhythm to it, like a pendulum of a clock: test a little, code a little, test a little, code a little. (To bring this out, we'll align test code to the left of the page, and application code to the right.)

For this example, suppose we have bibliographic data with author, title, and year of publication. Our goal is to write a system that can search that information for values we specify. We have in mind an interface something like this:

A search interface

We'll divide our work into two parts: the model and the user interface.

For our model, we have a collection of Documents, which know their metadata. A Searcher knows how to find Documents: given a Query, it will return a Result (a set of Documents). We'll create our unit tests (and our classes) bottom-up: Document, Result, Query, and Searcher.

Document, Result, and Query

Document

A Document needs to know its author, title, and year. We'll start with a "data bag" classs, beginning with its test:

public void testDocument() {
    Document d = new Document("a", "t", "y");
    assertEquals("a", d.getAuthor());
    assertEquals("t", d.getTitle());
    assertEquals("y", d.getYear());
}

This test doesn't compile (as we haven't created the Document class yet). Create the class with stubbed-out methods.

Run the test again to make sure it fails. This may seem funny – don't we want the tests to pass? Yes, we do. But by seeing them fail first, we get some assurance that the test is valid. And once in a while, a test passes unexpectedly: "that's interesting!"

Fill in the constructor and the methods to make the test pass.

Let's highlight this mini-process:

The Test/Code Cycle in XP

  • Write one test.
  • Compile the test. It should fail, as you haven't implemented anything yet.
  • Implement just enough to compile. (Refactor first if necessary.)
  • Run the test and see it fail.
  • Implement just enough to make the test pass.
  • Run the test and see it pass.
  • Refactor for clarity and "once and only once".
  • Repeat from the top.

This process ensures that you've seen the test both fail and pass, which gives you assurance that the test did test something, that your change made a difference, and that you've added valued functionality.

Some people will advocate not bothering to test simple setter and getter methods. ("They can't possibly be wrong.", "It's a pain to write a bunch of getter/setter tests.") I tend to write the tests anyway:

  • It doesn't take that much time to write the test, and it's certainly not hard, but it gives you that extra edge. ("You thought you were sure it's ok; now you have a test that demonstrates it.")
  • A test will often have a longer lifetime than the code it's testing. The test is there so when you add caching, or don't create objects until required, or add logging, etc., you still have assurance that the original function will work.
  • Boring tests full of setters and getters are often trying to tell you something: the class may not be pulling its weight. When a class is almost a "struct", it's often a sign that the responsibilities aren't distributed right between classes.

Result

A Result needs to know two things: the total number of items, and the list of Documents it contains. First we'll test that an empty result has no items.

public void testEmptyResult() {
    Result r = new Result();
    assert ("count=0 for empty result", r.getCount() == 0);
}

Create the Result class and stub out its getCount() method. See it fail until you add "return 0;" as its implementation.

Next test a result with two documents.

public void testResultWithTwoDocuments() {
    Document d1 = new Document("a1", "t1", "y1");
    Document d2 = new Document("a2", "t2", "y2");
    Result r = new Result(new Document[]{d1, d2});
    assert (r.getCount() == 2);
    assert (r.getItem(0) == d1);
    assert (r.getItem(1) == d2);
}

Add the getItem() method (returning null) and watch the test fail. (I'm going to stop mentioning that, but keep doing it. It takes a few seconds, but gives that extra bit of reassurance.) Implementing a simple version of Result will give:

 
public class Result {
    Document[] collection = new Document[0];

    public Result() {}

    public Result(Document[]collection) {
        this.collection = collection;
    }

    public int getCount() {return collection.length;}

    public Document getItem(int i) {return collection[i];}
}

The test runs, so we're done with this class.

Query

We'll represent the Query as just its query string.

public void testSimpleQuery() {
    Query q = new Query("test");
    assertEquals("test", q.getValue());
}

Create the Query class with a constructor, so that it remembers its query string and reports it via getValue().

Searcher

The Searcher is the most interesting class. The easy case is first: we should get nothing back from an empty collection of Documents.

public void testEmptyCollection() {
    Searcher searcher = new Searcher();
    Result r = searcher.find(new Query("any"));
    assert(r.getCount() == 0);
}

This test doesn't compile, so stub out the Searcher class.

 
public class Searcher {
    public Searcher() {}
    Result find(Query q) {return null;}
}

The test compiles, but fails to run correctly (because find() returns null). We can fix this with this change: "public Result find(Query q) {return new Result();}".

Things get more interesting when we try real searches. Then we face the issue of where the Searcher gets its documents. We'll begin by passing an array of Documents to the Searcher's constructor. But first, a test.

public void testOneElementCollection() {
    Document d = new Document("a", "a word here", "y");
    Searcher searcher = new Searcher(new Document[]{d});

    Query q1 = new Query("word");
    Result r1 = searcher.find(q1);
    assert(r1.getCount() == 1);

    Query q2 = new Query("notThere");
    Result r2 = searcher.find(q2);
    assert (r2.getCount() == 0);
}

This test shows us that we have to find what is there, and not find what's not there.

To implement this, we have to provide the new constructor that makes the test compile (though it still fails). Then we have to get serious about implementation.

First, we can see that a search has to retain knowledge of its collection between calls to find(), so we'll add a member variable to keep track, and have the constructor remember its argument:

 
Document[] collection = new Document[0];

public Searcher(Document[] docs) {
    this.collection = docs;
}

Now, the simplest version of find() can iterate through its documents, adding each one that matches the query to a Result:

 
public Result find(Query q) {
    Result result = new Result();
    for (int i = 0; i < collection.count; i++) {
        if (collection[i].matches(q)) {
            result.add(collection[i]);
        }
    }
    return result;
}

This looks good, except for two problems: Document has no matches() method, and Result has no add() method.

Let's add a test: we'll check that each field can be matched, and that a document doesn't match queries it shouldn't:

public void testDocumentMatchingQuery() {
    Document d = new Document("1a", "t2t", "y3");
    assert(d.matches(new Query("1")));
    assert(d.matches(new Query("2")));
    assert(d.matches(new Query("3")));
    assert(!d.matches(new Query("4")));
}

There are three situations for queries that we should deal with eventually: empty queries, partial matches, and case sensitivity. For now, we'll assume empty strings and partial matches should match, and the search is case-sensitive. In the future we might change our mind.

This is enough information to let us implement matches:

 
public boolean matches(Query q) {
    String query = q.getValue();

    return
       author.indexOf(query) != -1
    || title.indexOf(query) != -1
    || year.indexOf(query) != -1;
}

This will enable testDocumentMatchingQuery() to work, but testOneElementCollection() will still fail, because Result has no add() method yet. So, add a test for the method Result.add():

public void testAddingToResult() {
    Document d1 = new StringDocument("a1", "t1", "y1");
    Document d2 = new StringDocument("a2", "t2", "y2");

    StringResult r = new StringResult();
    r.add(d1);
    r.add(d2);

    assert ("2 items in result", r.getCount() == 2);
    assert ("First item",  r.getItem(0) == d1);
    assert ("Second item", r.getItem(1) == d2);
}

This test fails. Result already remembers its list by using an array, but that is not the best choice for a structure that needs to change its size. We'll change to use a Vector:

 
Vector collection = new Vector();

public Result(Document[] docs) {
    for (int i = 0; i < docs.length; i++) {
        this.collection.addElement(docs[i]);
    }
}

public int getCount() {return collection.size();}

public Document getItem(int i) {
    return (Document)collection.elementAt(i);
}

Make sure the old unit tests testEmptyResult() and testResultWithTwoDocuments() still pass. Add the new method:

 
public void add(Document d) {
    collection.addElement(d);
}

Let's consider the the new Result(Document[]) constructor. It was introduced to support the testResultWithTwoDocuments() test, because it was the only way we could create Results containing documents. Later, we introduced Result.add(), which is what the Searcher needs. The array constructor is no longer needed. So, we'll put on a testing hat and revise that test. Instead of Result r = new Result(new Document[]{d1,d2});, we'll use:

Result r = new Result();
r.add(d1);
r.add(d2);

We verify that all tests still pass, so it is now safe to remove the array-based constructor. We also see that testAddingToResult() is now essentially a duplicate of testResultWithTwoDocuments(), so we'll remove the latter.

Finally, all our tests pass for Document, Result, Query, and Searcher.

Initialization

Loading Documents

Where does a searcher get its documents? Currently, you'd call its constructor from the main routine, passing in an array of documents. Instead, we want the searcher to own the process of loading its documents.

We begin with a test. We'll pass in a Reader, and be prepared to see exceptions. We've also postulated a getCount() method, used only by tests to verify that something was loaded. An advantage of having the tests in the same package as the class under test is that you can provide non-public methods that let tests view an object's internal state.

public void testLoadingSearcher() {
    try {
        String docs = "a1\tt1\ty1\na2\tt2\ty2"; // \t=field, \n=row
        StringReader reader = new StringReader(docs);
            Searcher searcher = new Searcher();
            searcher.load(reader);
            assert("Loaded", searcher.getCount() == 2);
    } catch (IOException e) {
        fail ("Loading exception: " + e);
    }
}

Notice that Searcher still uses an array (the simplest choice at the time). We'll do as we did for Result, a refactoring converting from an array to a Vector.

 
package search;

import java.util.*;

public class Searcher {
    Vector collection = new Vector();

    public Searcher() {}

    public Searcher(Document[] docs) {
        for (int i = 0; i < docs.length; i++) {
            collection.addElement(docs[i]);
        }
    }

    public Query makeQuery(String s) {
        return new Query(s);
    }

    public Result find(Query q) {
        Result result = new Result();
        for (int i = 0; i < collection.size(); i++) {
            Document doc = (Document)collection.elementAt(i);
            if (doc.matches(q)) {
                result.add(doc);
            }
        }
        return result;
    }
}

(Verify that the old tests pass.) Now we're in a position to do the loading:

 
// Searcher:
public void load(Reader reader) throws IOException {
    BufferedReader in = new BufferedReader(reader);
    try {
        String line = in.readLine();
        while (line != null) {
            collection.addElement(new Document(line));
            line = in.readLine();
        }
    } finally {
        try {in.close();} catch (Exception ignored) {}
    }
}

int getCount() {
    return collection.size();
}
// Document:
public Document(String line) {
    StringTokenizer tokens = new StringTokenizer(line, "\t");
    author = tokens.nextToken();
    title = tokens.nextToken();
    year = tokens.nextToken();
}

Searcher's array-based constructor is no longer needed. We'll adjust the test and delete the constructor:

public void testOneElementCollection() {
    Searcher searcher = new Searcher();
    try {
        StringReader reader = new StringReader("a\ta word here\ty");
        searcher.load(reader);
    } catch (Exception ex) {
        fail ("Couldn't load Searcher: " + ex);
    }

    Query q1 = searcher.makeQuery("word");
    Result r1 = searcher.find(q1);
    assert(r1.getCount() == 1);

    Query q2 = searcher.makeQuery("notThere");
    Result r2 = searcher.find(q2);
    assert (r2.getCount() == 0);
}

SearcherFactory

Where does a Searcher come from? Currently, that's left up to whoever calls its constructor. Instead of letting clients depend on the constructor, we'd like to introduce a factory method responsible for locating the Searcher. (For the test, we'll put a file "test.dat" in the directory for testing. If we wanted to be less lazy, we'd have the test create and delete the file as well.)

public void testSearcherFactory() {
    try {
        Searcher s = SearcherFactory.get("test.dat");
        assert (s != null);
    } catch (Exception ex) {
        fail ("SearcherFactory can't load: " + ex);
    }
}

We can implement:

 
public class SearcherFactory {
    public static Searcher get(String filename) throws IOException {
        FileReader in = new FileReader(filename);
        Searcher s = new Searcher();
        s.load(in);
        return s;
    }
}

Now, a client obtains a Searcher by asking a SearcherFactory to give it one.

Looking Back

I'd like to put a design hat on, and look at the methods we've developed, from two perspectives: the search client and the Searcher class. Who uses each public method?

Search Client Searcher class
Document.getAuthor()
Document.getTitle()
Document.getYear()
new Query()
Result.getCount()
Result.getItem()
Searcher.find()
new Document()
Document.matches()
Query.getValue()
new Result()
Result.add()

Looking at the Document and Query classes, I still have twinges that say they may not be doing enough (being not much more than a "data bag"). But both seem like good, meaningful "near-domain" classes, so we'll hold off on any impulse to change them. The Result and Searcher classes feel like they have the right balance.

What about the development process? It seemed to generate some blind alleys. For example, we had to change data structures from arrays to vectors (twice!). Is this a flaw in our process? No, it's not. The array was an adequate structure when it was introduced, and it was changed when necessary. We don't mind blind alleys, as long as they're never one-way dead ends. We're not omniscient, so there will be times we need to change our minds; the key is making sure we never get stuck with a bad or over-complex design.

Moving Forward: Interfaces

The implementation we've derived above is a good starting point, but is not in final form. In real systems, the bibliographic information is often kept elsewhere, perhaps in a database, XML file, on another network, etc. We don't want our clients to know which alternative they're using.

The methods in the "Search Client" column of the table above show the interfaces required by clients. "Query" is probably OK as a class (since clients have to be able to construct them), but we would like to introduce interfaces for Searcher, Result, and Document. We'll apply the "Extract Interface" refactoring (from Fowler's book).

Unfortunately, the names we'd like for our interfaces are the same as the ones we already use for the classes. Since we'd like things to be better from the client point of view, and the classes so far are based on strings, we'll rename Searcher to StringSearcher, etc. and reserve the shorter names for the interfaces.

So, move Searcher.java to StringSearcher.java. Fix every call site and reference. Run the tests to verify that we've renamed correctly.

Introduce the interface:

 
public interface Searcher {
    public Result find(Query q);
}

(Run the tests.) Make StringSearcher implement the interface. (Run the tests.) Now, the only place that must reference StringSearcher by name is the SearcherFactory interface. (We could remove that dependency, and perhaps also put the String* objects in a different package, but we won't do that here for reasons of space.)

Apply the same process to Result, renaming the old Result to StringResult, and introducing the interface:

 
public interface Result {
    public Document getItem(int i);
    public int getCount();
}

The StringSearcher class should still construct a StringResult object, but its return type should remain Result. (We don't mind if the String* classes depend on each other, but we don't want to make clients aware of that fact.)

Finally, introduce the interface for Document:

 
public interface Document {
    public String getAuthor();
    public String getTitle();
    public String getYear();
}

We're left with two concrete classes that clients will depend on: SearcherFactory and Query. Clients depend on the interfaces for Searcher, Result, and Document, but not directly on the classes implementing those interfaces.

Conclusion

We've developed the bibliographic system's model in a typical Extreme Programming style, emphasizing simple design and a process of alternately testing and coding. The unit tests supported us in designing, coding, and refactoring. The resulting system could have either a simple command line or a graphical user interface attached to it.

Resources and Related Articles

Translations

[Written 1-25-2000; re-titled and revised 2-3-2000; added search.zip 7-2-00.]

The Test/Code Cycle in XP, Part 2: GUI

People who unit-test, even many who unit-test in Extreme Programming, don't necessarily test the user interface. You can use JUnit to assist in this testing, however. This paper will work through a small but plausible example, giving the flavor of testing and programming using JUnit. This paper is part 2, but can be read on its own; part 1 developed the model.

Example

Suppose we're developing a simple search engine. We'd like the user interface to look something like this:

Search system

We'll develop it in the XP style, working back and forth between testing and coding. The code fragments will reflect this: tests will be on the left side of the page, and application code on the right.
 
 

Model First

When you're creating a GUI (graphical user interface), you should develop and test the model first. We'll assume this has been done, and that it has the following interface:

 
public class SearcherFactory {
    public static Searcher get(String s) throws IOException {...}
}

public interface Searcher {
    public Result find(Query q);
}

public class Query {
    public Query(String s) {...}
    public String getValue() {...}
}

public interface Result {
    public int getCount();
    public Document getItem(int i);
}

public interface Document {
    public String getAuthor();
    public String getTitle();
    public String getYear();
}

In testing and development of the GUI, I don't mind depending on the interfaces of the model; I'm less happy when I have to depend on its concrete classes.

The GUI Connection

What we'd like to happen:

  • a searcher is associated with the GUI
  • a query is entered
  • the button is clicked
  • the table fills up with the result

We want to make this happen and unit-test the result.
 

Testing Key Widgets

We proposed a screen design earlier. The first thing we can test is that key widgets are present: a label, a query field, a button, and a table. There may be other components on the panel (e.g., sub-panels used for organization), but we don't care about them.

So, we'll create testWidgetsPresent(). To make this work, we need a panel for the overall screen ("SearchPanel"), the label ("searchLabel"), a textfield for entering the query ("queryField"), a button ("findButton"), and a table for the results ("resultTable"). We'll let these widgets be package-access, so our test can see them.

public void testWidgetsPresent() { 
    SearchPanel panel = new SearchPanel(); 
    assertNotNull(panel.searchLabel); 
    assertNotNull(panel.queryField); 
    assertNotNull(panel.findButton); 
    assertNotNull(panel.resultTable); 
}

The test fails to compile. (Of course – we haven't created SearchPanel yet.) So, create class SearchPanel with its widget fields, so we can compile. Don't initialize the widgets yet – run the test and verify that it fails. (It's good practice to see the test fail once; this helps assure you that it captures failures, and lets you ensure that the testing is driving the coding.) Code enough assignments to make the test pass.

Things to notice:

  • The test helped design the panel's (software) interface.
  • The test is robust against even dramatic re-arrangements of the widgets.
  • We took very small steps, bouncing between test, code, and design.
  • Our panel might not (and in fact, does not) actually display anything – we haven't tested that.
  • The panel still doesn't do anything (e.g., if the button were clicked).

We can make another test, to verify that the widgets are set up correctly:

public void testInitialContents() {
    SearchPanel sp = new SearchPanel();
    assertEquals("Search:", sp.searchLabel.getText());
    assertEquals("", sp.queryField.getText());
    assertEquals("Find", sp.findButton.getText());
    assert("Table starts empty", sp.resultTable.getRowCount() == 0);
}

Run this test, and we're ok.

At this point, our SearchPanel code looks like this:

 
public class SearchPanel extends JPanel {
    JLabel searchLabel = new JLabel("Search:");
    JTextField queryField = new JTextField();
    JButton findButton = new JButton("Find");
    JTable resultTable = new JTable();

    public SearchPanel() {}
}

We could go in either of two directions: push on developing the user interface, or its interconnection with searching. The urge to "see" the interface is strong, but we'll resist it in favor of interconnection.

Testing Interconnection

Somehow, we must associate a Searcher with our GUI, and verify that we display its results.

We'll give our panel two methods, getSearcher() and setSearcher(), that will associate a Searcher with the panel. This decision lets us write another test:

public void testSearcherSetup() { 
    Searcher s = new Searcher() { 
        public Result search(Query q) {return null;} 
    }; 

    SearchPanel panel = new SearchPanel(); 
    assert ("Searcher not set", panel.getSearcher() != s); 
    panel.setSearcher(s); 
    assert("Searcher now set", panel.getSearcher() == s); 
}

The compile fails, so bounce over to SearchPanel, add the methods, run the tests again, they fail; implement the set/get methods, and the test passes.

The panel still can't do much, but now we can associate a Searcher with it.

Testing with a Fake Searcher

A search returns a set of results. When something returns a list of values, I'm always interested to see how it will behave when it returns 0, 1, or an arbitrary number.

Because this is a unit test, I don't want to depend on the real Searcher implementations: I'd rather create my own for testing purposes. This lets me control behavior in a fine-grained way. Here, I'll create a new Searcher called TestSearcher. We'll have the query string be an integer, which will tell how many items to return. We'll name the items "a0" (for first author), "t1" (second title), etc.

But first… a test. (Notice this is a test of our testing class, not of our GUI.)

public void testTestSearcher() { 
    assertEquals(new Query("1").getValue(), "1"); 
  
    Document d = new TestDocument(1);
    assertEquals("y1", d.getYear());

    Result tr = new TestResult(2); 
    assert(tr.getCount() == 2); 
    assertEquals("a0", tr.getItem(0).getAuthor());

    TestSearcher ts = new TestSearcher(); 
    tr = ts.find(ts.makeQuery("2"));
    assert("Result has 2 items", tr.getCount() == 2); 
    assertEquals("y1", tr.getItem(1).getYear());
}

Go through the usual compile/fail cycle, and create the test classes, starting with TestDocument:

public class TestDocument implements Document { 
    int count; 
    public TestDocument(int n) {count = n;} 
    public String getAuthor() {return "a" + count;}
    public String getTitle() {return "t" + count;} 
    public String getYear() {return "y" + count;} 
}

The TestResult class has a constructor that takes an integer telling how many rows should be present:

public class TestResult implements Result { 
    int count; 
    public TestResult(int n) {count = n;} 
    public int getCount() {return count;} 
    public Document getItem(int i) {return new TestDocument(i);}
}

TestSearcher uses the number value of the query string to create the result:

public class TestSearcher implements Searcher { 
    public Result find(Query q) { 
        int count = 0; 
        try {count = Integer.parseInt(q.getValue());} 
        catch (Exception ignored) {} 
        
        return new TestResult(count); 
    } 
}

Run the test again, and it passes.

0, 1, Many

We'll build tests for the 0, 1, and many cases:

public void test0() { 
    SearchPanel sp = new SearchPanel(); 
    sp.setSearcher (new TestSearcher()); 
    sp.queryField.setText("0"); 
    sp.findButton.doClick(); 
    assert("Empty result", sp.resultTable.getRowCount() == 0); 
}

At last, we're using the GUI: setting text fields, clicking buttons, etc.

We run the test – and it passes! This means we already have a working solution – if our searcher always returns 0 items.

We move on:

public void test1() { 
    SearchPanel sp = new SearchPanel(); 
    sp.setSearcher (new TestSearcher()); 
    sp.queryField.setText("1"); 
    sp.findButton.doClick(); 

    assert("1-row result", sp.resultTable.getRowCount() == 1); 
    assertEquals(
        "a0", 
        sp.resultTable.getValueAt(0,0).toString());
}

Now we fail, because we don't have any event-handling code on the button.

When the button is clicked, we want to form the string in the text field into a query, then let the searcher find us a result we can display in the table. However, we have a problem in matching types: the Searcher gives us a Result, but the table in our GUI needs a TableModel. We need an adapter to make the interfaces conform.
 

Record our Mental Stack

We have several things in progress at the same time, so it's a good time to review them – and write them down – so we don't lose track of anything.

  • Write the button code
  • Test and develop a TableModel adapter
  • Get test1() to pass
  • Write testN() and get it to pass
  • Test the "look" of the GUI

Adapter Implementation

Let's write the button code as if a ResultTableAdapter class existed:

 
findButton.addActionListener(new ActionListener() { 
    public void actionPerformed(ActionEvent e) { 
        Query q = new Query(queryField.getText()); 
        
        resultTable.setModel(
            new ResultTableAdapter(getSearcher().find(q)));
    } 
});

When this fails to compile, stub out a dummy implementation:

 
public class ResultTableAdapter extends DefaultTableModel {
    public ResultTableAdapter(Result r) {}
}

Test0() still passes, and test1() still fails.

The adapter is straightforward to write, but we begin by writing a test.

public void testResultTableAdapter() { 
    Result result = new TestResult(2); 
    ResultTableAdapter rta = new ResultTableAdapter(result); 
    assertEquals("Author", rta.getColumnName(0)); 
    assertEquals("Title", rta.getColumnName(1)); 
    assertEquals("Year", rta.getColumnName(2)); 
    assert("3 columns", rta.getColumnCount() == 3);

    assert("Row count=2", rta.getRowCount() == 2); 
    assertEquals("a0", rta.getValueAt(0,0).toString()); 
    assertEquals("y1", rta.getValueAt(1,2).toString());
}

The test fails because the dummy implementation doesn't do anything.

Bounce over and implement the ResultTableAdapter. Change it to be a subclass of AbstractTableModel (instead of DefaultTableModel), then implement the column names, column and row counts, and finally getValueAt().

 
public class ResultTableAdapter 
        extends AbstractTableModel implements TableModel { 
    final static String columnNames[] = {"Author", "Title", "Year"};
    Result myResult;

    public ResultTableAdapter(Result r) {myResult = r;} 

    public String getColumnName(int i) {return columnNames[i];}

    public int getColumnCount() {return columnNames.length;}

    public int getRowCount() {return myResult.getItemCount();}

    public Object getValueAt(int r, int c) {
        Document doc = myResult.getItem(r);

        switch(c) {
        case 0: return doc.getAuthor();
        case 1: return doc.getTitle();
        case 2: return doc.getYear();
        default: return "?";
        }
    }
}

This test (testResultTableAdapter) should pass, and so should test1().
 

TestN and More

Write testN(), with say 5 items. It will also pass.

What else can give you problems? One possible problem occurs when we do a sequence of queries – can we get "leftovers"? For example, a query returning 5 items followed by a query returning 3 items should only have 3 items in the table. (If the table were improperly cleared, we might see the last two items of the previous query.)

We can test a sequence of queries:

public void testQuerySequenceForLeftovers() { 
    SearchPanel sp = new SearchPanel(); 
    sp.setSearcher (new TestSearcher()); 

    sp.queryField.setText("5"); 
    sp.findButton.doClick(); 
    assert(sp.resultTable.getRowCount() == 5); 
 
    sp.queryField.setText("3"); 
    sp.findButton.doClick(); 
    assert(sp.resultTable.getRowCount() == 3); 
}

This test passes.
 

Testing for Looks

We have a properly connected panel. We can check the widgets' relative locations:

  • label left-of queryField
  • queryField left-of findButton
  • queryField above table

(Would we bother with these tests? Perhaps not, we might just put the panel on-screen and deal with its contents manually. There are times when such tests would definitely be appropriate: perhaps when we're working against a style guide, or when the window format is expected to be stable.)

To make this test run, we need to put our panel in a frame or window. (Components don't have their screen locations set until their containing window is created.)

public void testRelativePosition() {
    SearchPanel sp = new SearchPanel();

    JFrame display = new JFrame("test");
    display.getContentPane().add(sp);
    display.setSize(500,500);
    display.setVisible(true);

    //try {Thread.sleep(3000);} catch (Exception ex) {}

    assert ("label left-of query", 
          sp.searchLabel.getLocationOnScreen().x 
        < sp.queryField.getLocationOnScreen().x);
        
    assert ("query left-of button", 
          sp.queryField.getLocationOnScreen().x 
        < sp.findButton.getLocationOnScreen().x);
        
    assert ("query above table", 
          sp.queryField.getLocationOnScreen().y 
        < sp.resultTable.getLocationOnScreen().y);
}

The test fails, as we haven't done anything to put widgets on the panel. (You can un-comment the sleep() if you want to see it on-screen.)

To implement panels, I usually do a screen design that shows the intermediate panels and layouts:

Now we can lay out the panel:

 
public SearchPanel() {
    super (new BorderLayout());

    findButton.addActionListener(new ActionListener() { 
        public void actionPerformed(ActionEvent e) { 
            Query q = new Query(queryField.getText()); 
            resultTable.setModel(
                new ResultTableAdapter(getSearcher().find(q))); 
        } 
    }); 

    JPanel topPanel = new JPanel(new BorderLayout());
    topPanel.add(searchLabel, BorderLayout.WEST);
    topPanel.add(queryField, BorderLayout.CENTER);
    topPanel.add(findButton, BorderLayout.EAST);

    this.add(topPanel, BorderLayout.NORTH);
    this.add(new JScrollPane(resultTable), BorderLayout.CENTER);
}

Compile, test, and it works.

We've successfully implemented our panel!

Main

To complete the system, we'll create a main() routine:

 
public class Main {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.err.println(
                "Arg - file w/tab-delimited author/title/year");
            System.exit(1);
        }

        Searcher searcher = null;
        try {
            searcher = SearcherFactory.get(args[0]);
        } catch (Exception ex) {
            System.err.println(
                "Unable to open file " + args[0] + "; " + ex);
            System.exit(1);
        }
        
        SearchPanel sp = new SearchPanel();
        sp.setSearcher(searcher);

        JFrame display = new JFrame("Bibliographic System - " + args[0]);
        display.getContentPane().add(sp);
        display.setSize(500,500);
        display.setVisible(true);
    }
}

Conclusions

We've completed development of our user interface. Not every aspect of a GUI can be unit-tested through the approach we've used, but we've identified a number of useful techniques:

  • Even GUI development can maintain the cycle-in-the-small of test-code-design.
  • GUI tests can be robust against changes in how the widgets are arranged on-screen.
  • Fields and buttons can be simulated with getText(), setText(), doClick(), etc.
  • Stub out the services provided by the model, to get fine-grained control over what the GUI test shows.
  • We can test relative positioning using getLocationOnScreen().

Unit tests can be tedious to write, but they save you time in the future (by catching bugs after changes). Less obviously, but just as important, is that they can save you time now: tests focus your design and implementation on simplicity, they support refactoring, and they validate features as you develop.

Resources and Related Articles

Translations

[Written 1-3-2000; revised 2-1-2000; re-titled and revised 2-4-2000. Linked to xp0001.zip, 10-26-2000.]

Introduction to Extreme Programming (XP)

Background

According to XP, the cost structure of software development has changed over the last 30 years.

Exponential cost increase or not?

When costs grow exponentially, it makes to spend money now to avoid big costs later. When they’re growing more slowly, it can make sense to avoid spending money.

Paying for design up-front creates early costs. Extreme Programming advocates more of a "pay-as-you-go" approach.

As the diagram suggests, costs tend to remain lower for longer in pay-as-you-go. Notice the area labeled "x" in the diagram: this represents the "extra" costs you pay. These costs can be even higher than you might think: the cost of programming to an up-front design can be higher, as you have to deal with more complexity earlier on in the programming. Also, the design will guess wrong about some things, putting in too much complexity in some places, and not enough in others.

Principles and Practices

XP is based on several principles:

  • Rapid feedback
  • Assume simplicity
  • Incremental change
  • Embrace change
  • Quality work

The principles guide the practices used by XP teams:

  • Planning Game
  • Small releases
  • Metaphor
  • Tests
  • Pair programming
  • Refactoring
  • Simple design
  • Collective ownership
  • Continuous integration
  • Open workspace
  • 40-hour week

We’ll focus on a few key practices here.

Planning Game

The Planning Game is based around the idea of user stories, a lightweight form of use cases. These are small stories (2-3 sentence) that a customer cares about, that can be reasonably tested, and that can be estimated and prioritized. (The customer is on-site during development, so they can provide more details as the team needs them.)

The Planning Game helps the user and developers prioritize stories. The user gets to add stories as needed, and splits stories that are too big to easily estimate.

Tests

There are two major types of tests in XP: unit tests and functional tests.

Unit tests are created by the developer, before and during programming. Developers have a responsibility to ensure that unit tests always run at 100%. These tests are used to ensure that development and refactoring make "forward progress".

Functional tests are specified by the user, then estimated and implemented by the developers (or an explicit test team). These tests will typically be run one or more times each day, as people integrate their work of the last few hours. The functional tests are what let the customer know there is true progress. Because they’re developed throughout the process (not at the end), functional tests also act as part of the specification.

Simple Design

In XP, the guiding mottos are "the simplest thing that could possibly work" and "you aren’t going to need it". These encourage fine-grained design, aided by refactoring.

Sometimes, an aspect of the design is too complex to know exactly what "could possibly" work, so an XP team will do a "spike" (a quick but narrow implementation) to help make the decision.

Pair Programming

All production code is written by a pair. (One person has direct responsibility for the task, but will work with someone else to do it.) This provides built-in code and design reviews, as well as cross-team learning.

The pairing is flexible; you’d normally have two or more different partners in a day.

Customer and Developer Rights

XP explicitly delineates the rights and duties of the parties involved in development. (This section excerpted directly from Ron Jeffries’ site.)

Manager and Customer Rights

  1. You have the right to an overall plan, to know what can be accomplished, when, and at what cost.
  2. You have the right to see progress in a running system, proven to work by passing repeatable tests that you specify.
  3. You have the right to change your mind, to substitute functionality, and to change priorities.
  4. You have the right to be informed of schedule changes, in time to choose how to reduce scope to restore the original date. You can even cancel at any time and be left with a useful working system reflecting investment to date.

Developer Rights

  1. You have the right to know what is needed, via clear requirement stories, with clear declarations of priority.
  2. You have the right to say how long each story will take you to implement, and to revise estimates given experience.
  3. You have the right to identify risky stories, to have them given higher priority, and to experiment to reduce risk.
  4. You have the right to produce quality work at all times. you have the right to peace, fun, and productive and enjoyable work.

(See http://xprogramming.com/products.htm.)

Beginning XP

How to begin with XP? If you can’t adopt it all at once, you need to apply those practices you can:

Strive for user involvement – push for user presence with the development team. Put tests in place. Many groups pay lip service to tests, but you will move faster the better tests you have. Tests are a prerequisite for refactoring, which lets you begin with simple designs and grow them as needed. Pair programming builds in reviews and learning, and can help you spread the XP practices within a team.

Caveats

[As of late 1999] I’ve never worked with an XP team (outside of a week’s training), so this is somewhat theoretical for me. My recent teams have used a lightweight version of Rational’s Unified Process: tests are required, but they’re not as up-front, ubiquitous, or as automated as they should be; teamwork is acceptable but true pair programming isn’t practiced; we’ve had more of an up-front focus than XP would have. Nevertheless, I think our teams have been struggling toward some of the XP practices, and would find most of them appropriate.

More Information

[Written 12-28-99]