I'm working on an example of developing acceptance tests using a Digital Video Recorder as my problem domain. After several acceptance tests I started working on the idea of season pass priorities. In a nutshell, a season pass says "record all episodes of a given program". When you add priority, then the highest-priority season pass takes precedence. For example, say you can only record one program at a time and on Monday Evening you want to record House at 7 PM CST as well as Chuck, same time, different channel. If House is the higher priority program, it should be recorded.

So I wanted to create several tests around priority with dimensions like:
  • Number of programs that can be recorded at the same time (1, 2, 4)
  • Priority when there is no conflict.
  • Priority when there is conflict.
  • Priority when there is conflict and then an episode gets removed from the schedule.
  • ...

First thing I wanted to do was create a schedule to test against. A program has zero to many episodes and I want to be able to create them in different ways. My first thought was to us a script table.

Script Table

I like Script tables because they remind me of Smalltalk. I like to use them when I need to create things that have one to many relationships and I want to create them together.

A program has a normal day and time and is on a channel. An episode can be at any day or time, but is the same day and time of the program by default.

Here is a script table that first creates a program and then adds 4 episodes to it:
|script|Program Scheduler                                                                               |
|$B=   |Create Program Named|BSG|On Channel|247               |Every|Friday   |At      |9:00|Duration|60|
|ensure|Add Episode To      |$B |Named     |He That ...       |On   |2008/4/4 |Duration|45              |
|ensure|Add Episode To      |$B |Named     |Six of One        |On   |2008/4/11|Duration|30              |
|ensure|Add Episode To      |$B |Named     |The Ties That Bind|On   |2008/4/18|Duration|15              |
|ensure|Add Episode To      |$B |Named     |Escape Velocity   |On   |2008/4/25|Duration|10              |
  • The first line creates a new instance of a Fixture (calls the constructor). In this case it is the ProgramScheduler class.
  • The next line executes a method called "CreateProgramNamedOnChannelEveryAtDuration". The method returns a value (the id of the program created), which is stored in a variable called $B.
  • The next 4 lines all starting with "ensure" call a method named "AddEpisodeToNamedOnDuration". The value returned from the second line, which was stored in $B, is used as input into each of these lines.

This particular example creates one program:
  • Named Battlestar Galactica
  • On channel 247
  • Every Friday
  • At 9:00
  • With a duration of 60 minutes

Next, 4 episodes are added to that program with the following names:
  • He That ...
  • Six of One
  • The Ties That Bind
  • Escape Velocity

Decision Tables

This was fine initially, but for some tests I wanted to make more stuff with less verbage. I needed a lower-fidelity way to create more things. By lower-fidelity, I really mean it was OK for some things to be less flexible (e.g. episode duration always being the same as the default value).

Then I decided to try Decision tables:
|Create Programs|
|Name |Channel|DayOfWeek|TimeOfDay|DurationInMinutes|id? |
|House|4      |Monday   |19:00    |60               |$ID=|
|Create Episodes                                                                |
|Program Id|Name                                                       |Date    |
|$ID       |He figures it out based on something Wilson says.          |2009/4/1|
|$ID       |He's wrong many times but then in the last minute is right.|2009/4/2|
|$ID       |They break into someone's house.                           |2008/3/1|
This accomplishes the same kind of thing in a more succinct manner than the script table.

  • The first table creates a single program, but notice it would be easy to add any number of programs to this table by adding additional rows.
  • The second table creates three episodes.

FYI, here's an example query table to validate these results:
|query:Episodes                                             |$ID                                 |
|name                                                       |date    |startTime|durationInMinutes|
|He figures it out based on something Wilson says.          |2009/4/1|19:00    |60               |
|He's wrong many times but then in the last minute is right.|2009/4/2|19:00    |60               |
|They break into someone's house.                           |2008/3/1|19:00    |60               |

Table Table

