Test Driven Development (series) - String calculator #2
What a nice feeling when you change existing code and you're not worried of breaking the application. 😅
Brief
We'll continue on the footsteps of the previous post and we'll add a new condition to the calculator.
Let's say we now want to allow the StringCalculator
to handle new lines between numbers (commas should still be supported).
Example: input = "1\n2,3"
-> result = 6
Implementation
What better way to start the implementation but with a test 😁 .
@Test
void testCalculator_TwoNumbers_NewLineBetweenNumbers() {
StringCalculator stringCalculator = new StringCalculator();
String input = "1\n2\n3";
Assertions.assertEquals(6, stringCalculator.add(input));
}
🔴 As expected, the test fails as, at the moment, we are only splitting the numbers by commas.
🔵 Because we want to support both ways of splitting the numbers string, I decided to go with a Strategy Pattern implementation.
This way we can easily add other types of delimiters in our application just by adding a new StringSplitStrategy
.
public class StringSplitStrategy {
private final String delimiter;
public StringSplitStrategy(String delimiter) {
this.delimiter = delimiter;
}
public String getDelimiter() {
return delimiter;
}
}
public class StringSplitter {
private static final String DEFAULT_DELIMITER_1 = ",";
private static final String DEFAULT_DELIMITER_2 = "\n";
private final static String CUSTOM_DELIMITER = ";";
private final List<StringSplitStrategy> splitStrategyList = new ArrayList<>();
public StringSplitter() {
StringSplitStrategy defaultSplitStrategy1 = new StringSplitStrategy(DEFAULT_DELIMITER_1);
StringSplitStrategy defaultSplitStrategy2 = new StringSplitStrategy(DEFAULT_DELIMITER_2);
splitStrategyList.add(defaultSplitStrategy1);
splitStrategyList.add(defaultSplitStrategy2);
}
public String[] splitNumbers(String input) {
String resultString = input;
for (StringSplitStrategy stringSplitStrategy : splitStrategyList) {
resultString = resultString.replace(stringSplitStrategy.getDelimiter(), CUSTOM_DELIMITER);
}
return resultString.trim().split(CUSTOM_DELIMITER);
}
}
The logic here is simple:
- iterate through the defined strategies
- replace strategy delimiter with a custom one inside the input string
- finally split the string by the new custom delimiter
public class StringCalculator {
private final StringSplitter stringSplitter = new StringSplitter();
public int add(String input) {
if (input.isEmpty()) {
return 0;
}
int sum = 0;
String[] splitNumbers = stringSplitter.splitNumbers(input);
for (String number : splitNumbers) {
sum += Integer.parseInt(number);
}
return sum;
}
}
🟢 Now run again the tests and see them turning green!
Finally let's check that the code still works if we receive numbers delimited by commas AND new lines.
@Test
void testCalculator_TwoNumbers_NewLine_And_Comma_BetweenNumbers() {
StringCalculator stringCalculator = new StringCalculator();
String input = "1\n,2\n,3";
Assertions.assertEquals(6, stringCalculator.add(input));
}
@Test
void testCalculator_TwoNumbers_Alternating_NewLine_And_Comma_BetweenNumbers() {
StringCalculator stringCalculator = new StringCalculator();
String input = "1\n2,3";
Assertions.assertEquals(6, stringCalculator.add(input));
}
🟢 You should see these two passing as well.
🟢 I couldn't call this the Test Driven Development series if I wouldn't also write tests for the StringSplitter
.
public class StringSplitterTest {
private static final Integer ITEM_1 = 1;
private static final Integer ITEM_2 = 2;
private static final Integer ITEM_3 = 3;
@Test
void CommaDelimited_StringInput() {
String input = ITEM_1 + "," + ITEM_2 + "," + ITEM_3;
StringSplitter stringSplitter = new StringSplitter();
String[] items = stringSplitter.splitNumbers(input);
assertEquals(3, items.length);
assertEquals(ITEM_1.toString(), items[0]);
assertEquals(ITEM_2.toString(), items[1]);
assertEquals(ITEM_3.toString(), items[2]);
}
@Test
void NewLineDelimited_StringInput() {
String input = ITEM_1 + "\n" + ITEM_2 + "\n" + ITEM_3;
StringSplitter stringSplitter = new StringSplitter();
String[] items = stringSplitter.splitNumbers(input);
assertEquals(3, items.length);
assertEquals(ITEM_1.toString(), items[0]);
assertEquals(ITEM_2.toString(), items[1]);
assertEquals(ITEM_3.toString(), items[2]);
}
@Test
void NewLine_And_Comma_Delimited_StringInput() {
String input = ITEM_1 + "\n" + ITEM_2 + "," + ITEM_3;
StringSplitter stringSplitter = new StringSplitter();
String[] items = stringSplitter.splitNumbers(input);
assertEquals(3, items.length);
assertEquals(ITEM_1.toString(), items[0]);
assertEquals(ITEM_2.toString(), items[1]);
assertEquals(ITEM_3.toString(), items[2]);
}
}
Stay tuned for the next post where we'll add even more conditions to our StringCalculator
🚀.
Stay tuned! 🚀