Test Driven Development in Real World Apps
Posted on 10/9/06 by Felix Geisendörfer
In this post I want to talk about my experiences with using test driven development as well as sharing my oppinions/attitude on programming in general. It's kind of a behind-the-scenes regarding my current thoughts & approaches towards coding. But let's start with TDD:
So far I've had tons of fun trying to create php code using test driven development. It caused my code to become a lot more structured/consistent because writing the tests shows you interfaces problems right away. This is because the process of writing the test forces you to think a lot more about *how* you want to use some class / piece of code vs. your traditional "how can I get task x done"-thinking.
Another benefit is, that once your code can do the task you want it to do, you are lot more likely to refactor and improve it. I used to write (complicated) code that I wouldn't touch any more after a while, just because I was afriad it would break something without me noticing it. When using test driven development I feel a lot more like improving things, just because I know my test case will notifiy me instantly if something breaks and what exactly went wrong. Great stuff, I love it!
Now I already talked about the fact that I'm working on an Image Library called Kaizhi. It initially started out with my need to resize & crop images for a web page I'm working on. I had written and rewritten image manipulation code over and over again in the past and I finally decided this would be the last time I do this. I'm simply one of those people who enjoy the process of writing code a lot more then the actual results. This often causes me to create little libraries for things I just partially need and which I often release on this blog afterwards. You could argue that this makes me a bad pragmatic programmer, but I feel really uncomfortable if a deadline seems to get into the way of code quality and I much rather spent more time on a piece of code in order to do it "the right way" instead of "the quick & dirty fast way". Now ideally those two meet in the middle, but usally it's complicated to make this balance working well.
But back to the Image Library I'm working on. For the reasons mentioned above I decided to try to write a highly reusable, light weight general purpose image manipulation library. Afer consulting wikipedia's list of artists, I decided to name it Kaizhi after Gu Kaizhi, an ancient chinese painter who wrote three books about painting theory. I've not used TDD for the entire library, but when I started to work on the GifFile class I knew it wouldn't be doable without good testing. Well actually that's a lie. My first attempt to write this class was done without any test writing and resulted in very poor code and finally I wasn't able to continue it any more. So that's the real reason I started to use TDD for it ; ).
But back to the class' origin again. My initial reason for writing a binary gif encoder/decoder was that the GD Library for PHP can create gif files, but unfortunatly no animated ones. But since the GIF documentation can be easily found on the web (87, 89a) I thought it would be a fun challenge to convert several gif files created by the GD library into one animated file.
The problem with using TDD on this was that I didn't know the GIF format very well, so there was a good chance I would write faulty tests. To avoid this, I decided to pick 3 random gif files from my computer and to take them apart manually and save the expected decoding information into some YAML files. Those would look like this example:
gd-sample.gif:
header:
version: 87a
logicalScreenDescriptor:
width: 300
height: 300
packed_fields:
global_color_table: true
resolution: 7
sort: 0
global_color_table_size: 256
background_color_index: 0
pixel_aspect_ratio: 0
blocks:
- type: image_descriptor
content:
seperator: "0x2c"
left: 0
top: 0
width: 300
height: 300
packed_fields:
local_color_table: 0
interlace: 0
sort: 0
reserved: 0
local_color_table_size: 0
- type: image_data
- type: extension
content:
label: 254
comment_data: "#x36felix was here with a hex editor, and had a lot of fun"
info: comment
So while I added more and more decoding code to my class I completed the expected YAML results for my selected samples. I also wrote a useful little function that would automatically run asserts, comparing the results of the class with the expectations from the YAML files. This way I could add new informations to my YAML samples that would cause my tests to fail until I completed the functionality inside the class. This form of TDD was very easy to work with since I only had to change my test case a couple times to improve the YAML/results comparision function while I got constant feedback on what caused my tests to fail. So like if I was working on an ideal case to demonstrate the advantages of TDD, I had more then one occation, where tweaking one function broke another which might have went unnoticed withouts the tests. So this was great. And the best thing was that I was writing very little testing code and lot's of real application code.
But I still had one big concern: What if my sample data was wrong and therefor my code just satisified the tests instead of the real task of decoding a gif file? That's when I had a really good idea. It basically came straight from my childhood. When I was young I loved to take things apart to figure out how they worked and then put them back together and hoped I didn't break them. Now when working with files you can do the exact same thing. You can simply write an encoder for the format you work with, and if you are able to produce the *exact* same file by decoding and then encoding it again, you would know you've written some decent code. So I did that, and after I was done I simply added ~50 GIF more files to my tests and checked if I could take all of them apart and put them back together correctly. That unvealed a couple more minor issues that the sample files couldn't highlight and after fixing those, it really only was a 5 minute task to take several gd gif files and convert them into an animated gif.
But ok, I promised you to talk about Real World Apps, and I know most of you guys don't code Image libraries all day long, and this "take apart & put back together" technic doesn't work quite that well with MySql or Http post data. So if you are looking for a very detailed tutorial on that stay with me until I complete my CakePHP testsuite called CakeTaster. But what this post offers in terms of real world apps, is a little insight in how you can apply TDD. TDD does not mean you have to test every function of the class you work with (that's extensive unit testing), TDD just means that your tests define the requirements your application (classes) need to meet. By thinking of TDD like that, you can get the best out of testing without spending an horrible overhead of time on it.
--Felix Geisendörfer aka the_undefined