Unit Tests: Rethinking Test Driven Development
All developers know that tests should be written at some point in the development cycle to reduce bugs in production. And the great news is that Vue.js has some fantastic tools to aid developers in unit test creation. But are there things we have missed that will help maximise the benefits of those tools to enhance the development experience and to improve our code?
What is test-driven development?
Test-driven development is a process that many software developers use to help them develop simple, clean code. Its methodology is derived from the scientific principles of hypothesis, prediction and experimentation.
The TDD process is as follows:
- Write a failing test for one requirement
- Implement just enough code to make the test pass
- Refactor with confidence (if needed)
When we compare this with the scientific method of experimentation, we can easily see that TDD is like an experiment, but for software:
- Make an observation
- Ask a question about the observation
- Form a testable explanation of the observation
- Make a prediction based on the hypothesis
- Test the prediction
- Iterate: make new predictions
If we simplify these two methodologies, we can see how similar they are:
Test-driven development, then, is simply the process of scientific experimentation brought into the programming sphere. This again highlights how important it is to have tests. Any scientist worth their salt never assumes something is right, they always test their hypothesis to prove it. A developer I learnt a lot from earlier in my career used to say: ‘If it isn’t tested, it doesn’t work’. In my experience that has proved to be a useful rule of thumb.
TDD offers the developer several benefits:
- Eliminates fear of change. Have you ever wanted to clean up some code, but been afraid to because of the cost of time in testing the updated code? Or been afraid of introducing a bug into production? And as the codebase gets larger, they are at a greater risk when the traditional “test-at-the-end” approach is used, as bugs are more challenging to find. With a bigger haystack, it’s even more difficult to find the needle. With TDD however, we can ensure that a bug will not be introduced, because we can be sure that the updated method will behave the same way as before. Simply write the test beforehand, update the method, and then run the test again. If the behaviour has changed the test will fail, so when it passes you can have confidence that the code change will not introduce a bug. No more post deployment waits to see if you’ve broken the whole system! No more testing in production!
- A safety net for CI/CD. Test failures will be flagged on a development pipeline, so will prevent bugs escaping from the test/staging environment into the wild.
- Faster developer feedback loop. Without TDD, developers may have to (for example) load the webpage, open a debug tool, navigate to the place the code is being run and then trigger the conditions to ensure the correct method is called. With TDD, developers can run tests from the console, providing faster feedback during development and debugging sessions.
- The code is easier to maintain. When using the TDD approach, developers naturally produce more ‘pure’ methods, because it makes it easier to test. Moreover, methods are likely to be smaller, written in more digestible chunks.
- Detailed feature documentation. When writing tests for a particular feature, developers will create specific, strict, and detailed documentation as they write the tests. This can then be used by the developers when they come back to work on that component when refactoring.
- Component structure design aid. Developers can be guilty of thinking of the software implementation as a problem before thinking about the components’ requirements or optimising for re-usability. Writing tests beforehand forces the developer to consider questions like: ‘What parameters are really needed for this method?’ or ‘What props should I pass to this component’, or even ‘Should I reduce the side-effects of this method?
What makes a good unit test?
A good test needs to produce a good report when it fails. This will enable the developer to quickly find and fix the point of failure. Therefore, a good test, when failing, will produce:
- A traceback stating where the code you were testing is
- A description of what you were testing
- The expected behaviour
- The actual behaviour
For example, this is a good bug report of an api call being made to the wrong endpoint. It shows a traceback (point 1), and the title of the test is a description of what we were testing (point 2). It shows the expected behaviour in green (point 3) and the actual in red (point 4). This bug report will allow the developer to easily find and fix the problem.
Unit test template
Each unit test should be a variation on this template.
Unit testing in Vue
In Vue applications, components are the main building blocks of the UI. Components are therefore the natural unit of isolation when it comes to validating an application's behaviour. Various Vue libraries support mounting and checking the methods of Vue components, as well as ensuring code coverage remains above a certain level.
As seen in this article, test-driven design is a more scientific way of writing code. It will help developers to maximise throughput, whilst reducing costly, time-consuming bugs and regressions. I can also attest to its ability to speed up the developer feedback loop. Speaking personally, I believe TDD is a much more fun way to develop, and I hope that my team has found that it improves the cleanliness of my code.