CD-ROMs, Unit Testing, and You

Body

I will go out on a little bit of a limb and say that the single most important and beneficial step that you can take to improve your code is to test first. Yes, I know. I hate testing, too.

I picked up a few books on Extreme Programming several months ago. There are far more useful tidbits of knowledge in there than I can explain in a short article, but one of the most simple and useful is to take what you *hate* and do it *more*. The premise to this unusual practice is that the more you perform this task, the more you choose to automate it, because you loathe to spend so much time on it. This dove-tails nicely with the old axiom that programmers are among the world's laziest people.

Having said that, my current assignment is ATAPI. That is the CD-ROM interface. For those who are interested, it is basically an implementation of the SCSI protocols over an IDE interface. I started to investigate this topic from three sides:

  1. What did Be do? Since there was no documented API, I read the CDButton sample source code.
  2. What does the standard say? I downloaded the MMC-3 standard and read through it.
  3. What does OBOS/NewOS require for drivers?

As I started to understand the scope of the driver a bit more, I started building a set of tests. I decided that I would test Be's driver. That would give me several advantages -- chief among them that I would have a working set of regression tests that I could run against my code. I would also have practice in using the API, so I would *know* the API, because I had *used* the API.

I dove into the CDButton example and realized that user level apps were calling ioctl. For those not so initiated, this is the command to send a command that is not open, close, read, write or create. It is the "and all other stuff goes in here" command. It is also a UNIXism, and in my opinion did/does not belong in a user level API. I know that all of the UNIX folks out there are groaning. Sorry.

So I wrote a simple helper wrapper class. I call it BAudioCD. It's definition looks like this:

class BAudioCD
{
public:
BAudioCD (char *device);
~BAudioCD();
bool Eject(void);
bool Load(void);
bool Close(void);
bool Play(uint32 trackNo,uint32 endTrack=99);
bool Pause(void);
bool Resume(void);
bool Stop(void);
BCDRomPosition GetPosition(void);
status_t GetMediaStatus(void);
BList *GetTOC(BList *storage);
bool ScanForward(void);
bool ScanBackward(void);
bool StopScanning(void);
bool SetVolume(uint32 volume);
uint32 GetVolume(void);
uint32 getCDDBID(int []);
uint32 getTracks(void);
uint32 getLength(void);
};

This is a preliminary -- it should be cleaned up a bit more and become an "official" part of the API. However, this abstraction allowed me to write my test harness for CD-ROM access. It is included at the end of this article. The test harness is very simple. You pass in the device and one command. A BAudioCD is instantiated, that one command is called, then the BAudioCD is detroyed and the application ends. The "parser" is nothing more than a long string of

if (!strcmp(argv[2],"Pause")
...
else if ...

commands. Efficiency is *not* an issue here. This is easy to write and easy to expand with new commands.

With this test tool complete, we can actually write a test script that calls the test tool. The test script will invoke the test tool repeatedly, trying different commands each time. It should capture the results to a "current run" and diff those results against the "known good", allowing the tester to replace the "known good" if the results are acceptable.

I hope that this gives everyone some insight into how to design unit tests. There are certainly a good number of additional tests that could be created here. These are the "it works once, under ideal conditions" tests.

  • The source code can be downloaded from here.