I first started doing Test-driven development at my last office job. When I started, I already knew quite a bit about the philosopy & idea behind TDD, but I read through the first part of the Test Driven Development: By Example book by Kent Beck anyway, and found it helpful.
To test, or not to test
Although I think unit tests and TDD are great practices to have in your programming toolbox, I am not one of the people that thinks they should be used all of the time. In many instances, I can still produce working software faster just by coming up with a solution (in my head, or on paper) and then implementing it, than by writing tests for each piece of functionality as I go. For example, I rarely test any of the quick scripts or programs I write that I use for myself, or ones that I give to others that I don’t modify often.
As long as you can tolerate a small amount of bugs (for specific functionality that you might have unit tested), and you have yourself, testers, or users testing the product & reporting bugs, you can often get away without unit tests. If you can’t write code (in a language that you’re experienced with) quickly that works, without writing tests first, then I wouldn’t hire you as a programmer in the first place.
Probably one reason I don’t test more often in such cases is that amount of work to set up tests is greater than the return in the short-term. I’ve worked mostly with PHPUnit so far, and the amount of work to set up tests is still much greater than I would like, and I’m ready to switch to anything that makes this easier. But still, no matter how you slice it, coming up with both the input and output data for tests almost always takes a fair amount of work, at least in my experience.
Reasons for testing, TDD
Having said that, I am still a big fan of testing, and TDD. The reasons testing, and more specifically TDD, are:
Testing in general:
- Repeated manual testing sucks
- Automated build & test suite to catch regressions
- Tests are documentation of functionality – they are the real spec
- Help other developers understand existing or new functionality
- Forces you to think about design before implementation
- Makes you think specifically about what goes in & and should come out
- Avoids trap of un-tested, working software
- If you implement it first, and it works, it’s a lot harder to convince yourself to write a test afterward
So, when should you write unit tests and start using TDD? Here are some reasons:
- Team size greater than one
- Code size is large enough that you can’t keep all dependencies in your head at one time
- Long-term reliability is desired
- You will be touching & changing the code base frequently
- Users will react negatively to regressions & bugs
- You want to maintain development velocity, and not spend all of your time fixing bugs
If it’s not painfully obvious, that pretty much describes any software development effort that produces production quality software.
Should you use automated testing during the startup phase? There are some good answers already for that.
One idea that I have thought about, is to use code reviews in lieu of more extensive testing where the need for short-term speed outweighs longer-term needs that would best be addressed by tests. I haven’t explored this myself, but I’m curious about the idea. You would definitely need to have very good code reviewers for this to be effective. There’s at least one person against this idea already.
As I started working as a freelance developer, I found myself introducing TDD to other developers again. I wrote up this summary with my personal advice on getting started with TDD. We are using PHP and Git for these projects, but you can substitude your xUnit framework and version control system.
When you’re using TDD, the whole way you approach development changes. It takes some getting used to. Here’s the summary\:
For PHP, and PHPUnit, all Tests and code all go in to classes & methods, so everything is Object Oriented
- this allows you to stub methods and create mock objects easily
Write a Test for the new functionality. This typically involves:
- create input data for the function to test
- create the expected output data
- create an object or mock object for the class you are testing
- call the method and user the PHPUnit assert API to check the results
- Implement the method & functionality
- You don’t write your code until after you’ve written a test, and verified it fails
- write just enough code to get the test to pass
- commit your changes to Git
- Go back to the first step, and repeat until all functionality is tested & implemented
- Clean up & Refactor
- Once everything is running, remove shortcuts & code duplication
- Rerun your tests to make sure you didn’t break anything
Make it Work, Make it Right, Make it Fast
I go through trying to get things to work as quickly as possible, and litter the code (and sometimes my notes) with FIXMEs and TODO items. After things work, I come back and fix these one by one, rerunning the tests each time to make sure I didn’t break anything. Sometimes I need to update & fix the tests & code as I go.
You can have several tests & sets of input/output data for a single method, if it is complicated. Each test should be simple and test one part of that functionality.
You pretty much have to force yourself to sit down and write the tests first the first couple of times. Instead of thinking about how you would implement something, you have to think about “What is this code going to do for me?” – by looking at what the inputs, side effects (except purely functional languages and expected outputs will be. If you are used to thinking about what class you will implement, and what function or method will be called, then start by including the files in your test code, instantiating the objects, and calling the methods or functions, complete with the arguments they will take – instead of creating the class or function at all first.
Once you get used to it, it becomes more natural, though it still requires a lot of discipline to not try and write the code first.
One cool side effect of writing tests for web sites/applications is that I use the browser much less. I write & debug the tests, and only pop open a browser at the end to find things I missed.
Sudoku, Peter Norvig, and Ron Jeffries
Whenever I hear someone preaching about TDD, without emphasizing the importance or thinking about how
to solve the problem first, I think of this post about
how Peter Norvig builds a Sudoku solver, and Ron Jeffries uses TDD but utterly fails to come up with a solution.
(Note: I haven’t read either yet, but that’s not really the point anyway …) .
Some other posts about this:
- Thinking Inside a Bigger Box » Why TDD makes a lot of sense for Sudoko
- Vlad’s Agile Software Development Blog: TDD Is Not An Algorithm Generator!
- Discussions about TDD always make me think of Ron Jeffries’ attempt to develop a… | Hacker News
- In all fairness, how can anyone compare Peter Norvig and Ron Jeffries on the sam… | Hacker News