XProgramming > XP Magazine > Adventures in C#: Coffee Shop
COLLECTED TOPICS: Adventures in C# | Documentation in XP | Book Reviews
Adventures in C#: Coffee Shop
Ron Jeffries
12/08/2003
Steve Howell on the extremeprogramming group is proposing a Coffee Shop application and acting, I guess, as customer on it. The discussion of the program has gone on too long without anything concrete, and so I have decided to program something to crystallize my ideas, and the situation.

Contents:

The Application

The real application is some kind of program running on the counter, where the wait people enter the things ordered, with various options on each item, and the program calculates the bill. Steve has ideas about end of day summaries and all kinds of things, although at present, I gather, we're pretending that the entire coffee shop is running on paper.

Part of his interest seems to be in how to break down the stories for early release. The discussion, though, is going toward such tiny pieces that I am confused: I would think we would release to the counter no more often than weekly.

I don't really know if he has in mind some ultimate app like a wireless pad-based application where the waitpeople can go to the table, click on things on their Pocket PC and have it beamed to the chef and the headquarters corporate database or not. For now, it's enter orders at the counter and display or print a ticket. In fact I am going to do this from memory, because the customer isn't here with me. I'm not going to read his material again, to sort of simulate not having the customer present.

My Initial Vision

There is a "Tape", like the one that you get at the restaurant or supermarket. On the Tape are Items, each with a price. Unlike a typical tape, however, Items have Options, and some of those Options have a price and therefore may change the price of the item.

The Tape will have a Tax, and a Total. Here's a rough picture of a Tape:

COFFEE SHOP
  Sandwich   $2.50
    Ham
    Mustard
  Sandwich   $2.95
    Ham
    Grey Poupon 0.45
  Coffee     $0.25
    Cream
    Sugar
  Latte   $4.90
    Double Shot 1.40
  -----------------
            $10.60
  tax          .64
  =================
  total     $11.24
  amt tend  $12.00
  change    $ 0.76

I'm not sure if I can do all this in a week or not at this point, but I suspect that I can. Here's what I am, and am not doing, however:

At this point, I'm working on the model part of the program, the objects that make up the application, namely the Tape, Item, Option, and so on. I want to understand how things fit together.

For the Future

I am really bad at GUIs. I plan to ask Paul Friedman, my occasional pair programming partner, to remotely pair with me on the GUI.

Begin With a Test

It's about 4:30 AM, and it's time to get to work. I've had too much time to think about this and not enough time programming. That's a bad thing, because I'll get all kinds of big ideas that can get in the way of simplicity. I'll try to be good. I'm going to work directly with the order ticket, and here's the test fixture as generated by my little Wizard:

using System;
using NUnit.Framework;

namespace CoffeeShop
{
  [TestFixture] public class TicketTest: Assertion {
  
    public TicketTest() {
    }
    
    [SetUp] public void SetUp() {
    }
    
    [Test] public void Hookup() {
      Assert(true);
    }
  }
}

After I set up the obligatory reference to NUnit, this works. I'm really glad that I built the Hookup test right into my prototype test fixture, I get quick feedback that way.

My plan is that the Ticket is the object that order items get written on and that it totals things up. I'll just start testing in that direction. At this moment, I am expecting to continually enhance one test rather than write new ones, because I'm not seeing an incremental breakdown of this tiny object. Well, wait, maybe I have an idea. We'll see. Anyway, here's a test:

    [Test] public void Coffee() {
      Ticket t = new Ticket();
      t.AddItem("coffee", 50);
      AssertEquals("  0.50  coffee", t.ItemList());
    }

The idea is that the Ticket gets items with prices, and that it gives back a formatted list, the detail part of the Ticket printout, with ItemList(). Now some code to make this compile and then run. Naturally, I'll fake it:

using System;

namespace CoffeeShop {

  public class Ticket {

    public Ticket() {
    }

    public void AddItem(String item, long price) {
    }

    public String ItemList() {
      return "  0.50  coffee";
    }
  }
}

Test runs. We'll need another test, and move Ticket creation to SetUp(). (Brian Marick said yesterday that he prefers to put all the setup in the test. I'll have to talk more with him about that.)

using System;
using NUnit.Framework;

