Skip to content

[이동훈] 1차시 미션 제출합니다. #106

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

Merged
merged 28 commits into from
Aug 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4ee4ba1
feat. 사칙연산 계산기 구현
dh2906 Jul 30, 2025
54afa40
feat. 0으로 나누는 경우 예외 발생
dh2906 Jul 30, 2025
06502aa
fix. 연산 후 int 범위를 벗어나면 예외 발생하도록 수정
dh2906 Jul 30, 2025
faa2f88
feat. 누락된 메소드 접근 제어자 추가
dh2906 Jul 30, 2025
6aaeab5
feat. 사칙연산 단위 테스트 추가
dh2906 Jul 31, 2025
eae23cd
fix. 0으로 나눌 경우 발생하는 예외 종류 수정
dh2906 Jul 30, 2025
0b0bd61
feat. 사칙연산 예외 발생 단위 테스트 추가
dh2906 Jul 30, 2025
4a76f60
feat. 문자열 계산기 구현
dh2906 Jul 31, 2025
4b08af2
feat. 문자열 계산기 테스트 추가
dh2906 Jul 31, 2025
11d80e0
build. AssertJ 의존성 추가
dh2906 Jul 31, 2025
682929a
refactor. 사칙연산 계산기 테스트 코드 JUnit -> AssertJ로 리팩토링
dh2906 Jul 31, 2025
65e7e2c
fix. 문자열이 비어있는지 검증하기 전 커스텀 구분자 파싱으로 발생하는 문제 해결
dh2906 Jul 31, 2025
98b0f3f
refactor. 문자열 계산기 테스트 코드 JUnit -> AssertJ로 리팩토링
dh2906 Jul 31, 2025
2394319
refactor. 에러 메시지를 하드 코딩 방식에서 상수 변수로 분리
dh2906 Jul 31, 2025
6546a24
refactor. 에러 메시지 포함 검증 시 상수 변수 활용
dh2906 Jul 31, 2025
3357666
style. 개행 컨벤션 적용
dh2906 Jul 31, 2025
6e1cc54
refactor. 메소드 책임 분할
dh2906 Jul 31, 2025
015db6f
fix. 단어 철자 오타 수정
dh2906 Jul 31, 2025
98575d9
fix. 메소드 명 수정
dh2906 Jul 31, 2025
32da58d
chore. 이전에 구현한 계산기 클래스 삭제
dh2906 Aug 2, 2025
fa35922
fix. 공백 문자열이 들어온 경우 계산 결과 0 반환하도록 수정
dh2906 Aug 2, 2025
f7824c7
fix. 문자열로 공백이 들어온 경우 테스트 분할
dh2906 Aug 2, 2025
640f52e
feat. 에러 메세지 집합 클래스 생성
dh2906 Aug 2, 2025
e71b3db
refactor. ErrorMessage 상수 사용하도록 변경
dh2906 Aug 2, 2025
859680d
refactor. 구분자 정규 표현식 필드 변수 -> 메소드 지역 변수로 수정
dh2906 Aug 2, 2025
b2a779c
refactor. 문자열 유틸 클래스로 분할
dh2906 Aug 2, 2025
d8d8ecc
refactor. 유틸 테스트 클래스 분할
dh2906 Aug 2, 2025
a5edf2c
fix. 잘못된 에러 메시지 반환하는 문제 수정
dh2906 Aug 2, 2025
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
3 changes: 1 addition & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ repositories {
}

dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation('org.junit.jupiter:junit-jupiter')
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')
}

Expand Down
31 changes: 0 additions & 31 deletions src/main/java/Calculator.java

This file was deleted.

58 changes: 0 additions & 58 deletions src/main/java/StringCalculator.java

This file was deleted.

25 changes: 25 additions & 0 deletions src/main/java/domain/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package domain;

import exception.ErrorMessage;