As already mentioned, I envisioned my domain with two separate concepts, Programs and Episodes. This might be a mistake but for now I am not considering whether it is or is not. An episode can only exist with a Program. Season passes operate on episodes, not programs. (If anything, removing the Program concept would probably simplify things.) So I needed a way to create a bunch of programs and episodes. I also needed a good way to be able to see what I was doing.

So I came up with the following table:
|Table:Create Schedule                                                          |
|start time|1:00                                                                |
|200       |aaaaBBccccccccDDDDeeeffffffffffgggggggghhhiijklmnopqrstttuvwxyzzzzzz|
|247       |aaaaBBBBccccDDDDeeeeFFFFggggHHHHiiiiJJJJkkkkLLLLmmmmNNNNooooPPPPqqqq|
|302       |aaBBccDDeeFFggHHiiJJkkLLmmNNooPPqqRRssTTuuVV                        |
|501       |____________________aaBBccDDeeFFggHHiiJJkkLLmmNNooPPqqRRssTTuuVV    |
|556       |______aaBBccDDeeFFggHHiiJJkkLLmmNNooPPqqRRssTTuuVV                  |

  • The first line calls a Table Fixture called Create Schedule.
  • The second line defines the time of day this schedule starts, 1 AM.
  • The next 5 lines describe a schedule of programs. Each line:
    1. Starts with the channel number
    2. Has letters for a program, e.g., aaa, BB, cccccccc.
    3. The length of the name determines its duration, with 15 per letter.
    4. Names are all the same letter (case included).
    5. An underscore means "no program scheduled"

I came up with this right from the domain (and I doubt I'm the first person to do something like list). I was trying to visualize the program schedule to figure out how to create it quickly and it occurred to me that I could do that like I did above.

How is this any better?
  • I can create a program schedule very quickly.
  • I can add episodes even easier.
  • For the kinds of tests where I'm worried about overlap, it is easier to describe the expected result since it fits on a screen so nicely.

Give the above episodes, I can now create the following season passes:
  • hhh on channel 200
  • kkkk on channel 247
  • VV on channel 302
  • kk on channel 501
  • ss on channel 556

Then, depending on their order, (priority) I can verify that what is scheduled is correct. For example, give the above order and assuming one recording at a time, the "to do" list should include:
  • hhh on 200
  • VV on 247

If I can instead record two things at the same time, then the "to do" list should include:
  • hhh on 200
  • kkkk on 247
  • VV on 302

And that's it. If the order is reversed, then the following programs will get recorded:
  • ss on channel 556
  • kk on channel 501

For me, this makes it much easier to:
  • Express a schedule
  • Find examples that will have both conflicting and non-conflicting programs
  • Easily verify the rules

This last form was harder to code but easier to write expressive tests. When working with acceptance tests, this seems like the correct trade off.

I used TDD to verify parsing of the table and then connected the table parser to the fixture and got the schedule built quickly.

Update as of March 2009

I was visiting my good friend David Nunn in Dallas and I showed him my "fluent" or DSL-esque table and he said "ugh! that's ugly!" He looked at it and pointed out that it looked like gibberish and also pointed out that I'd need to use a fixed-point font (I didn't tell him I put in pre and tags). He was right, of course. So I improved this table to this next version:

|Table:Create Schedule v2                                                 |
|   |1   |2   |3   |4   |5   |6   |7   |8   |9   |10  |11  |12  |13  |14  |
|501|    |    |    |    |    |aaBB|ccDD|eeFF|ggHH|iiJJ|kkLL|mmNN|ooPP|qqRR|
|556|    |__aa|BBcc|DDee|FFgg|HHii|JJkk|LLmm|NNoo|PPqq|RRss|TTuu|VVxx|xxxx|

To get this to work at first, I simply converted the row into a string and parsed it using the original parser. Of course, this only works if each cell is 4 letters long (or the same length). A real parser should handle this correctly. Even so, it greatly improves the match to the metaphor.