Sunday 15 July 2007

My first attempts at "proper" TDD

Although I've used it several times in the past, my first serious attempt was with FreshReports. That is, I wasn't testing it manually at all.

FreshReports is a printing component (a framework wannabe). It uses xml files as the source for design and in-memory objects (in contrast to the majority of such components that use database rows) for the data.

Naturally, I quickly figured out some basic design, wrote a trivial xml file and started to write tests. In addition, I've been inheriting from System.Drawing.Printing.PrintDocument, and used the hardcoded System.Drawing.Graphics object. Just like all tutorials on printing suggested.

Like any amateur, I thought I was doing unit testing, but in fact I was doing integration tests. I thought I was doing test-first, but in fact I designed something first and tried to fix my bugs with testing. Well, I was doing just fine. I stumbled upon TypeMock, and this wonderful tool allowed me to do a lot of testing without all these IoCs and SoCs people talk about. I said I was doing just fine because, although I could spend an hour or two in the debugger trying to figure out what's going on (or lately, after I read in some blog post that I shouldn't be in the debugger, I started to log way too much to the console and also started writing tests for my private variables), I've been doing it all the time before. I just didn't know there could be a better way.

At least I learned what a mock is. Because before that I thought it was too complicated for me.

My second attempt was with Dormidontus. I've learned a lot since then, and I have heard that unit testing is about testing small isolated pieces. Armed with TypeMock, I could mock everything my methods call (even the class I've been testing). So I decided to move with small steps. I figured, for example, that my main workflow consists of three steps. So, I happily mocked three method calls (because I already figured the classes responsible for these steps), then wrote the main method and got a green. Now, each method call could be naturally broken into smaller steps, so I mocked these smaller steps, repeated the calls in the production code, got a green again.

In short, most of my tests (until I got to the lowest level) contained no asserts, but just mocked calls to other objects. I guess this is what you call interaction-based tests. All went rather smooth, however, I felt that something is wrong. First, my test code duplicated my production code. There was no way to write my production code other than it was dictated by my tests. So, in a way my design was driven by my tests, but at the same time I knew my design before I even started to write a test. Second (a direct consequence), it was impossible to refactor without breaking the tests. Given these two, I felt that my tests are pretty useless, except for the fact that I didn't have to debug my application.

I then posted a question to the testdriven yahoo group regarding testing my high level methods that don't do anything except for calling other lower-level methods. After I received a couple of very clarifying answers, I realized my mistake. Instead of trying to provide some business value as soon as possible, I was focused on my oh-so-elegant design.

I still believe that TypeMock is terrific. It is a pure magic. For example, back to FreshReports, no matter how you abstract and wrap the Graphics object, eventually you should test your wrapper, or adapter, or proxy. You just can't do it without TypeMock.

Some people dislike TypeMock for it does not force you to apply "good" practices. Not only I disagree, it drives me mad. I strongly believe that grown-ups should not be forced. But that's a good idea for another blog post. Now I admit that with regards to TDD I'm far from being a grown-up, so perhaps being forced (or I'd say, led) could be a good thing for me until I'm more confident with these things. However, I'm still thankful since TypeMock gave me a soft start with mocking, and also gave me these lessons.

With Dormidontus, I managed to switch to the right path right in the middle of the project (yes, I even managed to force myself to delete a good part of the existing code). As for FreshReports, with some hesitation I decided to build it again from scratch. I've got the second beta of Orcas Express (too lazy to download and too scared to install the full version). The first beta refused to work with TypeMock, so I decided that it's a sign and I should try RhinoMocks.

Now I'm fighting through my first test.

No comments: