In a previous life before programming, I worked as an electrician. I liked electrical work. Still do. You’re either right or you’re wrong or so I thought when I was starting out.
To some extent this is true, a freshly wired light turns on or it doesn’t.
But often on projects in older homes, I discovered that repairing one problem could lead to surprising results elsewhere in the house. I would follow the building code and make my best judgments, but ultimately I was layering my work onto decisions that an electrician made decades ago. I could only see the wires coming in or out of the box I was working on.
The same issue exists in software development on a much greater scale. Dependencies can be interwoven in unexpected ways, features are deprecated over the years. Enter test driven development.
If the essence of an existing project is successfully captured with tests, any new bug created in the process of adding a new feature or performing maintenance would appear as a failing test, represented in angry red font in a browser. A programmer should analyze the test to identify the issue, devise a plan, and write code with the single goal of flipping the test to green. During this phase, resist the urge to refactor freshly written code if it doesn't directly lead towards passing the test.
When the test is passing again, that is the time to refactor. Examine your work. What could be improved for performance or readability? Keep an eye on your tests as you refactor and repeat the process if necessary. Test driven development describes this style of programming that could also be called red, green, refactor.
I first encountered tests in an educational environment where the tests were already written out for me. It was my job to pass them. Tests made sense in this context. They make sure a beginner stays on track and tackles a larger problem in small pieces. As I moved onto more advanced, open-ended problems, I learned that the pre-written tests often contained hints towards how the program should be written. Functions that should be called at specific times or the data type that should be returned by an unwritten method.
It took me a while to realize that the process of writing tests could benefit all levels of programming. I also didn’t realize that you would ever be writing tests for yourself.
There are huge advantages that come with carefully crafted tests. In fact, on any project, it should be the first thing you do! Testing provides a written record of the intended functionality of a project that can easily be viewed in one place. But even in a greenfield or ephemeral project, writing tests is easy enough and provides structure to keep you working towards deliverable goals.
“It” functions are your unit tests. Each it function should test a single aspect or component. Similar to “describe” functions, “it” functions take a string and a callback function. The string should be the name of the test. The callback function is usually anonymous and should run any necessary assertion statements. Mocha has many more features including hooks which are especially useful if you are testing features that work in parallel. Rather than repeating the code necessary to set up the parallel tasks, you can use a “beforeEach” hook to provide the same setup for each unit test.
Chai is a test assertion library that complements Mocha. Think of Chai as the code that invokes the tests. Think of Mocha as providing the box to hold the test and display the results. Chai expect() functions can be chained with a vast array of methods to test various qualities of code. They also come with language chains which are blank methods that make tests more readable.
Check out the following example that includes the features discussed above. The describe section will encapsulate any tests related to “boxyDancer”. Here I set up some initial variables and call in the chai assertion. Before each unit test, a new boxyDancer will be created. I’m also making use of the Sinon library to track if a method has been called. The expect statement is the last function called in the unit test. It will test its argument against the assertion at the end of the language chain. In this case it is testing if the value located at boxyDancer.$node.animate.called will resolve to true. The .to and .be methods are simply language chains that don’t affect the assertion.
Here is what the tests look like when hosted in Chrome. You can see that this section will relate to “boxyDancer”. The assertions within the it statement are also shown for reference. Any hooks or setup code in the describe section will be hidden.
And finally, here is the same test before it was passed.