Test Driven Development (TDD) in Java
By Sonal Dwivedi, Community Contributor - September 11, 2023
In Software Development Life Cycle (SDLC), normally testing is performed either in parallel or after a part of software is developed. On the contrary, Test-Driven Development (TDD) is a software development process that emphasises creating automated tests before writing the actual code for Software Under Test (SUT). It has become a standard best practice for developers working in an Agile development environment as it allows programmers to eradicate the tedious task of debugging and reworking so that programmers can focus on the design and coding part.
In this process, a test case is first created for a particular feature. If the testcase fails, new code is written to pass it. And lastly, the code is refactored such that the test should pass for all the parameters or given scenarios thereby improving design and maintainability. Due to this continuous process of developing and running automated tests before actual development of Software, TDD is also called Test First Development which is rooted in Extreme Programming.
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
Let us understand TDD with a simple Java program using JUnit framework to swap the last two characters of a string. Here there would be many 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, we have created an instance of class StringSwapHelper which does not exist. If this test is run, it will give compilation error and fail the test case. (RED)
2. Let us go ahead and create the StringSwapHelper class inside a package under src/main/java. (Hover on 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 we run the test case, it will still fail as the swaplasttwocharsclass() method is returning null.
3. Now we should add the business logic and what we 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. Now we should refactor the code for other conditions to satisfy. Let us 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 we run this test, it will fail as we hardcoded the firstChar and secChar to fetch for index 0 and 1 respectively. Therefore, as per the current implementation “ABCD” will return “BACD” and not “ABDC”, swapping the first and second index and not 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. Let us now see 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;
Let us 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
- 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 along with TestNG or JUnit, it is very essential to ensure better coverage and high test accuracy. 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.
This would result in a higher accuracy, as any bug which is highlighted under real conditions can be found in TDD and this results in early resolution of the bug. Hence you can deliver high quality web applications.