Whether you are a software company selling your application, or you are using software to help run critical functions of your business, the goal of a software application is to provide measurable business results. During the development of a software application we often hear the engineers discussing terms such as test-driven development (TDD), unit tests and code coverage. What does that all really mean, and how does it translate to business value for your company?
As we create shippable software at Ascendle, the proper way to test the product is at the forefront of our minds. After all, production bugs – found by real users in the real world – are indeed a 4-letter word. As a product manager, I want to know that my teams have tested the application every which way possible, so that I feel confident that I can ship a stable product to my customers. There are various testing strategies that can be employed, and using a combination of strategies is the best approach to ensure quality.
Acceptance Tests
Acceptance tests are a logical first step, as they translate the acceptance criteria for a user story into scenarios that the QA engineer, product owner and software engineers alike can understand. As described in our blogpost about ensuring quality with acceptance tests, they allow the entire team to visualize the behavior before it’s built, and provide specific examples for the developers as they begin writing code. The acceptance tests, written in a readable format, are used when running manual tests as the feature is developed.
Unit Tests
Relying solely on manual acceptance tests is not efficient enough to complete testing of the entire product as it is being enhanced, especially once it has grown. Enter unit tests. Unit tests are incorporated into the codebase as the developer writes the code. They are automated tests that exercise a small part of the software to validate that it works as expected. With development tools in place to exercise the automated tests, as changes are made and committed, the unit tests are run to ensure that none of the functionality has been broken.
The Test-Driven Development Process
Now let’s understand what test-driven development means. In a nutshell, the technique of test-driven development means that the automated unit tests are written BEFORE the code is written, as opposed to after the code is written. By writing the unit tests first, developers are forced to slow down a bit and think – to understand the requirements from the user’s perspective. They do so by referring to the acceptance criteria in the user story and the written acceptance tests, both of which should be focused on behavior and business outcomes.
The process works like this:
- Write a unit test for a scenario. Since there isn’t a single line of code that exists, the unit test will fail.
- Write the smallest amount of code that will make the unit test pass.
- Enhance the code, using the unit test for reference, to make it more robust.
This is known as “red-green-refactor.” The first unit test will fail (red), after the code is written it will pass the test (green), then it is enhanced (refactor).
How Does Test-Driven Development Contribute to my Project’s ROI?
At first glance, one might think that this technique would render software development more time consuming and therefore more costly. To the contrary, TDD will save time throughout the development cycle of the project, which can be directly traced back to ROI. It more quickly gets us to what we care about most: business results.
TDD allows developers to “fail fast,” providing a very short feedback loop to know if their code is working correctly. Developers can self-certify their code before anyone runs manual acceptance tests and before a peer code review is done. This shortens the turnaround time and results in fewer defects found during manual testing.
TDD also supports an agile approach of constant change. Most development teams have multiple engineers working on the same codebase at one time. As one developer working on a new feature commits their code, continuous integration tools will detect that change and automatically run the unit tests, testing all code in the application within minutes. Any failures are immediately reported to software developers, and the team will know that it needs to be fixed, before it goes through manual testing or integration testing, and long before it gets into users’ hands.
TDD saves time every step of the way: for the developers, peer code reviewer, tester, and even the product owner, who is the last team member to certify that the feature is shippable.
Having trouble getting your developers to write documentation? Good unit tests can actually do double duty as project documentation. A peer reviewer can read the tests and understand what the code should do.
The term “code coverage” refers to the percentage of code that is exercised by unit tests. With a TDD approach, the result is a greater degree of coverage. If unit tests are not written first, they can be intentionally skipped or otherwise forgotten, leaving a hole in your overall testing strategy.
Can TDD be applied to legacy systems as well as greenfield projects?
With a greenfield project, it is easy to use TDD from the start, resulting in excellent code coverage. But it’s not impossible to apply this technique to legacy code – the key is to start small. One great way to do so is by working in the tests when you are fixing bugs. The developer tackling the bug should first understand the expected behavior, write a failing unit test, then fix the code until it passes. As new features are added or existing ones updated, the developers can add unit tests to the code that is touched. Eventually, over time, the overall code coverage percentage will increase.
Increased Cost Savings Over Time
By using test-driven development, you will create an application that costs less money to maintain over time. As the original developers transition off the project and new ones step in to maintain the application, it will be easier to review the code and understand how it is supposed to work, which will make refactoring go more smoothly.
As new features are added, the team will know very quickly if a change has caused something else in the code to break. From a general perspective, it will be a more stable product, with a cleaner design, that produces fewer defects in production. However, as we know, real users will almost always find a way to break your application. When defects are found, writing the failing unit test before fixing will help ensure that the bug never returns. If you write the test after fixing the bug, you won’t know whether or not the bug will cause the unit test to fail.
To read more about test-driven development from Ascendle’s CEO Dave Todaro, check out The Epic Guide to Agile.