Test Driven Development
Ravi Nandru ?
Agile Coach | Scrum Master | Solution Architect | AI & ML | 13x AWS | 11x GCP | 4 x Azure I SPC 6
Agile approach to software development follows the four Agile manifesto principles. It aims to deliver quality code to customers at frequent intervals enabling early return on investment (ROI).
Quality code implicates:
- Code is thoroughly tested
- Code meets the needs of the customer
- Code can be changed easily
- Code is ‘clean’ (well-structured, self-documenting)
The technical practices prescribed by Extreme Programming aid in creating quality code while adopting Agile approach for software development. Test Driven development is one such practice.
Test Driven Development (TDD) is a technical practice consisting of short development cycles where test cases covering the new functionality are written first. Then code is written to pass the tests which are then refactored without changing the behavior of the code. Developers write automated tests before development. This means that development is driven from the tests. This ensures that no untested code goes into the software. Refactoring ensures that the code is ‘clean’.
The main philosophy of TDD, as mentioned by Kent Beck, lies in Ron Jeffries’ phase – ‘Clean code that works’. TDD helps to design and develop the code in a predictable way by writing only code just enough to make the test cases pass. TDD works on a simple process of writing failing tests (written for a new functionality) first. When code is written and refactored to make the test cases pass, hypothetical coding is avoided. Only enough code to meet the functionality is written. It helps to create a simple workable code and greatly helps developers for generating a clear and simple, and easily maintainable code.
Test driven development offers the following advantages:
- Adds reliability to the development process
- Encourages programmers to maintain an exhaustive set of repeatable tests which may be run selectively or exhaustively with the help of tools
- Developers strive to improve their code without the fear associated with code changes
- Makes programming more fun due to reliability on code
- Remove/ Reduce dependency on the debugger and no postponement of debugging
Test Driven Development is a development approach where:
- A comprehensive suite of automated tests is maintained
- No code goes into production unless it has associated tests
- Tests are written first
- Tests determine what code needs to be written
In simple terms, TDD suggests developers to write tests for a new functionality or feature and then develop code to pass the tests.
This document incorporates the design concept from Agile modeling and Extreme Programming which can be leveraged for Agile development projects.
- The developer first needs to understand the requirements to develop an automated test case and to achieve the desired result or feature
- Test cases are written first and are then added in the automated suites for execution
- When tests are written for a new functionality or feature, code would not be available for the same. Hence the tests tend to fail. These are called as failing tests
- The developer starts adding code to make the tests pass during execution
When the set of tests passes, code is written for the new feature or developer refactors the new code as required to make it compliant with the acceptable standards and provides it for next stages of testing
These steps are repeated for any new functionality or feature to be developed.
Why Test Driven Development
Following are the key drivers for adopting Test Driven Development:
- Design compliance is one of the visible benefits of TDD
- Code maintainability improves
- Acts as a safety net when a developer performs refactoring
- TDD is adopted to deliver the work in a simple way to avoid complexity
- TDD can be considered as both design and programming methodology which is based on simple rule of writing production code to fix a failing test
- TDD converts normal cycle of design-code-test into a sequential activity cycle of test-code-refactor in which developer writes a test first (initially failing) and then writes the code to pass it and further refactor the code
- TDD offers a simple and effective technique which helps to create high-quality software through the continuous design and test process and frequent refactoring. This way, the code becomes simple, less defective, better designed and of high quality. The developer gets confidence of writing code and becomes more productive.
Adopting TDD practice helps the project teams to achieve the following benefits:
- Faster Results: It provides an immediate feedback to reduce reversion errors. Developer writes only the code that is required resulting in faster results with clear, concise code
- Simplicity: Clean work, modular and less complex code
- Improved Quality: Automation is the biggest advantage in adopting the TDD
- Flexibility: Flexibility is in-built due to quick commits, short cycles and automation
- Offers shorter development cycles
- Reduces errors
- Has ability to respond rapidly to all the changing requirements
TDD is an iterative and incremental procedure. TDD does not mean that the developer can start working on code only after writing all the tests. A developer can first build the basic and high-level tests and once those are passed, the various other tests may be added. Duplication must be avoided while completing the functionality and development.
Steps for Implementing TDD
- Add Test
New test gets added for every new story or task to be developed. This test is the basic requisite for that particular user story. This test should be written in an automated way using various tools such as JUnit or NUnit. Developer must thoroughly understand requirements of the story, before writing the test. This test will fail once developer puts it on the automated framework.
- Run Test
Next step is to run the test. This helps serve two purposes. First of all, running of test assures that new code has been added, which is criteria for passing the test. This also makes sure that the functionality is not already developed as part of any other story. Secondly, a failing test is the first attempt towards TDD efforts. Failure in first attempt implies that TDD development is in progress.
- Make Changes
Next step is to make changes in the test and add the code in such a way that the test passes. Passing test includes the process of writing code and making changes. To pass this test, code written in the test should be isolated only for passing this particular test. No other code or functionality should be added.
- Run Test
In case test does not pass in the first attempt, the test must be run again. Failing in the test proves that it is not covering the functionality required by story and it requires further changes.
Passing of test assures that required functionality is covered. After passing test, further changes need to be done to refactor the code. Concept of ‘Refactoring’ can be described as cleaning of code, applying proper design pattern. This also assures that duplication of code is absolutely avoided.
In one Sprint we may have multiple stories. To keep adding more tests and new features or functionalities, the cycle of tests explained above can be repeated.
How is TDD Different from Unit Testing
TDD is not similar to unit testing. TDD and unit testing use unit testing frameworks such as xUnit (e.g. Junit, Nunit), however, the purpose and way in which it is used is clearly different.
Test and Code Average
While developing using TDD, the code gets tested rigorously and thoroughly. We can set some targets for line and branch coverage. For instance, let’s say for Line coverage it should be above 80% for new classes developed and for branch coverage it should be >85%
Tools such as Cobertura should be used to measure the coverage.
To measure the coverage (for example in J2EE environment), the process described below should be followed:
- Write the unit tests in iterative and incremental way
- Write unit tests only for the Java classes that are new or modified for that functional release
- Change the unit tests to fit the existing code before altering the code
- Alter functional class
- Run unit tests. Unit tests should run properly
- Run code coverage checker such as Cobertura
- Make sure that new class line coverage is >80% and branch coverage is >85%
- Refactor the code only when the code coverage is achieved
How to Review Project Using TDD Approach
Following things can be reviewed to check if the project is really following a TDD approach:
Make sure that all the evidences listed here are available for every release and available to see in continuous integration dashboard.
- >90% Automated for New Features (Unit Testing)
- Unit Test should be written as Standard (No exceptions)
- 80% Line coverage/85% branch Coverage (On new code/No exceptions)
- Opportunistic re-factoring of legacy code
- Unit test will be written for untested legacy code
- A unit test needs to be written for any method that is altered in a legacy class/module of code.
- 80% Line coverage/85% branch Coverage (No exceptions)
- Per Release Review of Unit test on each component
- Report to detail the following:
- Is test being written using TDD?
- Has there been an increase in Testing for a component?
- Defining where refactoring has occurred.
Tools for TDD
There are many tools which can help in developing a perfect test-driven development. Tests written in TDD are automated unit tests. There are unit test writing tools available in almost all the languages like JUnit for Java, CUnit for C language, NUnit for .Net, PHPUNit for PHP and VBUnit for Visual Basic.
Misconceptions about TDD
There are numerous misconceptions about TDD. Some of the commonly observed misconceptions are as given below:
- TDD is theoretical term and may not be beneficial in actual practice This is a misconception as using test driven development has many advantages.
- Independent or manual testing is not required with TDD By using TDD, user doesn’t need independent manual testing is a myth. Independent/manual testing is always essential to obtain quality software.
- Unit tests make 100% design specifications Unit help in design specifications. As the developers think about the production code before they write the tests in TDD and assume that it helps in creating detailed design effectively tests.
- One can achieve 100% coverage using regression suites in many Agile projects, it is observed that achieving 100% coverage is not possible because of third party components or framework in the code, which might not have source code or test cases. As user might be working on legacy functionality or systems, there might be user interface which are hard to test.
Challenges in Adopting and Executing Projects in TDD
Some of the challenges which developers perceive while executing projects in TDD:
- Changing developer’s mindset is challenging. Developers want to develop the code and not necessarily like to test it. Hence, in case of test driven development, writing test cases first and then develop the code in automated way, is found challenging by developers
- It is a challenge to apply test driven development approach for legacy code bases
- Applying TDD and writing automated tests for multithreaded environment is a challenge
- Quite often, developers feel that writing test first and then to develop the product may slow down the development process
- Another perception about TDD is that it is difficult to follow TDD in high-pressure work conditions or stringent timelines
- It is challenging to apply TDD in long-running projects where the code base is already developed without using the test-driven approach. Applying TDD concepts to already developed untested code base is a challenge
- Developers rarely estimate the effort required for TDD and this may pose to be a challenge