public class Calculator {

public int add(int num1, int num2) {
return Math.addExact(num1, num2);

Choose a reason for hiding this comment

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

~~Exact() 메서드는 어떤 장점이 있나요?
일반 연산자(+, - 등) 과 어떤 차이점이 있나요?

Copy link
Author

Choose a reason for hiding this comment

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

대표적인 예로 addExact는 비트 연산을 통해 int 타입인 두 수의 합이 int형의 저장 가능 범위를 벗어난다면 예외를 발생시켜주고 있습니다.
일반적인 연산자를 사용한다면 오버플로우 발생 시 부호 비트가 값의 크기를 나타내기 위한 비트로 사용되서 결과 값이 예상치 못한 값으로 나올 수 있습니다.

}

public int sub(int num1, int num2) {
return Math.subtractExact(num1, num2);
}

public int mul(int num1, int num2) {
return Math.multiplyExact(num1, num2);
}

public int div(int num1, int num2) {
if (num2 == 0)
throw new ArithmeticException(ErrorMessage.DIVIDE_BY_ZERO);

return num1 / num2;
}
}
52 changes: 52 additions & 0 deletions src/main/java/domain/StringCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package domain;

import exception.ErrorMessage;
import util.DelimiterUtil;
import util.ParseUtil;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

public class StringCalculator {

public int calculate(String str) {
if (str == null) {
throw new RuntimeException(ErrorMessage.NULL_STRING);
}

if (str.isBlank())
return 0;

String customDelimiter = DelimiterUtil.findCustomDelimiter(str);
String strNumbers = extractNumber(str, customDelimiter);
String[] tokens = splitTokens(strNumbers, customDelimiter);
List<Integer> numbers = ParseUtil.parseNumber(tokens);

return sum(numbers);
}

private String extractNumber(String str, String customDelimiter) {
if (customDelimiter != null) {
return str.substring(str.indexOf("\n") + 1);
}

return str;
}

private String[] splitTokens(String strNumbers, String customDelimiter) {
String delimiterRegex = "[,|:]";

if (customDelimiter == null) {
return strNumbers.split(delimiterRegex);
}

return strNumbers.split(Pattern.quote(customDelimiter));
}

private int sum(List<Integer> numbers) {
return numbers.stream()
.mapToInt(Integer::intValue)
.sum();
}
}
14 changes: 14 additions & 0 deletions src/main/java/exception/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package exception;

public class ErrorMessage {

public static final String NULL_STRING = "문자열에 null 값이 전달되었습니다.";
public static final String CUSTOM_DELIMITER_NOT_FOUND = "커스텀 구분자를 찾을 수 없습니다.";
public static final String NEGATIVE_NUMBER_NOT_ALLOWED = "음수는 처리할 수 없습니다.";
public static final String INVALID_STRING = "문자열은 처리할 수 없습니다.";
public static final String DIVIDE_BY_ZERO = "0으로 나눌 수 없습니다.";

private ErrorMessage() {
throw new RuntimeException();
}
}
21 changes: 21 additions & 0 deletions src/main/java/util/DelimiterUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package util;

import exception.ErrorMessage;

public class DelimiterUtil {

public static String findCustomDelimiter(String str) {
int startDelimiterIdx = str.indexOf("//");
int endDelimiterIdx = str.indexOf("\n");

if (startDelimiterIdx == -1 && endDelimiterIdx == -1) {
return null;
}

if ((startDelimiterIdx == -1) ^ (endDelimiterIdx == -1) || (startDelimiterIdx + 2 == endDelimiterIdx)) {
throw new RuntimeException(ErrorMessage.CUSTOM_DELIMITER_NOT_FOUND);
}

return str.substring(startDelimiterIdx + 2, endDelimiterIdx);
}
}
29 changes: 29 additions & 0 deletions src/main/java/util/ParseUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package util;

import exception.ErrorMessage;

import java.util.Arrays;
import java.util.List;

public class ParseUtil {

public static List<Integer> parseNumber(String[] tokens) {
return Arrays.stream(tokens)
.map(ParseUtil::parseTokenToInt)
.toList();
}

public static int parseTokenToInt(String token) {
try {
int number = Integer.parseInt(token.trim());

if (number < 0) {
throw new RuntimeException(ErrorMessage.NEGATIVE_NUMBER_NOT_ALLOWED);
}

return number;
} catch (NumberFormatException ex) {
throw new RuntimeException(ErrorMessage.INVALID_STRING);
}
}
}
95 changes: 56 additions & 39 deletions src/test/java/CalculatorTest.java
Original file line number Diff line number Diff line change
@@ -1,66 +1,83 @@
import org.junit.jupiter.api.DisplayName;
import domain.Calculator;
import exception.ErrorMessage;
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.*;

