By: Nicholas Bennett
Test-Driven Development (TDD) statistics can’t be ignored! Projects on average have 40-80% fewer defects when TDD is applied – and those statistics are conservative. So why aren’t more organizations adopting this proven best practice? Maybe it is a lack of training or understanding of TDD or dedication to the practice as old habits die hard. Let’s break down this problem and explore the habits development teams need to adopt for a comprehensive TDD culture.
What Is TDD?
TDD is a software development approach in which tests are written before the implementation of a solution’s code. TDD delivers value even after the design, development, and testing phases of the Software Development Life Cycle (SDLC). In fact, it continuously provides both quantitative and qualitative results to the developer, QA team, and other stakeholders when tests are integrated into the build pipeline. TDD can be used to deliver measurable statistics through automated feedback, while also working to minimize software defects and unexpected behavior. This testing method ultimately minimizes the chance that defects reach production code, otherwise wasting a significant amount of time and resources. The approach ensures that the end product is verifiable, maintainable, and continues to meet business and client needs after development.
What Do TDD Tests and Ice Cream Have in Common?
They come in many flavors! Some examples of test focuses include unit, integration, automated UI, security, performance, and 508 compliance. Each of these different areas of testing allows the developer to achieve a more reliable end product in different ways, and when used in conjunction builds a lasting solution.
Unit Tests
Unit testing tests the smallest individual components of software before integration testing. Typically, unit tests validate backend components and business logic through the White Box testing technique which is based on the internal structure of a specific component.
Integration Tests
Integration testing ensures the interaction between specific components of the system is performing as expected. Defects are uncovered by testing groups of components and how they interface with one another.
Automated UI Tests
Automated UI testing is a front-end testing technique in which the controls that a user interacts with are performed by the computer through automation of mouse and keyboard (unless running in headless mode). These tests help to verify that the controls are carrying out specific actions and that the user interface is updating correctly in response.
Security Tests
Security testing attempts to uncover underlying vulnerabilities in different parts of the system such as network infrastructure, the database, the client, and the server that could potentially be exploited by an intruder with malicious intent.
Performance Tests
Performance testing aims to measure the responsiveness and reliability of parts of the system when under load. This type of testing ensures business-critical transactions are performing as expected when subject to specific conditions.
508 Compliance Tests
508 Compliance tests ensure that an application’s user interface is accessible to people with disabilities. Section 508 in The Rehabilitation Act sets the standards for software accessibility and is a requirement for every federal government department.
Why Is It Important to Stakeholders?
Software defects are an unwanted side effect of the development process, and TDD helps to minimize the impact and the chance of a defect arising when carefully integrated as a standard practice.
As mentioned previously, the practice of TDD has shown to reduce product defect deficiencies between 40% and 80%. With TDD, the defects found early never make it to production code. The cost of fixing the production defect has the potential to be much higher than if it were caught and fixed during implementation.
The Benefits Outweigh the Costs: Proving TDD’s Value to Product Owners
The notion that the costs of integrating TDD outweigh the actual benefits is a miscalculation. The benefits of TDD can be out shadowed by the 15-20% additional development time it applies.
The consequences of defects often aren’t realized until they happen. The extra time spent practicing TDD has the potential to avoid the costs associated with defects reaching production. For example, a unit test has the potential to catch a defect in which a user incurs a loss of data that amounts to a loss of valuable time, and an automated UI test has the potential to catch front-end validation problems that provide unauthorized access.
The value that TDD provides can be measured both quantitatively and qualitatively.
Qualitative measurements:
- Produces better design and more reliable code
- Creates efficiency
- Automatic feedback for the developer before and after quality assurance (QA) team testing
- Potential to automate regression testing and minimize the time for QA testing team
- Reduce manual testers
- Minimizes defects that undermine the original requirements and the completed work
- Lessens the chance that defects resurface and go unnoticed until code reaches production
- Promotes solution longevity by allowing developers down the road to understand the code’s purpose and expected results
- Encourages easier code maintenance
- Acts as an extension to the solution’s documentation and proof of concept
Quantitative measurements:
During the development of new features, performance metrics and other customer requested statistics can be provided as a result of TDD diligence during the development process. Tests should output an expected and actual result that represents a pass or fail for the requirement. Supply the number of passing and failing tests that represent actual user scenarios as a deliverable alongside the demo of new application functionality. This ultimately results in increased transparency between the development team, QA team, stakeholders, and product owner.
Incorporating TDD Habits into the Development Process
TDD serves as an aid to the willing developer by offering a proven strategy for reliable code through immediate feedback for the implemented code. Red, green, and refactor are the three phases of TDD. Before beginning the first phase, the developer should think and write test cases that capture the business requirement.
The first phase (red phase) involves writing a test against code before implementation. The test will most likely fail. However, the process reveals errors that can be addressed by the developer.
The second phase (green phase) involves implementing the code needed to pass the first test run from the red phase.
The final phase (refactor phase) involves applying best practices and design patterns to the implemented code, so that code quality and maintainability are given respect.
The developer can repeat the red, green, and refactor cycle of TDD for each task and test case in a given feature. The cycle of TDD can be performed on a minute-by-minute basis by repeating the steps when developing the test for the code to be implemented.
Minute-by-minute cycles during code development:
- Writing a small number of new and failing automated unit test case(s) for the task at hand
- Implementing code which should allow the new unit test cases to pass after failure
- Re-running the new unit test cases to ensure they now pass the new code
- Refactor to make the code generic and reusable as the test gets more specific
As each test is completed to verify the code for the task at hand, the code should give respect to the existing tests in the codebase and can be confirmed on a per-task cycle. Since refactoring can be an on-going process, the developer should verify that the implemented code does not break any existing tests.
Per-task cycles:
- Integrate code and tests for new code into an existing codebase
- Re-running all the test cases in the codebase to ensure the new code does not break any previously running test cases
- Refactoring the implementation or test code as necessary
- Re-running all tests in the codebase to ensure that the refactored code does not break any previously passing test cases
In each phase, the test has the opportunity of locating potential defects that otherwise may have ended up as code for the final solution. The tests, once fully developed, can be incorporated into the build pipeline to perform automatic continuous integration (CI) testing.
Cultivating a Comprehensive Culture
TDD should be encouraged during development, and the techniques for its practice should be actively promoted and distributed within the organization. Developers who have difficulty getting started with TDD could potentially benefit from pair programming or reverse TDD. When pair programming, developers can alternate between running tests and fixing failing code. Reverse TDD may feel more comfortable for developers who typically write tests after implementing their code. Reverse TDD comments out implemented code, which allows the developer to start fresh from the red phase with an idea for the direction of the code.
If developers work together to enforce TDD principles, the solution’s code is more maintainable, reliable, and verifiable. TDD ultimately leads to a successful, lasting solution and benefits all stakeholders involved.
Pyramid Systems takes pride in delivering technological solutions that not only achieve the customer’s requirements and expectations, but also the longevity, reliability, and extensibility of those solutions for the future. A solution’s long-term reliability can be ensured by prioritizing the integration of TDD into the solution’s development process.
We are here to help! Contact us today at info@pyramidsystems.com to explore your frustrations, confusion, challenges, and/or aspirations with TDD.
Copyright © 2019 Pyramid Systems, Inc.