Distracted by the Style and Grace planning and other things, Chet and Ron have been away from the Shotgun project for a while. They need to reload their minds and get back to it. Will our heroes prevail? You know they will.

What with preparing for the Style and Grace workshop and other activities, it has been quite a while since we have worked on the shotgun project. Our customer (Chet) and developers (Chet and Ron) are available to work on it again, so it’s time to figure out what to do.

We immediately wished that we had left a failing test to remind us what to work on. Without that, we’ll need to review what works by looking at the tests, and then determine how to go forward.

This is not unlike what many project teams have to do. A project has been set down for a while, and now it has to be picked up again. All the players need to reload their memories with what is done, what remains to be done, and to figure out what their priorities are.1 Our first question was what we did last.

Looking Around

We looked at the change dates on our files and found the test file that had most recently changed. Quickly, we figured out that our most recent change was probably to write this test:

   @Test
    public void fourCorners() {
        PatterningSheet sheet = new PatterningSheet(folder + "fourcorners.bmp");
        List hitList = sheet.getHits();
        assertEquals(4, hitList.size());
    }

The input file, fourcorners.bmp, has a pixel in the four outermost corners of the picture. We don’t remember why we wrote this test but our guess now is that we wanted to make sure that our code that scans the pictures is reaching all the edges of the frame. Thus, this test is not very well named, because we had to figure that out, rather than just read the name. To get the ball rolling, we’ll rename this test.

Why did we choose to do this silly renaming right now? Part of the way we like to work is to find something to do, however trivial, that will let us focus not just on abstract ideas of what we might have done or might do, but to begin to create concrete code. We thought it would be most productive to find where we had last been, but in truth starting most anywhere might have been just as good.

But we’re here now, so what do we want to call this test? We went through a lot of choices, and even wrote a little paragraph comment, which we could then delete if we had a good enough method name. Here’s what we came up with, not great but better:

   @Test
    public void ensureNotOffByOneByFindingPixelsInCorners() {
        PatterningSheet sheet = new PatterningSheet(folder + "fourcorners.bmp");
        List hitList = sheet.getHits();
        assertEquals(4, hitList.size());
    }

Two things have happened. We’re working in the code, therefore moving foward, and we have improved the code base a bit. We’ve also learned that our test names aren’t very helpful. Probably when we wrote them, we had some purpose, such as to test whether a certain configuration of pixels was accepted as one hit, or two, but most of that information is now lost to us. We resolve to dial up our test name communication a bit.

Moving Right Along ...

The last few times we met, we reviewed new pictures that Chet had taken of actual shots, using new paper and a new camera setup. The earliest pictures we used had been hand processed by Chet, producing a monochrome picture from the JPG. The paper he used was quite white.

Now we’re using paper that is less white, and Chet doesn’t really want to manually process all the pictures. So we had been playing with unprocessed pictures taken at various exposures in various lights. None of them were very satisfactory. Down this path lies more photography and coding up a bunch of image processing things, such as Kelly Anderson was referring to on the lists. We could drill down into this problem, doing more and more pixel processing.

Another path we could follow would be to proceed from a clean input file, producing a clean set of Hit objects, and to work on the graphical display of the output, and the report we want to provide. Which should we do, and why?

We’ve seen teams, especially teams just starting with this style of development, get all hung up on some deep technical story like picture processing. We think that’s often the wrong thing to do, and here’s why. We can’t sell this product until it actually works. We have found that it is possible, though tedious, to process the pictures manually. If the product never comes to market, better picture processing will be useless. When it does come to market, it probably won’t sell millions of reports in the first week. The sooner it comes to market, the sooner we start learning what it really needs to do, and the sooner we start focusing on that big picture the sooner we’ll get our own marketing sense involved.

If we only sell two reports, taking the pictures manually will be just fine. If the reports are popular, the revenue will be there to fund better picture processing. After a few minutes discussion, the answer is obvious to us: let’s get a report out there and see if we can sell it. Our focus will turn to creating a report, populating the report, and getting the product running “end to end” as soon as we can.

Need a Test

If we’re going to work on end to end processing, we need a test. What should that test be, how should we write it, how would we make it work. Pay attention here: this is important stuff.

Chet started struggling in his mind with a test that would go front to back to help us see how the system is going to operate. The system wants to read one or more files, process, find centers and density analyses, produce some text that relates to the results, and assemble all this into “some kind of” report.

Chet felt at first this test would be “better” as a FitNesse test, but would be easier to write as a JUnit test. It would certainly not be a standard “unit” test, but a series of actions. He’s thinking that in FitNesse it’s not uncommon to have a setup and then a few tables that do something, rather than the “one assert per test” style that people suggest for JUnit. But what would a fitnesse test look like for a multi-page report?

On the other hand, the final system itself will actually be sequentially coupled in this way: it will in fact open the files, create the internal data, create the analyses, and assemble them into a report. It is likely that the final system will have such a “Composed Method” in it anyway. The FitNesse concern is likely a red herring, and creating and testing that method with JUnit seems to make sense.

