A few tips for switching existing projects over to test-driven development
It’s not test-driven development (TDD) if you write the tests after the fact. But is it possible to gradually switch an existing project over to TDD? Or do you really have to discard everything you’ve done so far and start over from scratch?
In the case of a hobby project that only you yourself use, starting over from scratch is something to think about. But for a program that even just a few people are already using, starting over from scratch is probably off the table.
And even if you’re sure you’re the only one using your program, a total reset might seem like a dreadful idea, especially if it’s a big program you’ve managed to put together in what little free time you have.
This is something I have a little bit of experience with. Working on a program to draw diagrams of prime numbers in the complex plane brought me the realization that automated testing was not only necessary but required.
And after taking a course with an emphasis on TDD, I realized that not only is TDD more natural, it also makes programming a heck of a lot easier. Sometimes extremely easy.
Regardless of the actual use cases of your program, it is worthwhile to switch the project over to TDD, whether by a hard reset or a gradual changeover. It is worthwhile to go from putting out fires to foreseeing the fires and putting them out sooner rather than later.
So here are my tips for gradually changing a project over to TDD:
- Quickly write simple unit tests for your program’s existing features that you think your program should pass easily. Write them consecutively, don’t run them one at a time. This is not really in the spirit of TDD, and worse, it might feel like a chore. But it will make you less averse to make changes to the existing features of your program (which is, after all, one of the benefits of TDD), and will provide an important baseline for the testing of new features. Don’t exempt getters and setters from this (they’re freebies here, so to speak), but maybe do exempt graphical user interface (GUI) elements. This tip doesn’t quite apply if you already do have tests for the old parts of your project, even if you’ve been testing after the fact.
- For each new feature you want to add to your program, write a test first. Or, if applicable and desired, write a stub first (it should be a stub that will obviously fail the test). Here it won’t matter that you didn’t begin the project as a TDD project. Of course at this point it’s important that you have tests for the existing features, even if those tests were written after production rather than before, so that you can quickly verify that your new features don’t break the old features.
- For improvements to existing features, write the tests first. If it really is an improvement, the test should fail the first time. If it passes on the first run of the test and it’s a valid test for what you intended to test, then it’s a reminder that the project did not start as a TDD project. And that’s okay.
- If you notice a mistake in your existing source, don’t correct it just yet, write a test first. Previously unnoticed mistakes can be a great opportunity to switch over to the TDD mindset. A mistake that has so far gone undetected probably means that there has been no motivation to correct it. Probably so far it has done nothing more than waste a few bytes of memory and a couple of CPU clock cycles. It could now be an opportunity to write a test that will fail on the first run. For example, suppose you notice that a function that returns an array of numbers has a line that says
approxs = floor(x)is followed by
approxs = floor(x). Probably you meant the latter to take a ceiling rather than a floor again. Your first instinct would be to correct that mistake right away, since it’s just a simple typo. But then you’d be passing up a chance to practice the fail-pass-refactor cycle on an actual program that you’ve written.
- If something in an old part of the program causes a test for a new part of the program to fail, don’t correct it just yet, write a test first. I know from personal experience that a mistake or omission in an old part of the program can go completely undetected until it causes a new test for a new part of the program to fail. And just like in the previous tip, don’t correct it right away, write a test first, specifically a test for the old part of the program that caused the problem. I suppose this sort of thing can happen in a project that started out as a TDD project, but it is much likelier to happen in a project that didn’t start out that way. You might disagree, but I don’t think it’s worthwhile to spend too much writing tests for the old parts of the project trying to find previously undetected problems. This would make the changeover to TDD too much of a chore. Better to forge ahead, and deal with the previously undetected problems only when they cause new problems.
- If you change a private function to public, write a test. This one is inspired by an opportunity that I missed. If all your tests are passing before you make this change, it probably means that the previously private function worked correctly for what you needed it to do when you thought it needed to be private. But now that you have realized its wider applicability, there likely are scenarios that you hadn’t thought about before and haven’t tested for. So now is the time to think about whether or not the now public function might be called upon to do something it was not quite tested for. If that’s the case, write a test. Don’t yet amend the function to cover the new scenario you just thought of. Depending on how the test goes, you might not even have to make any changes.
- When a new test fails or causes an error, don’t immediately assume that it’s because of a problem in your newest production code. The first tip was about balancing a need to get into TDD more quickly than would have been possible if you had slowed down to make sure the existing project was thoroughly tested. The benefit of switching to TDD like that outweighs the potential inconvenience of a misleading test failure, in my opinion.
- And lastly, accept that sometimes you will neglect to write a test first even though you had both an example and opportunity to do so. TDD is a habit, and like dieting and exercise, there are times you will fall off the wagon inexcusably. Such as times when you notice that you already had a test for something very similar to what you neglected to test first for. Don’t beat yourself up when that happens.