@DisplayName("계산기 기능 테스트 클래스")
public class CalculatorTest {

private final Calculator calculator = new Calculator();

@Test
public void 덧셈_테스트() {
final int num1 = 1;
final int num2 = 2;
final int actual = calculator.add(num1, num2);

assertThat(actual).isEqualTo(num1 + num2);
}

@Test
@DisplayName("더하기 연산 테스트")
public void testAdd() {
int num1 = 1, num2 = 2;
public void 뺄셈_테스트() {
final int num1 = 3;
final int num2 = 2;
final int actual = calculator.sub(num1, num2);

assertThat(3).isEqualTo(Calculator.add(num1, num2));
assertThat(actual).isEqualTo(num1 - num2);
}

@Test
@DisplayName("빼기 연산 테스트")
public void testSub() {
int num1 = 1, num2 = 2;
public void 곱셈_테스트() {
final int num1 = 3;
final int num2 = 2;
final int actual = calculator.mul(num1, num2);

assertThat(-1).isEqualTo(Calculator.sub(num1, num2));
assertThat(actual).isEqualTo(num1 * num2);
}

@Test
@DisplayName("곱하기 연산 테스트")
public void testMul() {
int num1 = 10, num2 = 2;
public void 나눗셈_테스트() {
final int num1 = 4;
final int num2 = 2;
final int actual = calculator.div(num1, num2);

assertThat(20).isEqualTo(Calculator.mul(num1, num2));
assertThat(actual).isEqualTo(num1 / num2);
}

@Test
@DisplayName("나누기 연산 테스트")
public void testDiv() {
int num1 = 1, num2 = 2;
public void 덧셈_오버플로우_예외_발생() {
final int num1 = Integer.MAX_VALUE;
final int num2 = Integer.MAX_VALUE;

assertThat(0).isEqualTo(Calculator.div(num1, num2));
assertThatThrownBy(() -> calculator.add(num1, num2))
.isInstanceOf(ArithmeticException.class);
}

@Test
@DisplayName("나누기 연산에서 나누는 값이 0인지 테스트")
public void testDivideByZero() {
int num1 = 2, num2 = 0;
public void 뺄셈_오버플로우_예외_발생() {
final int num1 = Integer.MIN_VALUE;
final int num2 = Integer.MAX_VALUE;

assertThatThrownBy(() -> {
Calculator.div(num1, num2);
}).isInstanceOf(ArithmeticException.class);
assertThatThrownBy(() -> calculator.sub(num1, num2))
.isInstanceOf(ArithmeticException.class);
}

@Test
@DisplayName("연산 후 int 범위를 벗어나는 지 테스트")
public void testOverflow() {
assertThatThrownBy(() -> {
Calculator.add(Integer.MAX_VALUE, 1);
}).isInstanceOf(ArithmeticException.class);

assertThatThrownBy(() -> {
Calculator.sub(Integer.MIN_VALUE, 1);
}).isInstanceOf(ArithmeticException.class);

assertThatThrownBy(() -> {
Calculator.mul(Integer.MAX_VALUE, 2);
}).isInstanceOf(ArithmeticException.class);
public void 곱셈_오버플로우_예외_발생() {
final int num1 = Integer.MAX_VALUE;
final int num2 = Integer.MAX_VALUE;

assertThatThrownBy(() -> calculator.mul(num1, num2))
.isInstanceOf(ArithmeticException.class);
}

@Test
public void 나눗셈_0으로_나누면_예외_발생() {
final int num1 = Integer.MAX_VALUE;
final int num2 = 0;

assertThatThrownBy(() -> calculator.div(num1, num2))
.isInstanceOf(ArithmeticException.class)
.hasMessageContaining(ErrorMessage.DIVIDE_BY_ZERO);
}
}
Loading