From eb6bedd7751274e54ad072c303d7edac247b0b83 Mon Sep 17 00:00:00 2001 From: haeunsong Date: Sat, 8 Feb 2025 01:13:14 +0900 Subject: [PATCH 1/6] =?UTF-8?q?3,4=EB=8B=A8=EA=B3=84=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EA=B3=84=EC=82=B0=EA=B8=B0=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 + src/main/java/StringCalculator.java | 53 ++++++++++++++++ src/test/java/StringCalculatorTest.java | 83 +++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/main/java/StringCalculator.java create mode 100644 src/test/java/StringCalculatorTest.java 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/StringCalculator.java b/src/main/java/StringCalculator.java new file mode 100644 index 00000000..1b463f7b --- /dev/null +++ b/src/main/java/StringCalculator.java @@ -0,0 +1,53 @@ +import java.util.regex.Pattern; + +public class StringCalculator { + + public static int getSum(String str) { + + if(str.length() == 0) { + return 0; + } + int sum = 0; + String separater = "[,|:]"; + String numbers = str; + + if (str.startsWith("//") && str.contains("\n")) { + + int newLineIdx = str.indexOf("\n"); + // /n 을 찾을 수 없거나, \n 이후 값이 없는 경우 + if (newLineIdx == -1) { + throw new RuntimeException("잘못된 입력 형식입니다."); + } + separater = Pattern.quote(str.substring(2, newLineIdx)); + numbers = str.substring(newLineIdx + 1); // \n 다음부터 끝까지 숫자가 있는 부분들 + + // \n 이후에 최소한 구분자가 한 개 이상은 있어야한다. 구분자가 없다면 배열 길이가 1이된다. + if(numbers.isEmpty() || numbers.split(separater).length == 1) { + throw new RuntimeException("잘못된 입력 형식입니다."); + } + } + // 구분자를 기준으로 split 진행 후 sum 실행 + String[] tokens = numbers.split(separater); + if(tokens.length == 1) { + return 0; + } + for (String token : tokens) { + int num = parseNumber(token); + if (num < 0) { + throw new RuntimeException("음수는 사용할 수 없습니다."); + } + sum += num; + } + + return sum; + } + + public static int parseNumber(String number) { + // 숫자가 아니라면 예외 발생 + try { + return Integer.parseInt(number); + } catch (NumberFormatException e) { + throw new RuntimeException("숫자가 아닌 값이 포함되어 있습니다."); + } + } +} diff --git a/src/test/java/StringCalculatorTest.java b/src/test/java/StringCalculatorTest.java new file mode 100644 index 00000000..4e5a491f --- /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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayName("문자열 계산기 테스트") +class StringCalculatorTest { + + StringCalculator stringCalculator = new StringCalculator(); + + @Nested + @DisplayName("기본 구분자 테스트") + class basicSeparaterTest { + @Test + @DisplayName("빈 문자열이면 0을 반환해야 한다.") + void should_return_0_when_string_is_empty() { + assertThat(0).isEqualTo(stringCalculator.getSum("")); + } + @Test + @DisplayName("구분자가 없는 경우 0을 반환해야 한다.") + void test() { + assertThat(0).isEqualTo(stringCalculator.getSum("123")); + } + @Test + @DisplayName("쉼표(,) 또는 콜론(:)으로 구분된 숫자들의 합을 반환해야 한다.") + void should_sum_numbers_separated_by_comma_or_colon() { + assertThat(6).isEqualTo(stringCalculator.getSum("1,2:3")); + } + } + + @Nested + @DisplayName("커스텀 구분자 테스트") + class customSeparaterTest { + @Test + @DisplayName("커스텀 구분자로 분리한 숫자들의 합을 반환해야 한다") + void should_sum_numbers_separated_by_custom_separator() { + assertThat(6).isEqualTo(stringCalculator.getSum("//;\n1;2;3")); + } + } + + @Nested + @DisplayName("Runtime Exception 테스트") + class runtimeExceptionTest { + @Test + @DisplayName("숫자가 아닌 값이 포함되면 예외를 발생해야 한다.") + void should_throw_exception_when_contains_non_number() { + + assertThatThrownBy(() -> stringCalculator.getSum("//;\n1;*;3")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); + + assertThatThrownBy(() -> stringCalculator.getSum("//;\n1456;-;3")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); + } + + @Test + @DisplayName("음수가 포함되면 예외를 발생해야 한다.") + void should_throw_exception_when_contains_minus() { + + assertThatThrownBy(() -> stringCalculator.getSum("-4,2:-9")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("음수는 사용할 수 없습니다."); + } + + @Test + @DisplayName("또 다른 잘못된 입력 형식의 경우 예외를 발생해야 한다.") + void should_throw_exception_when_another_wrong_input() { + + assertThatThrownBy(() -> stringCalculator.getSum("//;1;2;\n;")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("잘못된 입력 형식입니다."); + + assertThatThrownBy(() -> stringCalculator.getSum("//;1;2;\n123")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("잘못된 입력 형식입니다."); + + } + } + +} \ No newline at end of file From 035d8ad7d42e4a1f64f1347156b3176fcb36ed52 Mon Sep 17 00:00:00 2001 From: haeunsong Date: Sat, 8 Feb 2025 01:19:56 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor=20:=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20->=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/StringCalculator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/StringCalculator.java b/src/main/java/StringCalculator.java index 1b463f7b..a6ba5e7b 100644 --- a/src/main/java/StringCalculator.java +++ b/src/main/java/StringCalculator.java @@ -2,7 +2,7 @@ public class StringCalculator { - public static int getSum(String str) { + public int getSum(String str) { if(str.length() == 0) { return 0; @@ -42,7 +42,7 @@ public static int getSum(String str) { return sum; } - public static int parseNumber(String number) { + public int parseNumber(String number) { // 숫자가 아니라면 예외 발생 try { return Integer.parseInt(number); From 0afd62f783f374ec2eedf5ff6969561df0460fdf Mon Sep 17 00:00:00 2001 From: haeunsong Date: Sat, 8 Feb 2025 17:37:28 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=EB=AF=B8=EC=85=98=201,2=EB=8B=A8?= =?UTF-8?q?=EA=B3=84=20+=20=EB=AF=B8=EC=85=98=203,4=EB=8B=A8=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Calculator.java | 22 ++++++++++++++ src/test/java/CalculatorTest.java | 50 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/main/java/Calculator.java create mode 100644 src/test/java/CalculatorTest.java diff --git a/src/main/java/Calculator.java b/src/main/java/Calculator.java new file mode 100644 index 00000000..0a2d0a5f --- /dev/null +++ b/src/main/java/Calculator.java @@ -0,0 +1,22 @@ +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) { + //b가 0이면 에러메세지 + if (b == 0) { + throw new ArithmeticException("Divide by zero"); + } + return a / b; + } +} diff --git a/src/test/java/CalculatorTest.java b/src/test/java/CalculatorTest.java new file mode 100644 index 00000000..29d59106 --- /dev/null +++ b/src/test/java/CalculatorTest.java @@ -0,0 +1,50 @@ +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으로 쓰자 +@DisplayName("초간단 계산기 테스트") +class CalculatorTest { + private final Calculator calculator = new Calculator(); + + //@Nested 중첩 클래스 선언, 여러가지 메서드를 사용할 수 있음. + @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)); + } + } +} From 972b88c6e083bebb9291a9ac0ae152e1910a516f Mon Sep 17 00:00:00 2001 From: haeunsong Date: Sat, 8 Feb 2025 17:39:11 +0900 Subject: [PATCH 4/6] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Calculator.java | 1 - src/test/java/CalculatorTest.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/main/java/Calculator.java b/src/main/java/Calculator.java index 0a2d0a5f..113c6e85 100644 --- a/src/main/java/Calculator.java +++ b/src/main/java/Calculator.java @@ -13,7 +13,6 @@ public int multiply(int a, int b) { } public int divide(int a, int b) { - //b가 0이면 에러메세지 if (b == 0) { throw new ArithmeticException("Divide by zero"); } diff --git a/src/test/java/CalculatorTest.java b/src/test/java/CalculatorTest.java index 29d59106..a29c9e95 100644 --- a/src/test/java/CalculatorTest.java +++ b/src/test/java/CalculatorTest.java @@ -4,12 +4,10 @@ import static org.junit.jupiter.api.Assertions.*; -//한글 메서드명 대신 @DisplayName으로 쓰자 @DisplayName("초간단 계산기 테스트") class CalculatorTest { private final Calculator calculator = new Calculator(); - //@Nested 중첩 클래스 선언, 여러가지 메서드를 사용할 수 있음. @Nested class Add { @Test From ce29ca120aacda280b39657787a96bc92159d727 Mon Sep 17 00:00:00 2001 From: haeunsong Date: Sat, 8 Feb 2025 17:47:48 +0900 Subject: [PATCH 5/6] =?UTF-8?q?test=20:=20=EC=88=AB=EC=9E=90=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=8C=20=EA=B8=80=EC=9E=90=EA=B0=80=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=20=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/StringCalculatorTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/StringCalculatorTest.java b/src/test/java/StringCalculatorTest.java index 4e5a491f..16d9f00c 100644 --- a/src/test/java/StringCalculatorTest.java +++ b/src/test/java/StringCalculatorTest.java @@ -47,6 +47,7 @@ class runtimeExceptionTest { @DisplayName("숫자가 아닌 값이 포함되면 예외를 발생해야 한다.") void should_throw_exception_when_contains_non_number() { + // 특수문자가 포함된 경우 assertThatThrownBy(() -> stringCalculator.getSum("//;\n1;*;3")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); @@ -54,6 +55,11 @@ void should_throw_exception_when_contains_non_number() { assertThatThrownBy(() -> stringCalculator.getSum("//;\n1456;-;3")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); + + // 글자가 포함된 경우 + assertThatThrownBy(() -> stringCalculator.getSum("//;\nddd;ad;3")) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); } @Test From 15dad5413bfbaaa741e812689805a00b6d173cd4 Mon Sep 17 00:00:00 2001 From: haeunsong Date: Wed, 12 Feb 2025 02:24:42 +0900 Subject: [PATCH 6/6] =?UTF-8?q?refactor=20:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - should_throw_exception_when_another_wrong_input 이 경우 테스트를 통과하지 못했습니다. 차근차근 다시 해보겠습니다. --- src/main/java/StringCalculator.java | 78 ++++++++++++++++++------- src/test/java/StringCalculatorTest.java | 44 ++++++-------- 2 files changed, 77 insertions(+), 45 deletions(-) diff --git a/src/main/java/StringCalculator.java b/src/main/java/StringCalculator.java index a6ba5e7b..64ad4ddf 100644 --- a/src/main/java/StringCalculator.java +++ b/src/main/java/StringCalculator.java @@ -2,47 +2,81 @@ public class StringCalculator { - public int getSum(String str) { + /* + 1. 입력값 검증 + 2. 구분자 추출 + 3. 문자열을 구분자 기준으로 분리 + 4. 분리된 숫자 문자열 배열을 합산 + */ + public int splitStrBySeparaterAndGetSum(String str) { + // 1. 입력값 검증 if(str.length() == 0) { return 0; } - int sum = 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 = "[,|:]"; - String numbers = str; + // 커스텀 구분자가 존재하는 경우 if (str.startsWith("//") && str.contains("\n")) { - int newLineIdx = str.indexOf("\n"); - // /n 을 찾을 수 없거나, \n 이후 값이 없는 경우 + // /n 을 찾을 수 없는 경우 if (newLineIdx == -1) { - throw new RuntimeException("잘못된 입력 형식입니다."); + throw new RuntimeException("커스텀 구분자를 '//' 와 '\\n' 문자 사이에 정확하게 입력해주세요."); } separater = Pattern.quote(str.substring(2, newLineIdx)); - numbers = str.substring(newLineIdx + 1); // \n 다음부터 끝까지 숫자가 있는 부분들 + } + return separater; + } + + private String extractNumbers(String str) { + String numbers = ""; + + // 커스텀 구분자일 경우 + if(str.startsWith("//")) { + numbers = str.substring(str.indexOf("\n") + 1); + return numbers; + } + + // 기본 구분자이면 그대로 str 반환 + return str; + } - // \n 이후에 최소한 구분자가 한 개 이상은 있어야한다. 구분자가 없다면 배열 길이가 1이된다. - if(numbers.isEmpty() || numbers.split(separater).length == 1) { + private void validateTokens(String[] tokens) { + for(String token : tokens) { + if (token.isEmpty()) { throw new RuntimeException("잘못된 입력 형식입니다."); } - } - // 구분자를 기준으로 split 진행 후 sum 실행 - String[] tokens = numbers.split(separater); - if(tokens.length == 1) { - return 0; - } - for (String token : tokens) { - int num = parseNumber(token); - if (num < 0) { + + if(parseNumber(token) < 0) { throw new RuntimeException("음수는 사용할 수 없습니다."); } - sum += num; } + } + private int calculateSum(String[] tokens) { + int sum = 0; + for(String token : tokens) { + sum += Integer.parseInt(token); + } return sum; } - public int parseNumber(String number) { + private int parseNumber(String number) { // 숫자가 아니라면 예외 발생 try { return Integer.parseInt(number); @@ -50,4 +84,8 @@ public int parseNumber(String number) { throw new RuntimeException("숫자가 아닌 값이 포함되어 있습니다."); } } + + private boolean isNumeric(String str) { + return str.matches("-?\\d+"); // 정수 여부를 확인하는 정규식 (-는 음수 허용) + } } diff --git a/src/test/java/StringCalculatorTest.java b/src/test/java/StringCalculatorTest.java index 16d9f00c..418e34a8 100644 --- a/src/test/java/StringCalculatorTest.java +++ b/src/test/java/StringCalculatorTest.java @@ -1,6 +1,8 @@ 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; @@ -8,25 +10,20 @@ @DisplayName("문자열 계산기 테스트") class StringCalculatorTest { - StringCalculator stringCalculator = new StringCalculator(); + private StringCalculator stringCalculator = new StringCalculator(); @Nested @DisplayName("기본 구분자 테스트") class basicSeparaterTest { - @Test - @DisplayName("빈 문자열이면 0을 반환해야 한다.") - void should_return_0_when_string_is_empty() { - assertThat(0).isEqualTo(stringCalculator.getSum("")); - } - @Test - @DisplayName("구분자가 없는 경우 0을 반환해야 한다.") - void test() { - assertThat(0).isEqualTo(stringCalculator.getSum("123")); - } - @Test - @DisplayName("쉼표(,) 또는 콜론(:)으로 구분된 숫자들의 합을 반환해야 한다.") - void should_sum_numbers_separated_by_comma_or_colon() { - assertThat(6).isEqualTo(stringCalculator.getSum("1,2:3")); + + @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); } } @@ -36,7 +33,8 @@ class customSeparaterTest { @Test @DisplayName("커스텀 구분자로 분리한 숫자들의 합을 반환해야 한다") void should_sum_numbers_separated_by_custom_separator() { - assertThat(6).isEqualTo(stringCalculator.getSum("//;\n1;2;3")); + assertThat(6).isEqualTo(stringCalculator.splitStrBySeparaterAndGetSum("//;\n1;2;3")); + assertThat(2).isEqualTo(stringCalculator.splitStrBySeparaterAndGetSum("//;\n2")); } } @@ -48,16 +46,16 @@ class runtimeExceptionTest { void should_throw_exception_when_contains_non_number() { // 특수문자가 포함된 경우 - assertThatThrownBy(() -> stringCalculator.getSum("//;\n1;*;3")) + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;\n1;*;3")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); - assertThatThrownBy(() -> stringCalculator.getSum("//;\n1456;-;3")) + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;\n1456;-;3")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); // 글자가 포함된 경우 - assertThatThrownBy(() -> stringCalculator.getSum("//;\nddd;ad;3")) + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;\nddd;ad;3")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("숫자가 아닌 값이 포함되어 있습니다."); } @@ -66,7 +64,7 @@ void should_throw_exception_when_contains_non_number() { @DisplayName("음수가 포함되면 예외를 발생해야 한다.") void should_throw_exception_when_contains_minus() { - assertThatThrownBy(() -> stringCalculator.getSum("-4,2:-9")) + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("-4,2:-9")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("음수는 사용할 수 없습니다."); } @@ -75,11 +73,7 @@ void should_throw_exception_when_contains_minus() { @DisplayName("또 다른 잘못된 입력 형식의 경우 예외를 발생해야 한다.") void should_throw_exception_when_another_wrong_input() { - assertThatThrownBy(() -> stringCalculator.getSum("//;1;2;\n;")) - .isInstanceOf(RuntimeException.class) - .hasMessageContaining("잘못된 입력 형식입니다."); - - assertThatThrownBy(() -> stringCalculator.getSum("//;1;2;\n123")) + assertThatThrownBy(() -> stringCalculator.splitStrBySeparaterAndGetSum("//;1;2;\n;")) .isInstanceOf(RuntimeException.class) .hasMessageContaining("잘못된 입력 형식입니다.");