diff --git a/build.gradle b/build.gradle index 87254a3a..82bb7c27 100644 --- a/build.gradle +++ b/build.gradle @@ -10,8 +10,11 @@ repositories { } dependencies { + implementation("org.apache.commons:commons-lang3:3.18.0") 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..280838c9 --- /dev/null +++ b/src/main/java/Calculator.java @@ -0,0 +1,22 @@ +public class Calculator { + + public int add(int a, int b) { + return Math.addExact(a, b); + } + + public int subtract(int a, int b) { + return Math.subtractExact(a, b); + } + + public int multiply(int a, int b) { + return Math.multiplyExact(a, b); + } + + public int divide(int a, int b) { + if (b == 0) { + throw new IllegalArgumentException("0 나누기 오류"); + } + + return Math.floorDiv(a, b); + } +} diff --git a/src/main/java/StringCalculator.java b/src/main/java/StringCalculator.java new file mode 100644 index 00000000..f3c12457 --- /dev/null +++ b/src/main/java/StringCalculator.java @@ -0,0 +1,63 @@ +import java.util.Arrays; + +import org.apache.commons.lang3.StringUtils; + +public class StringCalculator { + + private static final String DEFAULT_DELIMITER = ",|:"; + private static final String START_CUSTOM_DELIMITER = "//"; + private static final String END_CUSTOM_DELIMITER = "\n"; + + private String customDelimiter = ""; + + public int add(String input) { + String delimiter = DEFAULT_DELIMITER; + + if (StringUtils.isEmpty(input)) { + return 0; + } + + if (hasCustomDelimiter(input)) { + input = extractCustomDelimiter(input); + delimiter += "|" + this.customDelimiter; + } + + return getSum(input.split(delimiter)); + } + + private int getSum(String[] numbers) { + return Arrays.stream(numbers).mapToInt(num -> { + validateIsNumber(num); + int number = Integer.parseInt(num); + validateIsPositive(number); + return number; + }).reduce(0, Math::addExact); + } + + private String extractCustomDelimiter(String input) { + int endCustomDelimiterIndex = input.indexOf(END_CUSTOM_DELIMITER); + this.customDelimiter = input.substring(START_CUSTOM_DELIMITER.length(), endCustomDelimiterIndex); + return input.substring(endCustomDelimiterIndex + 1); + } + + private void validateIsNumber(String number) { + if (!isNumber(number)) { + throw new RuntimeException("숫자가 아닌 값이 포함되어 있습니다"); + } + } + + private void validateIsPositive(Integer number) { + if (number < 0) { + throw new RuntimeException("음수는 허용되지 않습니다"); + } + } + + private boolean hasCustomDelimiter(String input) { + return input.startsWith(START_CUSTOM_DELIMITER); + } + + private boolean isNumber(String number) { + return number.matches("-?\\d+"); + } + +} diff --git a/src/test/java/CalculatorAssertJTest.java b/src/test/java/CalculatorAssertJTest.java new file mode 100644 index 00000000..5820c2dc --- /dev/null +++ b/src/test/java/CalculatorAssertJTest.java @@ -0,0 +1,116 @@ +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class CalculatorAssertJTest { + + private final Calculator calculator = new Calculator(); + + @Nested + @DisplayName("덧셈 테스트") + public class AddTest { + + @Test + public void 정수_더하기() { + // Given + int a = 2; + int b = 3; + + // When + int actual = calculator.add(a, b); + + // Then + int expected = 5; + assertThat(actual).isEqualTo(expected); + } + } + + @Nested + @DisplayName("뺄셈 테스트") + public class SubtractTest { + + @Test + public void 정수_빼기() { + // Given + int a = 3; + int b = 2; + + // When + int actual = calculator.subtract(a, b); + + // Then + int expected = 1; + assertThat(actual).isEqualTo(expected); + } + } + + @Nested + @DisplayName("곱셈 테스트") + public class MultiplyTest { + + @Test + public void 정수_곱하기() { + // Given + int a = 2; + int b = 3; + + // When + int actual = calculator.multiply(a, b); + + // Then + int expected = 6; + assertThat(actual).isEqualTo(expected); + } + } + + @Nested + @DisplayName("나눗셈 테스트") + public class DivideTest { + + @Test + public void 정수_나누기() { + // Given + int a = 6; + int b = 3; + + // When + int actual = calculator.divide(a, b); + + // Then + int expected = 2; + assertThat(actual).isEqualTo(expected); + } + + @Test + public void 예외_0_나누기() { + // Given + int a = 1; + int b = 0; + + // When & Then + assertThatThrownBy(() -> calculator.divide(a, b)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("0 나누기 오류"); + + assertThatCode(() -> calculator.divide(b, a)) + .doesNotThrowAnyException(); + } + + @Test + public void 나누기_결과가_정수가_아닐때() { + // Given + int a = 5; + int b = 2; + + // When + int actual = calculator.divide(a, b); + + // Then + int expected = 2; // 결과값은 정수로 나옴 + assertThat(actual).isEqualTo(expected); + } + } + +} diff --git a/src/test/java/CalculatorJunit5Test.java b/src/test/java/CalculatorJunit5Test.java new file mode 100644 index 00000000..b3d4a6ad --- /dev/null +++ b/src/test/java/CalculatorJunit5Test.java @@ -0,0 +1,70 @@ +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class CalculatorJunit5Test { + + private final Calculator calculator = new Calculator(); + + @Nested + @DisplayName("덧셈 테스트") + public class AddTest { + + @Test + public void 정수_더하기() { + assertEquals(5, calculator.add(2, 3)); + assertEquals(0, calculator.add(-1, 1)); + assertEquals(-5, calculator.add(-2, -3)); + } + } + + @Nested + @DisplayName("뺄셈 테스트") + public class SubtractTest { + + @Test + public void 정수_빼기() { + assertEquals(1, calculator.subtract(3, 2)); + assertEquals(-2, calculator.subtract(-1, 1)); + assertEquals(1, calculator.subtract(-2, -3)); + } + } + + @Nested + @DisplayName("곱셈 테스트") + public class MultiplyTest { + + @Test + public void 정수_곱하기() { + assertEquals(6, calculator.multiply(2, 3)); + assertEquals(-1, calculator.multiply(-1, 1)); + assertEquals(6, calculator.multiply(-2, -3)); + } + } + + @Nested + @DisplayName("나눗셈 테스트") + public class DivideTest { + + @Test + public void 정수_나누기() { + assertEquals(2, calculator.divide(6, 3)); + assertEquals(-1, calculator.divide(-3, 3)); + assertEquals(1, calculator.divide(-3, -3)); + } + + @Test + public void 예외_0_나누기() { + assertThrows(IllegalArgumentException.class, () -> calculator.divide(1, 0)); + assertDoesNotThrow(() -> calculator.divide(0, 1)); + } + + @Test + public void 나누기_결과가_정수가_아닐때() { + assertEquals(2, calculator.divide(5, 2)); + assertEquals(0, calculator.divide(1, 2)); + } + } +} diff --git a/src/test/java/StringCalculatorAssertJTest.java b/src/test/java/StringCalculatorAssertJTest.java new file mode 100644 index 00000000..fb12372a --- /dev/null +++ b/src/test/java/StringCalculatorAssertJTest.java @@ -0,0 +1,99 @@ +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class StringCalculatorAssertJTest { + + private final StringCalculator stringCalculator = new StringCalculator(); + + @Nested + @DisplayName("덧셈 테스트") + class AddTest { + + @Test + void 빈_문자열() { + // Given + String input = ""; + + // When + int result = stringCalculator.add(input); + + // Then + assertThat(result).isEqualTo(0); + } + + @Test + void 하나의_숫자() { + // Given + String input = "1"; + + // When + int result = stringCalculator.add(input); + + // Then + assertThat(result).isEqualTo(1); + } + + @Test + void 두_숫자_더하기() { + // Given + String input = "1,2"; + + // When + int result = stringCalculator.add(input); + + // Then + assertThat(result).isEqualTo(3); + } + + @Test + void 여러_숫자_더하기() { + // Given + String input = "1,2,3"; + + // When + int result = stringCalculator.add(input); + + // Then + assertThat(result).isEqualTo(6); + } + + @Test + void 커스텀_구분자() { + // Given + String input = "//;\n4;5,1:2"; + + // When + int result = stringCalculator.add(input); + + // Then + assertThat(result).isEqualTo(12); + } + + @Test + void 음수_예외() { + // Given + String input = "-1"; + + // When & Then + assertThatThrownBy(() -> stringCalculator.add(input)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("음수는 허용되지 않습니다"); + + } + + @Test + void 숫자가_아닌_예외() { + // Given + String input = "1,a"; + + // When & Then + assertThatThrownBy(() -> stringCalculator.add(input)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다"); + } + } +} diff --git a/src/test/java/StringCalculatorJunit5Test.java b/src/test/java/StringCalculatorJunit5Test.java new file mode 100644 index 00000000..f7c8a019 --- /dev/null +++ b/src/test/java/StringCalculatorJunit5Test.java @@ -0,0 +1,58 @@ +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class StringCalculatorJunit5Test { + + private final StringCalculator stringCalculator = new StringCalculator(); + + @Nested + @DisplayName("덧셈 테스트") + class AddTest { + + @Test + void 빈_문자열() { + assertEquals(0, stringCalculator.add("")); + } + + @Test + void 하나의_숫자() { + assertEquals(1, stringCalculator.add("1")); + assertEquals(5, stringCalculator.add("5")); + } + + @Test + void 두_숫자_더하기() { + assertEquals(3, stringCalculator.add("1,2")); + assertEquals(7, stringCalculator.add("3,4")); + } + + @Test + void 여러_숫자_더하기() { + assertEquals(6, stringCalculator.add("1,2,3")); + assertEquals(15, stringCalculator.add("4,5,6")); + } + + @Test + void 커스텀_구분자() { + assertEquals(3, stringCalculator.add("//;\n1;2")); + assertEquals(12, stringCalculator.add("//;\n4;5,1:2")); + } + + @Test + void 음수_예외() { + Exception exception = assertThrows(RuntimeException.class, () -> { + stringCalculator.add("-1"); + }); + } + + @Test + void 숫자가_아닌_예외() { + Exception exception = assertThrows(RuntimeException.class, () -> { + stringCalculator.add("1,a"); + }); + } + } +}