MKS Week 2: Test-Driven Design (TDD)
But enough about me! Let’s talk about code. Or, more specifically, let’s talk about what you can do before you actually write project code.
WHAT? Things before code? Say it isn’t so! Why would I care?
As an answer, let me present to you a common scenario:
Everyone who codes knows the feeling of starting a new project perhaps a little too excited, rushing headlong with no plan into a whole bunch of code. ‘I know exactly what I need,’ we might tell ourselves. ‘It’ll take maybe an hour.’
We also all know the feeling, many hours later, of staring at a bunch of code that mostly all works but is garbled, convoluted, and messy, while trying to write that one final piece without breaking everything else.
Ever start to feel like you’re working against yourself?
It’s frustrating, it’s exhausting, and it doesn’t make good code. It can easily make a beautiful passion project into an exercise in futility, doomed to never be finished or realized to its full potential.
Isn’t there another way?
This is where test-driven design comes in. It does mean a little more up front, but it creates a simple and easy process to make sure your code never gets off track, and your project never bloats into a terrifying, unstoppable mess.
The idea is, before you write any project code, come up with a basic test of what you would want the first step of the project to do. (There are plenty of good testing libraries out there. I recommend Mocha, with Chai as an assertion library, because it reads so nicely, but there’s several good ones.) Don’t think too big here: think the smallest divisible chunk you can imagine. It can be as basic as:
- ‘It should call for data from the server and deliver the specific piece of data I want.’
- ‘It should get input from the user and save that input in a variable.’
- ‘It should take an object and give me a modified object.’
Try to limit it to one basic operation. If you get too complex at the start it’s easy to fall back into the mode of adding too many fiddly add-on widgets or getting lost in the line-by-line logic of the code.
Then, once you’ve written the test, write only the code that you need to make that operation work. No more code is allowed than what you need for that one test. It can be hard to stop yourself, once you’re in the code-flow, but you need to pull back as soon as you can make that single test pass.
Then, take a quick glance at your whole code and think: ‘Now that I’m looking at the bigger picture, are there some small tweaks I can make to make this better?’
Anything to make it simpler? More semantic? More legible? Clearer? Make it require less memory from the computer (think for a second about your big ‘O’ values, if it’s an app that might end up being a memory hog).
After you’ve done that (and made sure the fixes didn’t break anything), then you write another test. Rinse and repeat, ad nauseam.
Your basic workflow becomes:
- Red: Write a test that is failing, because you haven’t written the code for it yet.
- Green: Write only the code you need to make that test pass.
- Refactor: Look at the code you just made and see if you can improve it. No extra functionality, though, just to make it do what it needs to pass the test in a clearer, better way.
This system guarantees that you will always build an MVP (minimum viable product) before you get lost in the bells and whistles. This guarantees that everything you are doing is working. If something breaks, you already have a whole testing suite to see which parts are broken. It forces you to write clear, modular code that can be easily reused. The system of tests actually helps you to remember to pull your head out of the code and see the bigger picture. It’s (almost) all the things that you always say you want to do when you code, yet they’re so easy to forget.