This article provides an introduction to test-driven development and its core ideas (TDD). Testing as you go (TDD) is a software development life cycle best practice that each agile software developer should use. Find out what test-driven development is, how it works, and why unit tests are so important to TDD. Take away an appreciation for test-driven development and the many benefits it may bring to your workflow.
What is test-driven development?
Traditional software development and testing are turned on their heads in test-driven development. The idea behind test-driven development is to write the test first, then iteratively modify the code until it passes the test you prepared.
Through TDD, you’ll create a failing unit test first, then make adjustments to the code until the test succeeds. It’s a bit of a backwards thought, no? However, when you employ this testing strategy, the code you create is more streamlined and robust.
Unit tests are tests that focus on a specific subset of logic, such as an algorithm. Determinism in unit testing is essential. In this context, “deterministic” refers to the absence of unintended consequences, such as the return of unexpected results from an external API request. Mock data would be used in place of real data that might evolve over time.
Five steps of test-driven development
There are 5 steps in the TDD flow:
- Read, understand, and process the feature or bug request.
- Translate the requirement by writing a unit test. If you have hot reloading set up, the unit test will run and fail as no code is implemented yet.
- Write and implement the code that fulfills the requirement. Run all tests and they should pass, if not repeat this step.
- Clean up your code by refactoring.
- Rinse, lather and repeat.
Figure 1 shows these steps and their agile, cyclical, and iterative nature:
Red-Green-Refactoring is a common name for this process since it reflects the results of the tests run during the cycle.
- The red phase indicates that code does not work.
- The green phase indicates that everything is working, but not necessary in the most optimal way.
- The blue phase indicates that the tester is refactoring the code, but is confident their code is covered with tests which gives the tester confidence to change and improve our code.
Test-driven development and CI/CD
Set up your tools, toolchain, and IDE first
You must prepare your tools, toolchain, and integrated development environment (IDE) before beginning test-driven development. We’re building a Node.js demo in our [code pattern], so we’ll be using these primary resources:
- nvm (Node Version Manager) for Node.js and NPM: NVM allows you to run the Node.js version you want and change it without affecting the system node.
- npm libraries for development:
- Jest for unit tests
- ESLint for linting
- Prettier for formatting
- Husky and lint-staged for precommit Git hooks
How to write unit tests that fail
Unit tests can fail in a number of different ways.
- Create a test that makes use of a nonexistent function in the code. As a result, the test will fail with a “not found” error (for instance, a 404 error).
- Change the assert statement so that it no longer succeeds. An important part of any unit test is an assert statement, which specifies the expected result of the code being tested. The request for new functionality or an adjustment to an existing issue should be reflected in the assert statement.
To make it fail, you could, for example, create an enriched data structure and then write an asset statement that returns an unexpected result. Your JSON may already return a person’s name, but the new expectation is for that information to be accompanied by the person’s cellphone number. First, you’d make the assert statement invalid by omitting everything save the person’s name. Then you’d append the appropriate code to the end of the expression to include the person’s phone number.
Also, in practical programming: Assert actualResult == “track”:”foo fighters” could be one such statement. The 404 will go away once the code (function) is connected, however the final result may be a null object. You enter ‘track’:’foo fighters’ as the result into the function as a literal value.
In other words, everything is OK to go now (Green!). There is no denying that the code is a placeholder at the moment, but the fundamentals are there. All connections between the test and a specific place in the code are solid. Then you can do things like access a file, database, or API to perform real business logic.
Deciding when to write unit tests
In general, there are two cases for when you’d write unit tests:
Case A: In order to implement a feature request, you must first create a unit test for it. A user may want to know how many countries are supported by a given currency exchange. A failed unit test is the first thing I create. Then, I make incremental adjustments to the code until the unit test succeeds.
Case B: There is a problem with production code, and it has a bug. This flaw causes a problem that needs a remedy or patch to be applied. To review, when the customer runs the code manually, they anticipate $USD to be used in several nations, but instead, they get a single country back.
A failed unit test is the first thing I create. Next, I make adjustments to my implementation code until the test succeeds. This not only corrects the code and gets rid of the bug, but it also provides me with a unit test I can use in the future to make sure the code is still solid.
Unfortunately, test-driven development isn’t widely used by programmers. Better, more robust, and more resilient code is produced through test-driven development. With any luck, this blog post has helped you grasp the TDD principle and put it to use in your own software development process.