One Day of TDD
I am am a big believer in software testing. I normally have created tests after writing my code and mostly to ensure that regressions of functionality don’t occur when the code is changed. As I have become more comfortable with testing, and the changes it requires such as writing testable code, I have found even more benefits of testing. Better automated testing, and better understanding of testing has changed my development practices.
I haven’t practiced TDD, but I do follow Test Driven Corrections (TDC) which I might be coining right now. Following TDC means that when you find a bug, you should try to write a test that fails on that bug, then fix bug and make the test pass. I have become a fan of fixing bugs this way because bugs often first appear in code that for one reason or another is brittle or has some unseen dependency. If you just fix the bug even if it was a simple mistake it is still far more likely that there will be another bug with that piece of code than other areas. If you know a section of code is error prone wouldn’t you want to catch the error as fast as possible?
A new tool in my toolbox is exploratory testing. If I was learning a new library or object in the past I would often write simple programs that would print out the state, manipulate that state, and print the result. I would then continue to learn ways to work with the objects and verify that the printed state matched my expectations. Hmmm that seems error prone, and not necessarily repeatable. Now when I am learning something new I tend to write tests against my expectations of how things should work. A great recent example of this was when I was learning to use the RightScale AWS gem so I could access Amazon’s SimpleDB (SDB). I ended up writing tests to create, save, update, and delete objects. After that it was easy to write a real DB layer for some of our objects and write additional tests for that as well.
The more I learned about testing the more useful I have found it in many situations. The issue is that I have still never bought into the idea of TDD for most development. Even though many smart people have written about the benefits of TDD (Adam@Heroku Jay Fields), it just hasn’t ever seemed worth it to me. I decided that I really couldn’t knock it if I really hadn’t ever tried it for any significant amount of time. So I decided to spend one full day completely following TDD.
I was going to be adding some new features to Devver, and thought it would be a good chance to try my TDD challenge. The features I was adding were some small actions on the server that would be triggered remotely by the client. This broke down into a few separate pieces of functionality.
1. The client would send one of three new requests based on user input and existing client state
2. The server would receive and parse these new messages
3. The server would call 3 new handlers with the encoded project data and carry out tasks
Breaking this into tests was very natural and led to nearly no debugging time as almost the first time the client and server connected the interactions all behaved exactly as expected. I didn’t waste any time looking into where a message or a response got lost or wasn’t properly acknowledged in the code. The tests had already simulated the message creation, parsing, routing as well as the event handling and completion. It is nice when you put all the pieces together and it just works, and you know it is very solid from the beginning.
To break down the tasks I wrote the tests in this order.
1. Tested creating messages to store the expected project information
2. Tested parsing messages to get the expect project information
3. Tested client inputs would call my event handlers
4. Tested that client event handlers would send the proper message
5. Tested that the server would receive and parse the expected messages
6. Tested that the server would call my event handlers
7. Tested that event handler would complete the task expected off them
After spending a day and completing all the pieces of functionality I had expected to complete I was happy with my TDD experiment. I came away with a new respect for TDD an while I still don’t think it would be well suited to all programming tasks, I can certainly see a place for it and plan on using TDD more in the future. I do think that it took me slightly longer to complete the features than it normally would have. I freely admit that the more often you do TDD development the better you would get and that likely less time would be wasted trying to think up the proper test cases. I felt that the code I wrote under TDD was of a higher quality than the code I normally write. It forced me to refactor and rework my code as I went as well as break it into small enough pieces that I could write simple tests to verify the next piece of the project. I think that the code I ended up writing will be less brittle and easier to work with in the future. As a developer, I was happier and more sure of the stability of the features I just added to the system.
I think TDD is especially well suited to situations with small communications between systems, as each independent system can be completely tested and have it’s behavior verified while isolated from the other pieces. I was surprised that working in a way that demanded more upfront costs before I wrote anything functional didn’t slow me down more. I was expecting that it would make my development process slower by a factor of two, but the truth is that a single nasty bug halting your forward progress can take up more time than you would have needed to spend initially if working with a TDD approach.
I don’t plan on trying to move over to an entirely TDD approach but by challenging myself to work in a way that seemed unintuitive to me, I ended up learning a lot and likely will use the approach in the future for myself.