In the Software Development Life Cycle (SDLC), testing is usually performed in parallel or after a part of the software is developed. On the contrary, Test-Driven Development (TDD) emphasises creating automated tests before writing the code for Software Under Test (SUT).
Overview
Test-driven development (TDD) in Java is a software development process in which developers write automated tests before writing the corresponding code.
How TDD Works
TDD follows a “Read-Green-Refractor” cycle to maximise the test coverage.
- Write a Test Case: Define a test case that describes the expected behavior of the software.
- Execute the Test (Red Phase): Run the test before implementing the functionality. Since the feature doesn’t exist initially, the test will fail.
- Write Minimal Code (Green Phase): Write the minimum code required to pass the failing test.
- Refactor the Code: After the test case is passed, refactor the code to enhance the readability, design, and maintainability.
- Repeat the Cycle: Once the code is refactored, continue the process by writing new test cases for additional functionality.
Benefits of TDD
- Improved Code Quality: Ensures code is thoroughly tested, leading to fewer bugs.
- Early Bug Detection: Issues are identified early, reducing debugging time.
- Better Design: Promotes writing modular, clean, and efficient code.
- Faster Refactoring: Confidence in code correctness allows for safer and easier refactoring.
- Comprehensive Coverage: Ensures all aspects of the code are tested, enhancing reliability.
TDD in Java
TDD in Java plays an important role by promoting a disciplined and systematic approach to write the automated test cases beforehand which maximises test coverage right from the start. In the TDD framework, only when the test fails, it suggests writing new code which prevents code duplication.
TDD aims to create tests prior to the implementation of code. TDD is a workflow from Red to Green to Refactor.
TDD Workflow
A test is created so that it fails (RED). The code is corrected to pass the test (GREEN). Once the test is passed it is refactored
Also Read: TDD vs BDD vs ATDD : Key Differences
Understanding TDD Process: 5 Steps of TDD
To understand how TDD works, the below flowchart exhibits the 5 steps of TDD that are interconnected, based on the pass or fail result of the test case.
TDD Process Flowchart
Here are the steps in a TDD Process:
- Write a testcase: Write a code which describes the desired behaviour of the software you need to implement.
- Execute the test case and check for failures: As the implementation does not exist initially, the test will fail (RED).
- Write some code: Write the minimum code necessary to pass the failing test case (GREEN).
- Refactor code: After the test case is passed, refactor the code to improve the design, maintainability and readability (REFACTOR).
- Repeat the cycle: Once the code is refactored, return to the “Write a testcase” step to continue the iteration by implementing more tests.
This awesome concept was introduced by Kent Beck in 2003 who stated that TDD encourages simple designs and inspires confidence. And how to achieve this in Java?
TDD in Java using JUnit: Example
In order to understand Test-Driven Development (TDD), consider a simple Java program using the JUnit framework to swap the last two characters of a string. This involves handling various conditions, such as:
- For a given string “AB” should return “BA”.
- For a given string “ABCD” should return “ABDC”.
- For a given string “SELENIUM” should return “SELENIMU”.
- For a given string “A” should return “A”. (If there is only a single char in a string it should return the same char.)
- Empty string should return empty.
Pre-requisites for TDD in Java using Selenium and JUnit
Create a Maven project and add JUnit dependency.
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
Read More: What is Maven in Java? (Framework and Uses)
Steps to perform TDD in Java using Selenium and JUnit
1. Create a package under src/test/java and create a test class to test the String swap functionality.
public class StringSwap { @Test public void stringSwap2charsOnly() { StringSwapHelper help=new StringSwapHelper (); Assert.assertEquals("BA", help.swaplasttwochars("AB")); } }
Here, an instance of the class StringSwapHelper, which does not exist, has been created. If you run this test, it will result in a compilation error and fail the test case. (RED)
2. Now, create the StringSwapHelper class inside a package under src/main/java. (Hover over the undefined class and click on ‘Create Class’)
Similarly create swaplasttwochars() method inside StringSwapHelper class.
public class StringSwapHelper { public String swaplasttwochars(String string) { // TODO Auto-generated method stub return null; } }
If you run the test case, it will still fail as the swaplasttwocharsclass() method is returning null.
3. Now add the business logic and what you expect the swaplasttwocharsclass() method to perform. We will add the logic to swap for only 2 characters first.
public class StringSwapHelper { public String swaplasttwochars(String string) { char firstChar = string.charAt(0); char secChar = string.charAt(1); return "" + secChar + firstChar; // "" is appended as 2 chars cannot be appended } }
Run the test and observe that the test case is passed. (GREEN)
4. Refactor the code for other conditions to satisfy. Implement the logic to swap the last two characters for 4 characters long String.
public class StringSwap { @Test public void stringSwap4chars () { StringSwapHelper help=new StringSwapHelper(); Assert.assertEquals("ABDC", help.swaplasttwochars("ABCD")); } }
Now, if you run this test, it will fail because the firstChar and secChar are hardcoded to fetch the characters at index 0 and 1, respectively. As per the current implementation, “ABCD” will return “BACD” instead of “ABDC,” swapping the first and second index rather than the second-last and last index.
5. Below is the refactored code:
public class StringSwapHelper { public String swaplasttwochars(String string) { int length = string.length(); String strExceptLast2Chars = string.substring(0, length - 2); char secondLastChar = string.charAt(length - 2); char lastChar = string.charAt(length - 1); return strExceptLast2Chars + lastChar + secondLastChar; } }
Run the test and stringSwap4chars method should pass now (REFACTOR). Alternatively, you can also check for longer String.
@Test public void stringSwap() { StringSwapHelper help=new StringSwapHelper(); Assert.assertEquals("SELENIMU", help.swaplasttwochars("SELENIUM")); }
6. Now check out how it works for only 1 character.
@Test public void stringSwap1CharOnly() { StringSwapHelper help=new StringSwapHelper(); Assert.assertEquals("A", help.swaplasttwochars("A")); }
This will give StringIndexOutOfBoundsException as the current implementation is fetching second last and last index of the String and “A” will have only index 0;
Now refactor for this problem as well.
Read More: Exception Handling in Selenium WebDriver
Refactored code:
public String swaplasttwochars(String string) { int length = string.length(); if(length<2) { return string; } String strExceptLast2Chars = string.substring(0, length - 2); char secondLastChar = string.charAt(length - 2); char lastChar = string.charAt(length - 1); return strExceptLast2Chars + lastChar + secondLastChar; }
Above code should also work for empty string. Complete code can be found at the GitHub repository.
Tools and frameworks for TDD in Java
TDD in Java is facilitated by various testing frameworks that provides tools for writing, executing and managing testcases.
- JUnit: JUnit is one of the most popular and open-source testing frameworks used by developers to write and execute unit test cases. These tests are designed to test individual components or units of code in isolation which verifies that each module or component of the software works as intended. It provides a structured and standardised way to create, manage and run tests by providing annotations and assertion methods. @Test, @Before, @After, and @BeforeEach are some of the commonly used annotations.
- TestNG: TestNG (Test Next Generation) is another popular and open-source testing framework inspired by JUnit and NUnit. It is used to perform unit, functional, integration and end-to-end testing. It provides the developer the ability to write powerful tests with more flexibility, easy annotations, grouping, parameterizing and sequencing.
Best Practices for TDD in Java
Follow the below-mentioned best practices to effectively implement Test-Driven Development (TDD) in Java:
- Write small test cases which fail at the start: Start by writing small test cases that captures the actual behaviour of the functionality. Write a test case such that it fails the expected behaviour confirming that the test is actually testing the functionality.
- Tests should be independent: Focus on one functionality at a time and progress in incremental manner. Every test should be self-sufficient and capable of working in isolation. It should not be dependent on any other test case to avoid complexity.
- Execute tests regularly: A test should be conducted before coding and after coding. It should also be performed after code refactoring. In simple words, run the test for any code change or code update to ensure that any regressions if found should be fixed on time and the development pace should be on track.
- Tests for positive and negative scenarios: Tests for positive scenarios are fine however, tests for negative scenarios are also important. This surfaces any potential issues with the code and makes the system robust.
- Give significance to testing: Testing is an indispensable part of Software Development Life Cycle (SDLC), and TDD ensures that tests are obligatory at any stage of development. Adequate time and resources should be employed in TDD to make it more robust. Arrange proper training to the development team and encourage them to embrace the TDD and follow its best practices. Prioritising testing, will ensure the Software’s quality is not neglected and is aligned with the need of the end user.
Conclusion
TDD is an important step towards Agile development. While TDD in Java can be easily achieved using Selenium and TestNG, achieving better coverage and high test accuracy is crucial.
By testing on a real device cloud like BrowserStack Automate, you can ensure that all the real user conditions are taken into consideration while performing the tests. BrowserStack Automate allows testing on 3,500+ real devices and browsers, helping teams validate their code across different setups. This ensures a smooth user experience, catches issues early, and improves software quality.