Test Driven Development (TDD) is an industry best practice, and it's a method we teach our students from week 1 of the course.
Throughout this post, I will be using Mocha as a testing framework, and Chai as an assertion library.
If you're new to programming, you'll need to ensure you have Node installed on your machine before you read up on how to install Mocha and Chai. You can check if you have Node by entering `node -v` in the command line. If it returns anything other than a version number, you don't have it.
What is TDD?
Simply put, developers (or testers) program tests to ensure that code is performing as expected. For example, if I have a function called `double`, which takes a number and multiplies it by two, I might test that, when given 2, it returns 4. Sounds simple right?
Written in code, that test would look like this:
If the double function is working, when we run the test above we should see something like this:
Why do we do it?
If you're just starting out programming, the previous example seems ludicrous. Is it really worth that extra code for a green tick? What's wrong with our trusted friend, the console.log?
Testing isn't really about green ticks, satisfying though they are! It's about ensuring that your code is always doing what you expect of it, even after you make changes. Consider the following: you're working as a developer professionally, and you've been asked to write a function which works out valid phone numbers for your website. Several weeks pass, and you're told that some new types of numbers are now valid. You have to update your function to reflect this.
If you didn't test, you're in trouble. This is your code, but it's not exactly fresh in your mind. You know it all worked with console.logs last time, so you can just tweak it and add in logic to handle the new valid numbers. Once you think you've done that, you can either hope all the original numbers work as expected, or retest the whole thing with console.logs.
In the sliding-doors reality where you did test, you can write a couple more tests for the new numbers, and then attempt to add the functionality. If all your tests past, hey presto! You've done it. You've updated the code without breaking what you wrote before. If they don't, then you'll know exactly what area you're failing in.
A more effective workplace
It's not just about updating your own code either. If you're working as a developer, it's highly unlikely you'll just be working on your own code. You'll often inherit code written in an unfamiliar style, provided without comments. If you're asked to add additional functionality to code like this, then you'd better hope there are tests. Because otherwise you're going to spend a lot longer working out what's going on and trying not to break things than actually making changes.
Testing isn't about making time-savings in the immediate short-term. But it pays dividends. Whether you're working on your own, or working as a developer in a busy organisation, testing yields excellent returns on small investments.
Now that we know what testing is, and why we should do it, let's look at TDD. TDD is the principle that testing should be done actively, rather than retrospectively. We should write tests before we've even written a line of code, see them fail, then write the code necessary to pass them. This is called the 'Red-Green-Refactor'.
Let's do this with a simple function and tests. Let's say we're building a function that sums all the numbers in an array. If it's not given an array, or there are no numbers in the array, then we'll return 0.
To begin with, we should write a test to ensure that we return 0 when our function is given an empty array. This should look something like this:
In our describe block, we name the function we are testing (sumArray). In our it block, we name the test we are making. In our expects, we lay out the expectations, which must all be correct in order for the test to pass.
When speaking in terms of TDD, the simplest way to get this particular test to pass is this:
This says that, no matter what we call sumArray with, it will always return 0. Clearly this isn't our end goal of our function, but this is the very nature of TDD. We increase the complexity of our tests step by step, and write the necessary code for them to pass.
Now we need to increase the complexity of our test cases, with our end goal in mind. Our next it block could look something like this.
Now we need to write a function that actually traverses the array and sums the numbers. For this we could write a for loop, but let's do it with a reduce. Because our first test took an empty array and returned 0, a reduce with an accumulator that starts at 0 should also still work.
So here we reduce, summing each of our array's numbers into our accumulator (acc) and returning the final accumulator.
Our final test, to ensure our function is working as it should, should be running it with an array containing non-numerical values. Ideally, it should just ignore them, while counting numbers if they're apparent.
In order to get this test to pass, we once again need to modify our function. We'll need to update our reduce to handle non-numerical values. Our final function will look like this:
Break it down
Hopefully you can see that, along with providing tests for the long term, TDD helps you to break the problem down into smaller chunks. This means there's a greater chance of not forgetting important, code-breaking edge cases, and not diving in too quickly, and trying to solve the entire problem at once.
If you're interested in learning to code with Test Driven Development, you should take a look at our Full-Time and Part-Time courses!