Implementing interfaces and using TDD
published: Mon, 19-Jan-2004 | updated: Thu, 27-Oct-2005
I've been a fan of TDD (Test-Driven Development) for a while, ever since I researched it for work about 18 months ago. I then wrote an article on the subject for The Delphi Magazine (it was the April 2003 issue if you have the back-issues CD; and if you are a Delphi developer and don't have this CD, I heartily recommend that you buy it).
Anyway, I was writing some code yesterday and I used a technique that I have not talked about before, so I thought I'd take this opportunity to do so.
If you recall, TDD is a technique for writing code that ensures (demands?) you write the test case before you write the code in question. It also stipulates that you always try and make your code as simple and elegant as possible.
The way it does this is to use a three stage process that almost becomes a mantra: red light, green light, refactor. Red light means that you've written a test, but it fails (the term comes from the various xUnit testing frameworks where a failing test is indicated by a red blob or bar). The reason it fails is that you don't have the actual code written yet, of course. So you write the code in the simplest manner possible and retest. Eventually (or immediately) you'll get a green light, indicating that the test (and all prior tests) have passed. At that point, you look at what you've just written in terms of the rest of the code, and refactor it to make it simpler. Always your test suite is saving your bacon, by making sure that you don't break the code you've already written, the bane of any attempt to modify or add to code.
Well, suppose you are writing a class and you want to implement an interface. Normally, in TDD, you wouldn't make such a bold move, you'd add the methods one by one as a result of a failing test, and then much later as part of your refactoring work, you say, doh, let's use the IWotsit interface instead, and start modifying code all over the place.
In general, though, you have a goal to reach with this class you're writing. You'll know right off the bat that the class must implement IWotsit because it has to fit into the overall application framework. So, for example, if you're writing a container-type class (aha! say my usual readers, he's on his hobbyhorse again), you know that you have to implement IList. Rather than edge towards the IList implementation, step by step, you want to just write:
type TMyContainer = class(TObject, IList) ... end;
in Delphi; or:
class MyContainer : IList { ... }
in C#.
But as soon as you do of course, blam, you have to actually implement the interface (and in the case of IList, you also have to implement IEnumerable and ICollection as well). What to do?
Well, what I do is to implement the interface fully, but to make each and every method of the interface raise or throw an exception, the NotImplementedException exception to be precise. So, in Delphi, I write:
type TMyContainer = class(TObject, IList) ... function Add(aItem : TObject) : integer; // defined by IList ... end; ... function TMyContainer.Add(aItem : TObject) : integer; begin raise NotImplementedException.Create(); end;
And in C#:
class MyContainer : IList { ... // defined by IList public int Add(object value) { throw new NotImplementedException(); } ... }
That way, I can implement the interface immediately, and yet all future tests I write will still automatically fail, at least until the point I have to write the actual code to enable the test to pass. So, in my example, when I eventually write the test for Add, it will fail (which is what I want). To make the test for Add pass, I will have to remove the raise/throw statement, and implement Add properly.
As a matter of course, I recommend that you write a small keyboard macro to insert this raise/throw statement, so that you can easily add it to any methods you write that you don't want to implement straight away.
(I use a program called ShortKeys for this, but any similar type program will work. In Visual Studio 2003, you could write a macro to do it. So long as the number of keystrokes you have to type is small, you'll find that you'll do it automatically without thinking.)