Breaking Apart at the Seams

written by SilkRoad on Saturday, January 19 2008

I had a project that involved developing  software for those hand-held barcode scanners that are often used in warehouses to track inventory. The project required a lot of repetitive manual testing. Data had to be manually keyed in with a key pad to test the functionality I was adding. Previously tested functionality had to be re-tested any time new code was added to verify that it had not been affected by the changes. It soon became apparent that it would be worth the effort to write some unit tests.

However, I was working on a legacy system implemented in C++, and the new class that I wrote depended on a concrete class that read data in from the hardware device. For the sake of discussion I'll refer to the concrete class as "HandHeld", and the class I wrote as "WarehouseAdjustments". The code was dependant on the user physically entering data into the device. The challenge was determining how to instantiate my WarehouseAdjustments class in a separate test harness so I could write some unit tests for it and avoid having to manually test everything. I couldn't just comment out the instances of HandHeld that WarehouseAdjustments used for my test because it was important that the unit test test the same source code that would run in production.

Fortunately, there is a great book that can help in solving problems like this: Working Effectively with Legacy Code by Michael Feathers. This is a rare book in that it describes how to deal with the existing, and often messy, code bases that the average developer is faced with rather than assuming we all develop systems from scratch as most programming books do. With chapter titles like "I Need to Change A Monster Method And I Can't Write Tests For It", "My Application Has No Structure", and "I'm Changing The Same Code All Over The Place" the book is full helpful techniques for writing clean, testable code.

The book introduces the concept of seams in your source code. Seams are locations in your code where you can change the functionality of your programs without changing the code. Seam types described include preprocessor seams, link seams, and object seams. Some programming languages, like C and C++, have a preprocessor to handle a processing stage before compilation. In these languages you can use the preprocessor to substitute calls to untestable code, like calls that expect a physical input from a hardware device, with mock functions or classes. Link seams use the linker to link into your program mock functions or classes. When working with object oriented languages object seams will usually be the most useful type of seam, and this is the type of seam I used to address the issue with the HandHeld class that my WarehouseAdjustment class depended on.

In order to be able to instantiate and test my WarehouseAdjustments class in a separate test harness, I made HandHeld implement an interface. In C++ this meant inheriting from an abstract base class I called IHandHeld. Then I changed WarehouseAdjustments to depend on IHandHeld instead of HandHeld. I also created a sub-class of HandHeld called MockHandHeld which I could use in the unit tests instead of HandHeld. MockHandHeld overrode methods that read from the hardware making them return values to my WarehouseAdjustment class that I could set programmatically. The same WarehouseAdjustment source code could then be used in my unit test and production code. Except that in my unit tests WarehouseAdjustments' IHandHeld member referenced a MockHandHeld object instead of a HandHeld object enabling me to mock a user entering data into the device.

Similar Posts

  1. Project Management Software
  2. Cuyahoga's Search Implementation
  3. Search in C# with Lucene.Net / DotLucene

Comments are closed

Options:

Size

Colors