diff --git a/build.gradle b/build.gradle index 87254a3a..239f9e78 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,9 @@ repositories { dependencies { testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation platform('org.assertj:assertj-bom:3.25.1') testImplementation('org.junit.jupiter:junit-jupiter') + testImplementation('org.assertj:assertj-core') } test { diff --git a/src/main/java/Calculator.java b/src/main/java/Calculator.java new file mode 100644 index 00000000..113c6e85 --- /dev/null +++ b/src/main/java/Calculator.java @@ -0,0 +1,21 @@ +public class Calculator { + + public int add(int a, int b) { + return a + b; + } + + public int subtract(int a, int b) { + return a - b; + } + + public int multiply(int a, int b) { + return a * b; + } + + public int divide(int a, int b) { + if (b == 0) { + throw new ArithmeticException("Divide by zero"); + } + return a / b; + } +} diff --git a/src/main/java/StringCalculator.java b/src/main/java/StringCalculator.java new file mode 100644 index 00000000..64ad4ddf --- /dev/null +++ b/src/main/java/StringCalculator.java @@ -0,0 +1,91 @@ +import java.util.regex.Pattern; + +public class StringCalculator { + + /* + 1. 입력값 검증 + 2. 구분자 추출 + 3. 문자열을 구분자 기준으로 분리 + 4. 분리된 숫자 문자열 배열을 합산 + */ + + public int splitStrBySeparaterAndGetSum(String str) { + // 1. 입력값 검증 + if(str.length() == 0) { + return 0; + } + + // 2. 구분자, 커스텀 구분자일 경우 구분자 제외 검사해야할 문자열 추출 + String separater = extractSeparater(str); + String numbers = extractNumbers(str); + + // 3. 문자열을 구분자 기준으로 분리 + String [] tokens = numbers.split(separater); + validateTokens(tokens); + + // 4. 분리된 숫자 문자열 배열 합산 + return calculateSum(tokens); + } + + private String extractSeparater(String str) { + // 기본 구분자는 , 또는 : 이다. + String separater = "[,|:]"; + + // 커스텀 구분자가 존재하는 경우 + if (str.startsWith("//") && str.contains("\n")) { + int newLineIdx = str.indexOf("\n"); + // /n 을 찾을 수 없는 경우 + if (newLineIdx == -1) { + throw new RuntimeException("커스텀 구분자를 '//' 와 '\\n' 문자 사이에 정확하게 입력해주세요."); + } + separater = Pattern.quote(str.substring(2, newLineIdx)); + } + return separater; + } + + private String extractNumbers(String str) { + String numbers = ""; + + // 커스텀 구분자일 경우 + if(str.startsWith("//")) { + numbers = str.substring(str.indexOf("\n") + 1); + return numbers; + } + + // 기본 구분자이면 그대로 str 반환 + return str; + } + + private void validateTokens(String[] tokens) { + for(String token : tokens) { + if (token.isEmpty()) { + throw new RuntimeException("잘못된 입력 형식입니다."); + } + + if(parseNumber(token) < 0) { + throw new RuntimeException("음수는 사용할 수 없습니다."); + } + } + } + + private int calculateSum(String[] tokens) { + int sum = 0; + for(String token : tokens) { + sum += Integer.parseInt(token); + } + return sum; + } + + private int parseNumber(String number) { + // 숫자가 아니라면 예외 발생 + try { + return Integer.parseInt(number); + } catch (NumberFormatException e) { + throw new RuntimeException("숫자가 아닌 값이 포함되어 있습니다."); + } + } + + private boolean isNumeric(String str) { + return str.matches("-?\\d+"); // 정수 여부를 확인하는 정규식 (-는 음수 허용) + } +} diff --git a/src/test/java/CalculatorTest.java b/src/test/java/CalculatorTest.java new file mode 100644 index 00000000..a29c9e95 --- /dev/null +++ b/src/test/java/CalculatorTest.java @@ -0,0 +1,48 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("초간단 계산기 테스트") +class CalculatorTest { + private final Calculator calculator = new Calculator(); + + @Nested + class Add { + @Test + void add() { + assertEquals(5, calculator.add(2, 3)); + } + } + + @Nested + class Subtract { + @Test + void subtract() { + assertEquals(-3, calculator.subtract(3, 6)); + } + } + + @Nested + class Multiply { + @Test + void multiply() { + assertEquals(6, calculator.multiply(2, 3)); + } + } + + @Nested + class Divide{ + @Test + @DisplayName("0으로 나누면 ArithmeticException 예외가 발생한다.") + void should_throw_exception_when_divide_by_zero() { + assertThrows(ArithmeticException.class, () -> calculator.divide(3,0)); + } + + @Test + void divideByNotZero() { + assertEquals(2, calculator.divide(6, 3)); + } + } +} diff --git a/src/test/java/StringCalculatorTest.java b/src/test/java/StringCalculatorTest.java new file mode 100644 index 00000000..418e34a8 --- /dev/null +++ b/src/test/java/StringCalculatorTest.java @@ -0,0 +1,83 @@ +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayName("문자열 계산기 테스트") +class StringCalculatorTest { + + private StringCalculator stringCalculator = new StringCalculator(); + + @Nested + @DisplayName("기본 구분자 테스트") + class basicSeparaterTest { + + @ParameterizedTest(name = "입력값 \"{0}\"은 결과값 {1}을 반환해야 한다.") + @CsvSource({ + "'', 0", + "'123', 123", + "'1,2:3', 6" + }) + void should_return_correct_sum(String input, int expected) { + assertThat(stringCalculator.splitStrBySeparaterAndGetSum(input)).isEqualTo(expected); + } + } + + @Nested + @DisplayName("커스텀 구분자 테스트") + class customSeparaterTest { + @Test + @DisplayName("커스텀 구분자로 분리한 숫자들의 합을 반환해야 한다") + void should_sum_numbers_separated_by_custom_separator() { + assertThat(6).isEqualTo(stringCalculator.splitStrBySeparaterAndGetSum("//;\n1;2;3")); + assertThat(2).isEqualTo(stringCalculator.splitStrBySeparaterAndGetSum("//;\n2")); + } + } + + @Nested + @DisplayName("Runtime Exception 테스트") + class runtimeExceptionTest { + @Test + @DisplayName("숫자가 아닌 값이 포함되면 예외를 발생해야 한다.") + void should_throw_exception_when_contains_non_number() { + + // 특수문자가 포함된 경우 + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;\n1;*;3")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); + + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;\n1456;-;3")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); + + // 글자가 포함된 경우 + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;\nddd;ad;3")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); + } + + @Test + @DisplayName("음수가 포함되면 예외를 발생해야 한다.") + void should_throw_exception_when_contains_minus() { + + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("-4,2:-9")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("음수는 사용할 수 없습니다."); + } + + @Test + @DisplayName("또 다른 잘못된 입력 형식의 경우 예외를 발생해야 한다.") + void should_throw_exception_when_another_wrong_input() { + + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;1;2;\n;")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("잘못된 입력 형식입니다."); + + } + } + +} \ No newline at end of file