Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 2단계 - 문자열 덧셈 계산기 #5916

Merged
merged 7 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/main/java/step2/calculator/StringAddCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package step2.calculator;

public class StringAddCalculator {

public static int calculate(String input) {
validate(input);
StringAddCalculatorToken[] tokens = split(input);
return sum(tokens);
}

private static void validate(String input) {
StringAddCalculatorInputValidator.validate(input);
}

private static StringAddCalculatorToken[] split(String input) {
return StringAddCalculatorTokenGenerator.generateTokens(input);
}

private static int sum(StringAddCalculatorToken[] tokens) {
return StringAddCalculatorTokenCalculator.sum(tokens);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package step2.calculator;

class StringAddCalculatorInputValidatorException extends RuntimeException {

public StringAddCalculatorInputValidatorException(String message) {
super(message);
}
}

public class StringAddCalculatorInputValidator {

public static void validate(String input) {
if (input == null || input.isEmpty()) {
// "null" and "Empty String" are valid in StringAddCalculator
return;
}

if (input.isBlank()) {
throw new StringAddCalculatorInputValidatorException("Input cannot be blank");
}
}
}
39 changes: 39 additions & 0 deletions src/main/java/step2/calculator/StringAddCalculatorToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package step2.calculator;


class StringAddCalculatorTokenException extends RuntimeException {

public StringAddCalculatorTokenException(String message) {
super(message);
}
}


public class StringAddCalculatorToken {

private final int value;

public StringAddCalculatorToken(String value) {
if (value.isEmpty()) {
this.value = 0;
return;
}
this.value = Integer.parseInt(value);
validate();
}

public StringAddCalculatorToken(int number) {
this.value = number;
validate();
}

private void validate() {
if (this.value < 0) {
throw new StringAddCalculatorTokenException("Don't allow negative number");
}
}

public int toInteger() {
return this.value;
}
}
Comment on lines +12 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 👍 👍

그런데 Exception과 같은 파일에 선언해두면 클래스를 찾기 어려우니 독립적으로 선언해주시면 더 좋을것 같습니다. :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package step2.calculator;

public class StringAddCalculatorTokenCalculator {

public static int sum(StringAddCalculatorToken[] tokens) {
int result = 0;
for (StringAddCalculatorToken stringAddCalculatorToken : tokens) {
result += stringAddCalculatorToken.toInteger();
}
return result;
}
}
Comment on lines +3 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 sum을 할 수 있는 별도의 역할을 표현해주셨네요~!

이와 관련하여 일급컬렉션이라는 개념을 한번 찾아보시고 다음 미션부터 참고하여 반영해보시면 좋을것 같습니다 :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package step2.calculator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringAddCalculatorTokenGenerator {

final static String[] defaultDelimiters = {",", ":"};
final static String[] defaultCustomDelimiterRegexes = {"//(\\D)\n(.*)"};

public static StringAddCalculatorToken[] generateTokens(String input) {
if (input == null || input.isEmpty()) {
return new StringAddCalculatorToken[]{new StringAddCalculatorToken(0)};
}

ArrayList<String> delimiters = new ArrayList<>(Arrays.asList(defaultDelimiters));

for (String customDelimiterRegex : defaultCustomDelimiterRegexes) {
Matcher matcher = Pattern.compile(customDelimiterRegex).matcher(input);
if (matcher.find()) {
String customDelimiter = matcher.group(1);
delimiters.add(customDelimiter);
input = matcher.group(2);
Comment on lines +23 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1과 2는 어떠한 의미를 가지는 숫자일까요? 🤔

다른사람이 단번에 알아차릴 수 있도록 이름을 부여해보는건 어떨까요?

https://bottom-to-top.tistory.com/100

}
}
Comment on lines +20 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 일련의 과정도 이 애플리케이션에서 아주 중요한 항목으로 보이기 때문에 굉장히 테스트를 하면 좋을 부분으로 보이네요!

이렇게 패턴을 찾고 문자열과 구분자를 분리해내는 것도 하나의 클래스가 담당할 수 있게 만들어보면 좋을 것 같습니다~

그리고 거기서 더 나아간다면 확장에 용이하도록 디자인 패턴(전략 패턴)을 적용해볼 수 있을것 같네요!

지금하기에는 너무 많은 변경사항이니 관련하여 슬~쩍 찾아만 보셔도 좋을것 같습니다. :)


String[] stringTokens = input.split(String.format("[%s]", String.join("", delimiters)));
if (input.startsWith("-") && delimiters.contains("-")) {
stringTokens[1] = "-" + stringTokens[1];
}

StringAddCalculatorToken[] stringAddCalculatorTokens = new StringAddCalculatorToken[stringTokens.length];
for (int i = 0; i < stringTokens.length; i++) {
stringAddCalculatorTokens[i] = new StringAddCalculatorToken(stringTokens[i]);
}

return stringAddCalculatorTokens;
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package study;
package step1.study;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;


Expand All @@ -25,21 +26,23 @@ void setUp() {
numbers.add(3);
}

// Test Case 구현
@Test
void srs1Test() {
assertThat(numbers.size()).isEqualTo(3);
@DisplayName("set.size()")
void size() {
assertThat(numbers).hasSize(3);
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void srs2Test(int number) {
assertThat(numbers.contains(number)).isTrue();
@DisplayName("set.contains() #1")
void contains1(int number) {
assertThat(numbers).contains(number);
}

@ParameterizedTest
@CsvSource(value = {"1:true", "2:true", "3:true", "4:false", "5:false"}, delimiter = ':')
void srs3Test(int number, boolean expected) {
@DisplayName("set.contains() #2")
void contains2(int number, boolean expected) {
assertThat(numbers.contains(number)).isEqualTo(expected);
}
}
39 changes: 39 additions & 0 deletions src/test/java/step1/study/StringTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package step1.study;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

public class StringTest {

@Test
@DisplayName("String.split()")
void split() {
String value1 = "1,2";
assertThat(value1.split(",")).containsExactly("1", "2");

String value2 = "1";
assertThat(value2.split(",")).containsExactly("1");
}

@Test
@DisplayName("String.substring()")
void substring() {
String value = "(1,2)";
assertThat(value.substring(1, value.length() - 1)).isEqualTo("1,2");
}

@Test
@DisplayName("String.charAt()")
void charAt() {
String value = "abc";
assertThat(value.charAt(0)).isEqualTo('a');
assertThat(value.charAt(1)).isEqualTo('b');
assertThat(value.charAt(2)).isEqualTo('c');
assertThatThrownBy(() -> {value.charAt(3);}).
isExactlyInstanceOf(StringIndexOutOfBoundsException.class);
}
}
87 changes: 87 additions & 0 deletions src/test/java/step2/calculator/StringAddCalculatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package step2.calculator;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class StringAddCalculatorTest {

@Test
@DisplayName("input: null --> output: 0")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 보통 테스트 이름은 서술적으로 표현하는 편인 것 같습니다~

"입력이 null이면 기본값을 반환한다." 와 같이 지을 수 있을것 같네요 :)

public void givenNull_whenCalculate_thenReturnZero() {
int result = StringAddCalculator.calculate(null);
assertThat(result).isEqualTo(0);
}

@Test
@DisplayName("input: \"\"(=Empty String) --> output: 0")
public void givenEmptyString_whenCalculate_thenReturnZero() {
int result = StringAddCalculator.calculate("");
assertThat(result).isEqualTo(0);
}

@Test
@DisplayName("input: \" \"(=Blank) --> output: RuntimeException")
public void givenBlank_whenCalculate_thenThrowException() {
assertThatThrownBy(() -> StringAddCalculator.calculate(" ")).isExactlyInstanceOf(
StringAddCalculatorInputValidatorException.class);
}

@Test
@DisplayName("input: \"1\" --> output: 1")
public void givenOneNumber_whenCalculate_thenReturnJustNumber() {
int result = StringAddCalculator.calculate("1");
assertThat(result).isEqualTo(1);
}

@Test
@DisplayName("input: \"1,2\" --> output: 3")
public void givenTwoNumbersWithComma_whenCalculate_thenReturnSum() {
int result = StringAddCalculator.calculate("1,2");
assertThat(result).isEqualTo(3);
}

@Test
@DisplayName("input: \"1,2:3\" --> output: 6")
public void givenThreeNumbersWithCommaAndColon_whenCalculate_thenReturnSum() {
int result = StringAddCalculator.calculate("1,2:3");
assertThat(result).isEqualTo(6);
}

@Test
@DisplayName("input: \"//;\\n1;2;3\" --> output: 6")
public void givenThreeNumbersWithCustomDelimiter_whenCalculate_thenReturnSum() {
int result = StringAddCalculator.calculate("//;\n1;2;3");
assertThat(result).isEqualTo(6);
}

@Test
@DisplayName("input: \"//-\n1-2-3\" --> output: 6")
public void givenThreeNumbersWithCustomDelimiterMinus_whenCalculate_thenReturnSum() {
int result = StringAddCalculator.calculate("//-\n1-2-3");
assertThat(result).isEqualTo(6);
}

@Test
@DisplayName("input: \"-1,2,3\" --> output: StringAddCalculatorTokenException(=RuntimeException)")
public void givenNegativeNumber_whenCalculate_thenThrowException() {
assertThatThrownBy(() -> StringAddCalculator.calculate("-1,2,3")).isExactlyInstanceOf(
StringAddCalculatorTokenException.class);
}

@Test
@DisplayName("input: \"//-\n-1-2-3\" --> output: RuntimeException")
public void givenNegativeNumberWithCustomDelimiterMinus_whenCalculate_thenThrowException() {
assertThatThrownBy(() -> StringAddCalculator.calculate("//-\n-1-2-3")).isExactlyInstanceOf(
StringAddCalculatorTokenException.class);
}

@Test
@DisplayName("input: \"11,22:33\" --> output: 66")
public void givenTwoDigits_whenCalculate_thenReturnSum() {
int result = StringAddCalculator.calculate("11,22:33");
assertThat(result).isEqualTo(66);
}
}
47 changes: 0 additions & 47 deletions src/test/java/study/StringTest.java

This file was deleted.