We’re planning to produce PDF output, and we have a library to create PDF files. However, we don’t know a good way to write a test for a PDF file. Again we’re at a point where teams get stuck: “But our system has to do X. There’s no good way to test X with JUnit or FitNess. Therefore Agile or XP or whatever you’re selling doesn’t work for us.”

It will not surprise you that we come up with a different answer. Here’s an example of what we mean by “Style and Grace”. We get past “can’t” in the strongest possible way. We replace “can’t” with “can, though it might not be a very good way.”

We could go down a rathole here, but recognizing the trap, instead we begin by thinking about our product. Our product, essentially, is a report. We point the system at a bunch of pattern photos, and out comes a report. It’s all lovely, probably comes in a nice leatherette binder and so on. The computer’s job is to take some input and produce the report, which will be a PDF file.

We did a very brief Quick Design Session. I grabbed a bunch of cards that happened to be on the table and tossed one down. “Here’s a Report. In the report are Pages (laying three Page cards on the Report). Inside each Page are Paragraphs and Pictures (laying some Paragraph and Picture cards on a Page card).

That’s it: those are the objects we’ll need. Each Report will contain various Pages, Paragraphs, Pictures, and so on. We can test those inner objects with code tests, because they’ll have behavior and contents. So we’ll write some tests for Report, and essentially TDD the whole Report into existence.

We decide to make our end to end tests with JUnit for now. We will name the test file ReportCreationTest. Here’s our first test, with some things highlighted for discussion:

package com.hendricksonxp.patterning.test;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

import com.hendricksonxp.patterning.model.PatternReport;

public class ReportCreationTest {

    private PatternReport report;

    @Before
    public void setUp() throws Exception {
        report = new PatternReport();
    }

    @Test
    public void emptyReportHasNoPages() {
        assertEquals(0, report.pages().size());
    }
}

Our thinking here is that there is a class PatternReport. It has pages, and a newly created report won’t have very many pages at all. This test won’t compile for various reasons including there being no PatternReport class, and no methods on it, and so on, resulting in:

package com.hendricksonxp.patterning.model;

import java.util.ArrayList;

public class PatternReport {

    public ArrayList pages() {
        return new ArrayList();
    }

}

That little test forced us to create the report class, and to return all the pages we had, namely none. Perfect. The test is green. Product complete. Ship it! We could go home now if we wanted to. But there’s anothe few minutes before lunch, and we would like to leave on a filing test, so that tomorrow we’ll know what to do. Now we have a little discussion about the next test, and what it tells us about the system. This is, in essence, another Quick Design Session. I’ll describe our design thoughts in terms of the test:

   @Test
    public void coverSheetHasClientName() {
        ReportPage coverSheet = report.pageNamed("CoverSheet");
        ReportParagraph namePara = coverSheet.paragraphNamed("ClientName");
        assertEquals("Dan Orlich", namePara.text());
    }

We will do a PatternReport for a client. We figure that report will have a lovely cover sheet, maybe like this:

Pattern Analysis Industries Pattern Analysis Report for

Dan Orlich

lovingly prepared at great expense by Chet and Ron</strong>We decide to test whether the PatternReport’s cover sheet has the name Dan Orlich2. We have already talked about the PatternReport object. We imagine that it contains pages, and since having the pages be in some standard sequential order would be silly, we will instead let the Report be able to return pages by name. Similarly Pages will have ReportParagraphs in them, also named. Paragraphs, we decided, include text, though they may also have other attributes in the future. Close enough for now.

Now Chet and I paused to discuss a topic from the Style and Grace realm. We feel, sometimes, as if we are the only two people in the universe who would do what follows. It now looks as if we have to create a huge mechanism here, of reports and pages and paragraphs and indexing, with some kind of backing store and probably an installation of Oracle or something.

We’re not going to do that. Instead, we are just going to make this test first fail, and then run.

Discuss the folder structure, the header info, flat text, possible XML and why it’s no big deal.

We know that we’ll receive pattern sheets from the client and take pictures of them. The natural thing to do will be to put the pictures from each client in a folder on the computer, and point the product at that folder to produce the report. In that folder, there will be some kind of file that will provide the client’s name, address, phone number, credit card, whatever. If not exactly that, we know that we’ll have to have that information provided somehow. Maybe it will be an entry in the giant Oracle Database of Shot Pattern Reports, and we’ll have the GUID of the client’s info in there. (Somehow, I doubt it.)

Close enough. We’re going to provide the system with the name of a folder, and in that folder it will find everything it needs to do the job. It will produce the report into that folder, or into some other folder, or write them with giant flashing “lasers”3 onto costly platinum sheets. Something really complicated, we’re sure. We just don’t care.

