Software QA FYI - SQAFYI

- Test-Code Cycle - article on testing java GUI

By: Bill Wake

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:

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().

Full article...


Other Resource

... to read more articles, visit http://sqa.fyicenter.com/art/

- Test-Code Cycle - article on testing java GUI