How TDD (Test-Driven-Development) works (Part 1)
Test-driven development (TDD) is a software development process in which a developer writes the code repeatedly in a very short development cycle: first, the developer writes an (initially failing) automated unit test that defines how a new feature should work or what to expect when extending existing functionality, then writes the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards.
The following is the flow chart of the TDD cycle
There are a lot of articles produced on TDD and Wikipedia is generally a decent beginning. This blog post covers real tests and the development of a simple string calculator. We will learn this test-first development approach by implementing the requirements one by one without knowing them in advance.
In the following section, you will find the test script with respect to each requirement and afterward the actually produced code. Read, only one requirement at a time, write the tests and the implementation yourself and compare it with the results from this blog. Keep in mind that there are multiple different approaches to writing tests and implementation. This example is only one out of many possible solutions.
Let’s begin our journey!
List of Requirements
- Create a simple String calculator with a method int Add(string numbers)
- The method can take 0, 1, or 2 numbers, and will return their sum (for an empty string it will return 0) for example <“ “> or <“1”> or <“1,2”>
- Allow the Add method to handle an unknown amount of numbers
- Allow the Add method to handle newlines between numbers (instead of commas).
- The following input is ok: “1\n2,3” (will equal 6)
- Support different delimiters
- To change a delimiter, the beginning of the string will contain a separate line that looks like this: “//[delimiter]\n[numbers…]” for example “//;\n1;2” should return three where the default delimiter is ‘;’
- The first line is optional. All existing scenarios should still be supported
- Calling Add with a negative number will throw an exception “negatives not allowed” – and the negative that was passed. If there are multiple negatives, show all of them in the exception message stop here if you are a beginner.
- Numbers bigger than 1000 should be ignored, so adding 2 + 1001 = 2
- Delimiters can be of any length with the following format: “//[delimiter]\n” for example: “//[—]\n1—2—3” should return 6
- Allow multiple delimiters like this: “//[delim1][delim2]\n” for example “//[-][%]\n1-2%3” should return 6.
- Make sure you can also handle multiple delimiters with lengths longer than one char
Albeit this seems to be an exceptionally straightforward program, taking a gaze at those prerequisites can overpower. We should adopt an alternate strategy. Ignore what you have recently read and let us go through the requirements one at a time.
Develop a simple String calculator with a TDD approach
I am using the IntelliJ community edition with JDK 11 for this tutorial. So first create a new maven java project in the IntelliJ and add the following dependencies in the pom.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>TestDrivenDevelopment</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
</project>
After creating the project you will see a normal maven project structure like this. I have already added two Java class files StringCalculator.java and StringCalculatorTests.java. See the following structure for your reference.
Requirement 1: The method can take 0, 1, or 2 numbers separated by a comma (,).
Let’s start writing our first two tests and their corresponding method to be tested
[Tests written in Java language]package com.gofadi.softwaresolutions.tddtests;
import com.gofadi.softwaresolutions.tdd.StringCalculator;
import org.junit.Assert;
import org.junit.Test;
import static com.gofadi.softwaresolutions.tdd.StringCalculator.add;
public class StringCalculatorTests {
// Requirement 1: The method can take 0, 1 or 2 numbers separated by comma (,).
// Test 1
@Test(expected = RuntimeException.class)
public final void whenMoreThan2NumbersAreUsedThenExceptionIsThrown(){
add("1,2,3");
}
// Test 2
@Test
public final void when2NumbersAreUsedThenNoExceptionIsThrown() {
add("1,2");
Assert.assertTrue(true);
}
}
[Java Implementation]
package com.gofadi.softwaresolutions.tdd;
public class StringCalculator {
public static void add(final String numbers){
// So far only method definition is written
}
}
Okay, so far we have written only two test methods in order to test the first requirement if there will be more than two string numbers separated by commas then the method should throw a RuntimeException otherwise not.
For Java implementation, we have just defined the method signature and no code has been written so far. Now if we run our first test then the test should fail because we have not implemented the expected functionality of throwing the exception in our code but our second test will pass as it is just calling the function and we expect a true statement if 1 or 2 string numbers are passed as method argument separated by commas. Execute the tests from StringCalculatorTest class
Now we will write the code which will throw the exception in the if block after that, we will rerun our tests to check if our first test passes or not.
package com.gofadi.softwaresolutions.tdd;
public class StringCalculator {
public static void add(final String numbers){
String[] numbersArray = numbers.split(",");
// if length of numbers array is greater than 2 which means there are 3 numbers in string
if (numbersArray.length > 2) {
throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
}
}
}
Now as we have written the functionality of throwing the exception we will test our code by running our test method again and will see the test report
Now we will write our third test which will test the add method if a non-number is passed as a String argument and then it should throw a RunTimeException.
[Tests written in Java language]package com.gofadi.softwaresolutions.tddtests;
import com.gofadi.softwaresolutions.tdd.StringCalculator;
import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import static com.gofadi.softwaresolutions.tdd.StringCalculator.add;
@FixMethodOrder(MethodSorters.DEFAULT)
public class StringCalculatorTests {
// Requirement 1: The method can take 0, 1 or 2 numbers separated by comma (,).
// Test 1
@Test(expected = RuntimeException.class)
public final void whenMoreThan2NumbersAreUsedThenExceptionIsThrown(){
add("1,2,3"); // The test will pass as the functionality of throwing the exception is implemented
}
// Test 2
@Test
public final void when2NumbersAreUsedThenNoExceptionIsThrown() {
add("1,2");
Assert.assertTrue(true);
}
// Test 3
@Test(expected = RuntimeException.class)
public final void whenNonNumberIsUsedThenExceptionIsThrown() {
add("1,a");
}
}
After running the tests the first two tests will be passed but the third one will fail because we have not implemented the functionality of throwing an exception for a non-number string.
Now we will refactor our code to add the functionality of throwing RuntimeException and will rerun or tests
[Java Implementation]package com.gofadi.softwaresolutions.tdd;
public class StringCalculator {
public static void add(final String numbers){
String[] numbersArray = numbers.split(",");
// if length of numbers array is greater than 2 which means there are 3 numbers in string
if (numbersArray.length > 2) {
throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
} else if (numbersArray.length > 0 && numbersArray.length <= 2) {
for (String number : numbersArray) {
Integer.parseInt(number); // If it is not a number, parseInt will throw an exception
}
}
}
}
Now when we rerun our tests again all three tests will be passed as we have implemented the parseInt functionality.
Summary:
This is part 1 of the blog series in which we have seen how Test Driven Development (TDD) works in a real development environment. We have written test scripts for the first requirement and implemented its corresponding functionality in Java. We will further continue this blog series by implementing one requirement at a time and will see how our String Calculator code evolves step by step. To be continued 🙂
References:
The example of the string calculator has been taken from the Roy Osherove Katas. Don’t click the link until you’re finished with other parts of this blog series. This exercise is best done when not all requirements are known in advance.
0 Comments