But why don’t we care? What if we decide later to change the input format from flat text to XML or to put the information into a database or read it with a handwriting recognition system from the return address label on the client’s submission. Won’t that change EVERYTHING??? We tell ourselves “Be brave, little piglet.” Being brave, we think just a bit further.

We code in objects. Our system will take some key or other, such as a folder name “orlich”, to kick it off. It will find a bunch of information in that “folder” or whatever it is. It will find “client information”. There will be a client information object. It will be the only object in the whole system that knows whether the client information is in a flat text file, an XML file, or written in the sky with smoke. We may have to write some tricky code to read the smoke but if we do, it’ll be inside the client information object and we’ll just have to do it once and put it in one place. It won’t affect everything.

Therefore we don’t have to worry about it. We’ll just encapsulate the knowledge. And therefore, the test above is perfectly good, and here’s the code it takes to make it run, and run red:

package com.hendricksonxp.patterning.model;

import java.util.ArrayList;

public class PatternReport {

    public ArrayList pages() {
        return new ArrayList();
    }

    public ReportPage pageNamed(String string) {
        return new ReportPage();
    }

}
package com.hendricksonxp.patterning.model;

public class ReportPage {

    public ReportParagraph paragraphNamed(String string) {
        return new ReportParagraph();
    }

}
package com.hendricksonxp.patterning.model;

public class ReportParagraph {

    public String text() {
        return "";
    }

}

Yep, that’s it. Return a string. There is an egregious violation of the Law of Demeter here, so the design is not perfect, but we know how to fix imperfect designs. And we expect that our design will change, but not in ways that will kill us.

The test compiles, runs, and fails, because “” != “Dan Orlich”. Just in time for lunch. Another perfect day.

One tiny detail. Right before the ReportPage object got typed in, Chet and I spoke briefly about the fact that we aren’t throwing an exception or doing anything like asking whether the page exists. This is, of course, standard “happy path” programming. That’s the way we do it, and we think it is part of Style and Grace. But why isn’t it stupid?

If a page doesn’t get found, we’ll return a fake page, probably using the Missing Object pattern. That page will behave normally, except that it won’t have any interesting paragraphs, and the uninteresting (missing) paragraphs won’t have much interesting text, probably limiting themselves to saying “missing” or something.

I stepped away at that time for a moment, and when I came back, Chet had started to define an interface, IReportPage. I said “YAGNI”. He said “but we’re going to need to return a Missing … OK.” We discussed that if we need an interface, Eclipse will help us extract it and rename things in that area. Even though it looks like we need the MissingReport, and the interface right this very minute, we do not.

Don’t do what you don’t need to do. Style and Grace.

We wrapped for the day and talked over lunch:4

Reflection

At lunch we reflected that we began this project with a very technical set of stories, that answered the question of whether and how we could process the input from a sheet of paper with holes in it. We think that had we been coaching a team working on this product, we would have at least suggested holding off on that and doing an end to end story first. But we talked ourselves out of that because we were so concerned that it might be technically impossible, and we still feel that we needed to clear that up. But it’s a slippery slope: everything that has gone on up until today has some visible business value, but it isn’t bringing things together in the way that today’s work did. Today, we have a product about to spring into existence, as soon as we make this test run. That’s a big deal, because it will help us focus on what the product is, not on little techie details.

We relied on objects to encapsulate implementation. We posited a structure for our report, with pages and paragraphs and text. We built trivial objects to represent these things. We expect those objects to stand the test of time pretty well, and we observe that they are only a few lines of code wasted if they do not.

We have put off the question of mechanically testing the PDF but we did talk about it. What we said was that we have a library for producing the PDF and that when it comes time to test the reports, we can do something like mock that library to test the report sequence. As for testing whether the paragraphs look right, we’re not sure. We might just test that we make the right calls and let the library do its work. Or we might do something more complex: we’ll let tomorrow make that call.

At the end of today’s session, we had restarted the project, created a much stronger understanding of end to end than we had before, posited a reasonable but very lightweight structure for the system, and we had written a test and made it fail in just the right way. We were proud of the day’s work, and it had all taken place in a few hours in Borders Brighton coffee shop, under the influence of the lovely Gemmie’s delicious Iced Chai Lattes, and a few bottles of water.

Smooth, easy, no fear. Style and Grace. It’s not too late to sign up for the workshop: sign up today. Think how much fun it’ll be, and how much we’ll all learn about how to do our work.


  1. Chet remarks that forgetting what needs to be done occasionally happens over lunch. This time, we went to lunch and didn’t come back for weeks. We might have been just as confused on the subsequent day: we’ll never know now.

  2. Dan Orlich, a former Green Bay Packer, is a member of the National Trapshooting Hall of Fame. Chet chose this name for our first test in Dan’s honor.

  3. cf. Dr. Evil.

  4. Lu and Carl’s. Burger for Chet, Chicken Fenders for me. Stacie Of The Beautiful Eyes was our waitress. Life is good.