Test first is fun!
Monday, September 08, 2008
So the Test-Driven-Development and Extreme-Programming people tell you you should write your tests even before you write the actual code. "Now this is taking things a bit too far," you might think. "To the extreme, even. Why would I want to do this?"
In this post, I'll tell you my answer to this question. I now really do want to write my tests first...and here's why!
After many years of writing code without using or writing unit tests, I took a colleague's advice and read Kent Beck's "Extreme Programming Explained." I picked "write tests first" as the first XP practice to try out in my daily coding.
The practice is: Write a failing test for each feature you plan to implement. Run the test and see it fail. Then implement the feature until the test succeeds. Refactor now and begin again.
Why write the test first? The obvious reason, I thought, was to make it more likely that tests will get written at all. But I heard the promise that this was not just a way to ensure tests aren't overlooked, but a way to higher productivity. I tried it, and found that getting tests written was indeed one of the less important reasons to write tests first!
Writing tests firsts leads you to think about the interface first. Of course, you do that anyway when you write the header file with the C++ class definition or when you write a Java interface before you implement any methods. However, writing a test lets you focus on how the new interface will be used before even writing the interface. You could call writing the interface the supply side and writing the test the demand side of the deal. Writing the test first, you set out with the customer's or user's view of the new class.
Another way of seeing the same thing is to regard the test as a coded specification. In the test, you specify what service the new class or feature should provide, and you specify, by example, the syntax with which this service will be requested. In contrast to specifications written in natural language, a specification written into a test contains a technical safeguard against growing stale: if it does, the test will probably fail.
These two aspects of unit tests are enough to make me feel excited about writing them first. Tests are no longer a necessary chore, but the place and time where I start to design something new. That's what I love to do. How soon can I get started writing my next test?
But this is still not the best part: If I write a test first, run it to see it fail (often even fail to compile), and write the code to satisfy the test, then I have everything in place to see my code running the minute it is written and compiled! No more dread of strange behaviour or system crashes the first time I launch the system with my new code! No more laborious navigating through the application to my code's feature! No more wondering: Did my code actually get executed or not?
Just a quick run-the-testcase, and I know how my code runs: green - good. Red - not yet good. Read failure message and fix code until green. Debugging sucks, testing rocks, indeed!
Of course, all the complex issues of integration and system testing remain. Good unit testing gives me a good head start for integration, but I might still be in for unpleasant surprises there.
The point I want to make here, though, is about my state of mind when I write new code. For me, writing new code for complex systems was always accompanied by fear: fear of crashes I'd have to debug, fear of creating bugs I might not discover, fear of the dreary work of searching for bugs I might have created. Fear that took up a considerable amount of my mind space and slowed me down.
Now, this fear is gone! I happily go about writing my code because I know the tests are already in place. It will cost me just a few keystrokes to run my finished code, and I will immediately see what it does. Hooray, I wrote a program, and it works, and it's easy to prove it!
It's the same old enthusiasm that I felt more than 20 years ago when I wrote and ran my first programs. Many of you have felt it, too - the joy of inducing some life into this dead piece of hardware through our written word. And now this joy sits in my mind again where fear of crashes was before. You'd better believe that speeds up my coding! Want to give it a try yourself?
Our team occasionally leverages TDD, and we certainly write unit tests for all our code (even if not as TDD). However, one area we continue to struggle with is how TDD applies to UI-centric code.
ReplyDeleteIt's easy enough to write a test for something functional or algorithmic (lots of inputs and outputs to check). It's not necessarily straightforward to write a test for UI code, before the code exists.
For example, UI unit tests often test the state of visual widgets to see if they were updated properly by some data. However, you may not have decided on the type/name of each widget before writing the code, and so the test can't be meaningfully constructed.
I'd be interested to know if you are applying TDD to UI-centric code, and if so what your recommendations are.
I found a bug on the first day of release of Google Chrome.
ReplyDeletewhile entering :% in address bar it crashed. So strange.
Really a BIG ad for tdd, but I love it!
ReplyDeleteThanks for your explanation.
great question vvagell...anyone have any thoughts on TDD on UI code?
ReplyDeleteOn TDD for UI code. We've had a great amount of success doing it. Our biggest gain was from separating out the mechanics of the testing from the testing itself. We did this by creating a simple scripting language, which allows us to specify tests like:
ReplyDeleteSetup dataset1
Load Homepage
Set element name to foo
Click go
Check message is "You typed foo"
It was a significant investment, but has paid dividends. We're testing web based apps, the script gets translated into PHP which drives a browser with COM and uses SimpleTest to make assertions. The good thing is that we don't have to worry about this complexity when writing the tests.
Another reason to make sure the test fails first is so that you're sure it's testing the right code. If you write the code to add a feature first and then the test, a passing test doesn't prove the new feature is being exercised.
ReplyDeleteI've never read an article on TDD that actually made me want to try it. It always seemed like so much trouble for very little gain. The way you explained the advantages makes it sound like fun! I just might try it. Thanks!
ReplyDeleteI have recently started looking at TDD and one of the challenges that I faced is testing private methods. I am actually curious how do you guys go about this?
ReplyDeleteI came up with a solution that leverages aspect oriented programming, but I am not sure what would everyone else think about it.
http://www.khussein.com/wordpress/?p=38
Thanks,
Khaled
Khaled, we use Reflection to test private methods in Java.
ReplyDeleteHowever, it's always a judgment call of *which* private methods should be unit tested, since many are covered thoroughly by testing public methods.
Here are some general guidelines:
* Does the private method include one or more corner cases that would be challenging to test via a public method that routes through this private method? If so, maybe unit test the private method.
* Does the private method encapsulate an algorithm with many input/output possibilities, which would be challenging to validate without direct inspection of its return values? If so, you might unit test the private method so you can easily exhaust dozens or hundreds of inputs/outputs.
* Does the private method only get called under exceptional circumstances by public methods, that would be difficult to reproduce in a unit test? If so, you might just unit test the private method.
I know there's a bit of a holy war around whether or not to unit test private methods (and there are certainly architectural approaches you can take from the outset, to avoid the three points I listed above), but I think it's reasonable in some cases.
From a technical perspective, check out Reflection in Java, or see if your language of choice has something similar.
Hello @ all! :-)
ReplyDeleteTo the subject: "TDD with UI-centric code":
You could try out this approach: Presenter first
The thing with private Methods: Is private method an antipattern?!
I hope, that I could help a little. :-)
best regards, Christian