TDD is an investment in the future.
There are operational checks to most systems, and in the world of software engineering, test-driven development can help maintain those checks, and in turn, code. Test-driven development (TDD) is a software engineering technique and practice that takes a test-first approach to writing code.
TDD requires unit testing, which is where individual units or components of a software are tested before the code they are supposed to validate. The point is to test the smallest part of any software, with only a few inputs and a single output, in order to validate that each unit of the software performs as designed. Unit testing is the first level of software testing performed, usually prior to integration testing. Unit testing is important because the cost of fixing a bug during unit testing is less than fixing bugs at higher levels of testing.
In a test-driven development cycle, an engineer writes a unit test that defines a desired function, produces the minimum amount of code to pass that test, and then restructures the new code to satisfactory standards.
For example, a TDD cycle will look like the following:
During the 1990s, test-driven development was originally formalized by Kent Beck as one of the pillars of Extreme Programming (XP) methodology. XP is a specific, agile framework regarding engineering practices. Its maxim: to produce higher quality software as well as a higher quality of life for engineering teams. XP’s five values are communication, simplicity, feedback, courage, and respect.
Today, however, TDD is recognized as its own discipline with a focus on the continuous improvement of code. As a result, investments made on tests should save time and money in the future when adding new features or re-writing operations. Yet, in order to maintain a return on investment (ROI), it’s crucial to keep test code clean, organized, and dense in quality. For example, ask yourself, is it easily readable? Is it concise? Does the test say as much as possible with a minimum amount of code? To keep tests concise and clear, unit tests should contain only one output.
While test-driven development can seem time-consuming, it allows for problems to be exposed early, saving time and reducing the cost of resolution (and bugs) in the long run.
TDD can seem like a lot of upfront work, but when done right, it can lead to maintainable, reusable code that also makes the addition of new functionalities easier—which can mean an increase engineer productivity overall. TDD also encourages engineers to refactor and continuously improve their work.
According to Kent Beck’s book on TDD, teams and TDD practitioners report:
The test-driven development approach is a big shift for engineers in how they tackle coding. It’s necessary to allow a significant amount of time for engineers to learn the TDD approach. This can seem costly upfront, but when there’s proper onboarding for test-driven development, the long-term benefits increase as the engineer gets more comfortable refactoring code and making sure it’s reusable for others.
However, it’s also up to the engineer’s ability to create a roadmap and document repository in the form of planned tests. Do the tests have executable specifications and application documentation? Tests need to describe the requirements they’re validating.
Typical engineer mistakes include:
Typical team pitfalls include:
Aside from the basics of a test-driven development cycle, a clean test should follow 5 rules that follow the acronym FIRST:
Test-driven development requires unit-testing—an important first step—but there are four levels of tests that are necessary to run afterwards in order to make sure your code and software all run smoothly. Integration testing, the second level, comes after unit testing. Integration testing is where individual units are combined and tested as together as a group. This is to test whether there are any bugs between integrated units. Third is system testing. System testing tests finished and integrated software in order to examine the system as a whole and if it meets the specified requirements. Fourth is acceptance testing, where a system is tested to determine if it meets business requirements and if it’s acceptable for delivery.
A chair is a good analogy for how to think about these four levels of testing. When a chair is manufactured, the legs, arms, backrest, upholstery, and seat are built separately and unit tested separately. When two or more parts (or units) are ready, they are put together and integration testing can be performed. When the chair is put together and integrated, system testing is performed. When that is done, acceptance testing confirms the chair is ready for end-users.
In order to ensure code is up to par, you can also run smoke tests and regression tests. Smoke tests ensure that the most important functions of system work. Regression testing ensures that any changes, bug fixes, or enhancements to the software have not negatively impacted it.
Test-driven development can keep your team’s code clean and organized, and save time in the long-run, but the long-term benefits of test-driven development reside in whether engineers have enough time to learn the process for TDD and whether they can wholly adopt and practice testing and refactoring.
With TDD, it’s important to remember the following:
TDD is an investment in the future and can ultimately help engineers be better overall programmers. It saves time, makes refactoring code easier, allows for better design, ensures living documentation and that your code will work. As with anything, practice is important.
Amanda Roosa is a freelance writer. You can find her work on AngelList, Zendesk, and Data-Driven Investor.