TDD

TDD-Övning

First write an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards.
Stegen att följa:
Add a test
Run all tests and see if the new one fails
Write some code
Run tests
Refactor code
Repeat

There’s a plenty of articles written on TDD and is always a good start. This article will focus on the actual test and implementation using variation of one of the . Do not click the link until you’re finished with this article. This excercise is best done when not all requirements are known in advance.
Below you will find the test code related to each requirement and afterwards the actual implementation. Try to read only one requirement, write the tests and the implementation yourself and compare it with the results from this article. Remember that there are many different ways to write tests and implementation. This article is only one out of many possible solutions.
Let’s start!

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 new lines 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 length longer than one char
Even though this is a very simple program, just looking at those requirements can be overwhelming. Let’s take a different approach. Forget what you just read and let us go through the requirements one by one.

Create a simple String calculator

Requirement 1: The method can take 0, 1 or 2 numbers separated by comma (,).


Let’s write our first set of tests.
[JAVA TEST]
package com.wordpress.technologyconversations.tddtest;
import org.junit.Test;
import com.wordpress.technologyconversations.tdd.StringCalculator;
public class StringCalculatorTest {
@Test(expected = RuntimeException.class)
public final void whenMoreThan2NumbersAreUsedThenExceptionIsThrown() {
StringCalculator.add("1,2,3");
}
@Test
public final void when2NumbersAreUsedThenNoExceptionIsThrown() {
StringCalculator.add("1,2");
Assert.assertTrue(true);
}
@Test(expected = RuntimeException.class)
public final void whenNonNumberIsUsedThenExceptionIsThrown() {
StringCalculator.add("1,X");
}
}

It’s a good practice to name test methods in a way that it is easy to understand what is being tested. I prefer a variation of
with When [ACTION] Then [VERIFICATION]. In this case the name of one of the test methods is whenMoreThan2NumbersAreUsedThenExceptionIsThrown.
Our first set of tests verifies that up to two numbers can be passed to the calculator’s add method. If there’s more than two or if one of them is not a number, exception should be thrown. Putting “expected” inside the @Test annotation tells the JUnit runner that the expected outcome is to throw the specified exception. From here on, for brevity reasons, only modified parts of the code will be displayed. Whole code divided into requirements can be obtained from the GitHub repository ( and ).

[JAVA IMPLEMENTATION]
public class StringCalculator {


public static final void add(final String numbers) {
String[] numbersArray = numbers.split(",");

if (numbersArray.length > 2) {
throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
} else {
for (String number : numbersArray) {
Integer.parseInt(number); // If it is not a number, parseInt will throw an exception
}

}
}
}

Keep in mind that the idea behind TDD is to do the necessary minimum to make the tests pass and repeat the process until the whole functionality is implemented. At this moment we’re only interested in making sure that “the method can take 0, 1 or 2 numbers”. Run all the tests again and see them pass.

Requirement 2: For an empty string the method will return 0


[JAVA TEST]
@Test
public final void whenEmptyStringIsUsedThenReturnValueIs0() {
Assert.assertEquals(0, StringCalculator.add(""));
}
[JAVA IMPLEMENTATION]
public static final int add(final String numbers) { // Changed void to int
String[] numbersArray = numbers.split(",");
if (numbersArray.length > 2) {
throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
} else {
for (String number : numbersArray) {
if (!number.isEmpty()) {
Integer.parseInt(number);
}
}
}
return 0; // Added return
}
All there was to do to make this test pass was to change the return method from void to int and end it with returning zero.

Requirement 3: Method will return their sum of numbers


[JAVA TEST]
@Test
public final void whenOneNumberIsUsedThenReturnValueIsThatSameNumber() {
Assert.assertEquals(3, StringCalculator.add("3"));
}
@Test
public final void whenTwoNumbersAreUsedThenReturnValueIsTheirSum() {
Assert.assertEquals(3+6, StringCalculator.add("3,6"));
}
[JAVA IMPLEMENTATION]
public static int add(final String numbers) {
int returnValue = 0;
String[] numbersArray = numbers.split(",");
if (numbersArray.length > 2) {
throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
}
for (String number : numbersArray) {
if (!number.trim().isEmpty()) { // After refactoring
returnValue += Integer.parseInt(number);
}
}
return returnValue;
}

Here we added iteration through all numbers to create a sum.

Requirement 4: Allow the Add method to handle an unknown amount of numbers


[JAVA TEST]
//@Disable
//@Test(expected = RuntimeException.class)
// public final void whenMoreThan2NumbersAreUsedThenExceptionIsThrown() {
// StringCalculator.add("1,2,3");
// }

@Test
public final void whenAnyNumberOfNumbersIsUsedThenReturnValuesAreTheirSums() {
Assert.assertEquals(3+6+15+18+46+33, StringCalculator.add("3,6,15,18,46,33"));
}
[JAVA IMPLEMENTATION]
public static int add(final String numbers) {
int returnValue = 0;
String[] numbersArray = numbers.split(",");
// Removed after exception
// if (numbersArray.length > 2) {
// throw new RuntimeException("Up to 2 numbers separated by comma (,) are allowed");
// }
for (String number : numbersArray) {
if (!number.trim().isEmpty()) { // After refactoring
returnValue += Integer.parseInt(number);
}
}
return returnValue;
}
All we had to do to accomplish this requirement was to remove part of the code that throws an exception if there are more than 2 numbers. However, once tests are executed, the first test failed. In order to fulfill this requirement, the test whenMoreThan2NumbersAreUsedThenExceptionIsThrown needed to be removed.

Requirement 5: Allow the Add method to handle new lines between numbers (instead of commas).


[JAVA TEST]
@Test
public final void whenNewLineIsUsedBetweenNumbersThenReturnValuesAreTheirSums() {
Assert.assertEquals(3+6+15, StringCalculator.add("3,6n15"));
}
[JAVA IMPLEMENTATION]
public static int add(final String numbers) {
int returnValue = 0;
String[] numbersArray = numbers.split(",|n"); // Added |n to the split regex
for (String number : numbersArray) {
if (!number.trim().isEmpty()) {
returnValue += Integer.parseInt(number.trim());
}
}
return returnValue;
}
All we had to do to was to extend the split regex by adding |\n.

Requirement 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 take 1 and 2 as parameters and return 3 where the default delimiter is ‘;’ .

[JAVA TEST]
@Test
public final void whenDelimiterIsSpecifiedThenItIsUsedToSeparateNumbers() {
Assert.assertEquals(3+6+15, StringCalculator.add("//;n3;6;15"));
}
[JAVA IMPLEMENTATION]
public static int add(final String numbers) {
String delimiter = ",|n";
String numbersWithoutDelimiter = numbers;
if (numbers.startsWith("//")) {
int delimiterIndex = numbers.indexOf("//") + 2;
delimiter = numbers.substring(delimiterIndex, delimiterIndex + 1);
numbersWithoutDelimiter = numbers.substring(numbers.indexOf("n") + 1);
}
return add(numbersWithoutDelimiter, delimiter);
}
private static int add(final String numbers, final String delimiter) {
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.