namespace CoffeeShop
{
  [TestFixture] public class TicketTest: Assertion {

    Ticket t;
  
    public TicketTest() {
    }
    
    [SetUp] public void SetUp() {
      t = new Ticket();
    }
    
    [Test] public void Hookup() {
      Assert(true);
    }

    [Test] public void Coffee() {
      t.AddItem("coffee", 50);
      AssertEquals("  0.50  coffee", t.ItemList());
    }

    [Test] public void Tea() {
      t.AddItem("tea", 45);
      AssertEquals("  0,.45  tea", t.ItemList());
    }
  }
}

4:39 AM: Now we should get some action. This might be a bit of a big bite, because I have to process the item name, the price, and the formatting to make this all go. Could take a couple of minutes. Let's see:

I need a collection of, oh, OrderItems, and to add one on each AddItem:

using System;
using System.Collections;

namespace CoffeeShop {

  public class Ticket {

    ArrayList items;

    public Ticket() {
    }

    public void AddItem(String item, long price) {
      items.Add(new OrderItem(item, price));
    }

    public String ItemList() {
      return "  0.50  coffee";
    }
  }
}

I need an OrderItem class: Starting at 4:41 AM ...

Now it's 4:47 AM, that took a moment or two, but the same tests run. I moved the "fake" over to a new class OrderItems, with this code in Ticket:

using System;
using System.Collections;

namespace CoffeeShop {

  public class Ticket {

    ArrayList items = new ArrayList();

    public Ticket() {
    }

    public void AddItem(String item, long price) {
      items.Add(new OrderItem(item, price));
    }

    public String ItemList() {
      return items[0].ToString();
    }
  

And this implementation of OrderItem:

using System;

namespace CoffeeShop {

  public class OrderItem {

    String itemName;
    long price;
  
    public OrderItem(String itemName, long price) {
      this.itemName = itemName;
      this.price = price;
    }

    public override String ToString() {
      return "  0.50  coffee";
    }
  

The coffee test is running now. Now we'll need to improve OrderItem to make the ToString() work right, and both tests should run. Then we'll deal with the list.

It's 4:57 AM.

    public override String ToString() {
      StringWriter w = new StringWriter();
      w.Write("  {0}.{1}  {2}", 0, price, itemName);
      return w.ToString();
    }

This shows me that the tea test has a bug in it.

[Test] public void Tea() { t.AddItem("tea", 45); AssertEquals(" 0,.45 tea", t.ItemList()); }

There's an extra comma in there. Should be:

    [Test] public void Tea() {
      t.AddItem("tea", 45);
      AssertEquals("  0.45  tea", t.ItemList());
    }

Both my tests are working now at 4:59 AM. It's time for a break, I've been programming for a half hour and I'm really tired. Let's review what we have.

Session One Retrospective

We now have an object Ticket, that accepts OrderItems. OrderItems know a name, and a price (in pennies -- did you notice that? Can you tell me why I did it? Should the code reflect that it is pennies? Probably. Let's remember to do that.)

An OrderItem knows how to format itself for the Ticket. The Ticket has a list of items but really only deals with its first item.

These objects feel pretty good to me, because a Ticket seems to be part of the customer's domain, as do items and prices. There is of course plenty more to do but this is only a half hour's work so far.

Session Two Prospective

Here are some notes for next time:

  • We'll need to add two items to the Ticket and get a suitable ItemList() result.
  • We'll need to add options to OrderItems, like mustard and mayo.
  • We'll need to add priced options to OrderItems like spinach!
  • We'll need to have the Ticket compute tax.
  • We'll need to have the Ticket compute the total amount.
  • We'll want amount tendered and change.

My buddy Paul is going to work on the GUI. To support that, we need a Menu, which will include possible items and their options and prices. He'll display those, the user will click them, he'll send them to a Ticket, and display the Ticket. My idea is that the Ticket will be displayed in a sort of TextBox that will look like a paper ticket.

I suspect that Paul could actually run with what's here, but we'll see if I can get a little further later this morning. For now I want a break, and a shower. Maybe I can do another hour from 0600 to 0700 or something.

XProgramming > XP Magazine > Adventures in C#: Coffee Shop
COLLECTED TOPICS: Adventures in C# | Documentation in XP | Book Reviews