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:

  1. iterate through the defined strategies
  2. replace strategy delimiter with a custom one inside the input string
  3. 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 🚀.


💡
Don't miss out on more posts like this! Susbcribe to our free newsletter!
💡
Currently I am working on a Java Interview e-book designed to successfully get you through any Java technical interview you may take.
Stay tuned! 🚀