I'm a big fan of Test Driven Development (TDD). I'm a true believer in the Unit Test and Test Automation. I am a cheerleader for the process of start: add test, run tests, write code, run tests, fix code, run tests, goto start.
That is what I preach. But what I do on my own, in my heart of hearts, is RDD: Rewrite Driven Development.
Lucky for all of us, I don't write production code at work. As an Engineering leader I preach the benefits of TDD and sell it to our software engineers and our product managers.
Writing unit tests and automating builds leads to higher levels of productivity. You might feel like you're wasting time, but over the long term you're saving time, because your code will be more correct, better designed, safer and faster to change. You'll write only the code you need and that code will be reasonable—as in you will be able to reason about unit-tested code with logic as opposed to the risky probabilities you resort to when thinking about changing code without unit-tests.
I believe in TDD because when our engineering teams practice it I have observed all of these benefits and more. I've read the arguments for TDD in books and blogs by the super smart people I respect in software. I have a lot of observational and theoretical knowledge of TDD. And yet at home, I practice RDD. Its like I'm a vegan at work and eating Quarter Pounders with Cheese at home.
At home I'm coding for pleasure. I start many projects and finish few of them. I work alone. I code to relax, to try out a new technology, to discover new insights that I can share at work.
TDD seams like a whole lot of pain when coding for pleasure. Writing good unit tests is hard. It's hard to see your own blind spots. Most of my code is going nowhere and I don't have much to devote to coding so why waste time maintaining a bunch of tests and waiting for the tests to compile and run?
RDD is great because I'm always drawing on a clean sheet of paper. Even if I have to write the same utility function again I code it in light of having done it in the past. So obviously, I must be coding faster, harder, stronger.
And yet, look at my personal productivity... "I start many projects and finish few of them. I work alone."
Maybe the reason that I start so many projects and finish so few of them is that I'm not doing TDD. Maybe the reason I work alone, in the age of social coding, is that without TDD my code is so bad that I'm embarrassed to share it on GitHub. Thus, I've decided to experiment and abandon quick-and-dirty RDD for slow-and-clean TDD with one of my own projects.
Last year I wrote an iOS app for my own amusement and released it to Apple's App Store. You've never heard of my app. But that's ok; it's not for you; it's for me. The app is called Emoji Tac Toe and enables you to play Tic Tac Toe with Emoji.
I was very proud of myself... but I didn't write any unit tests!
This year, after Apple's WWDC, I wanted to play around with all of Apple's shiny new toys: CoreML, ARKit, Swift 4! In the past I would start a new project and write an app that used these new SDKs and language features from scratch. But gee wiz, another half written app cluttering up my Dev directory while my little Tic Tac Toe app slowly decays in the App Store. Maybe the right thing to do, the best way to learn about CoreML, ARKit, and Swift 4, is to implement them in an actual app, an app that other people can download? And on top of that, since my app is not about the benjamins, why not open source the code and enable other devs to learn from my experience?
Great plan! Then I look at my app's code. It was a total mess. You can take a look for yourself here: Pre-unit tested Emoji Tac Toe
The main game logic is only 358 lines of code in a single file. But I had written that code almost a year ago. I had written it quickly and by myself. I got the game working and published it to the apps store and forgot all about it. Looking over the code now, I realized that some of it didn't make sense. In the mad rush to get something, anything published I didn't want to mess with something that seemed to be working.
At this point I would have started over. I was very tempted as 358 lines of code is not a problem to rewrite. And I was sure I could do a better job. I'm almost a year older and wiser!
But no! I had to get off the RDD hamster wheel! The proper way to move a legacy software project forward is not to rewrite it from the ground up but to go back and write all the unit tests. Then go forward using TDD to add the new features.
That's what I'm doing! I'm eating my own cat food (I'm a cat kind of person).
After a couple of weeks of writing unit tests for this simple little app I am totally stunned. I'm amazed my code worked in the first place! I had no idea I was such a thick-headed programer. I'm amazed balls that a couple of dozen unit tests turned my crap code into something much less crappy.
After writing unit tests, I am totally not embarrassed of myself and I can share my code with confidence with the whole world! You can see the unit tested code here: Post-united tested Emoji Tac Toe
Emoji Tac Toe is not only unit tested, it's documented. As I wrote each unit test, I had to figure out what each function actually did. And one function,
aiChoose(_ gameBoard: unpredictable:), I had to break down into 10 functions. As I tested and refactored I fixed several nasty hidden bugs and found ways to make my code more predicable, reliable, and concise. I also came up with about 15
\\ TODO:s that I can tackle later.
My unit tests, which you can find here, Emoji Tac Toe unit tests, are not perfect. Some just test for NIL or not NIL. If this were an app that handle people's money or pacemakers I would spend much more time with the design and implantation of the tests. But part of the philosophy of TDD is not to over do it. Do it just right.
Watch out world! Now that I've instrumented my tic-tac-toe game with a proper set of unit tests I'm going to add cool new CoreML, ARKit, and Swift 4 features for fun and edutainment purposes!
The only path to keeping old code valuable is the path of TDD. Because of Moore's Law and Venture Capital, old code starts to die the moment you type it into Xcode. But we do our best to fight progress. We run our code on outdated OSs and older hardware. And new OSs and hardware bend over backwards to remain compatible with our old code. We think we're being smart and cautious. But all we're really doing is slowing down the march of progress and enabling the kids on Kickstarter to run rings around our old code with apps and services that take advantage of new paradigms.
TDD won't solve all our code problems. But unit tests make it much easier to fight RDD and practice what we preach!