Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
df1e765
docs(readme): 문자열 덧셈 계산기 요구사항 및 TDD 절차 추가
shlish95 Oct 20, 2025
a011d6c
feat: 빈 문자열(null 포함) 입력 시 0 반환 기능 구현
shlish95 Oct 20, 2025
c9abf23
test: 기본 구분자(콤마/콜론) 합산 테스트 추가
shlish95 Oct 20, 2025
30b3f1e
feat: 기본 구분자(콤마/콜론) 기준 합산 기능 추가
shlish95 Oct 20, 2025
fc2402f
test: 커스텀 구분자 지원 테스트 추가
shlish95 Oct 20, 2025
d534fdc
feat: 커스텀 구분자 표준 형태 파싱 및 합산 기능 추가
shlish95 Oct 20, 2025
61bf591
test: 커스텀 구분자 리터럴 \"\\n\" 입력 지원 테스트 추가
shlish95 Oct 20, 2025
7191289
feat: 커스텀 구분자 리터럴 \"\\n\" 입력 파싱 지원
shlish95 Oct 20, 2025
a8d9901
refactor: 합산/파싱 로직 분리(sumTokens, ensureHasNumbers)로 예외 검증 준비
shlish95 Oct 20, 2025
648e299
test: 숫자 아님 입력 시 예외 테스트 추가
shlish95 Oct 20, 2025
b9936ce
feat: 숫자 아님 입력에 대한 IllegalArgumentException 변환
shlish95 Oct 20, 2025
d0be91f
test: 빈/공백 토큰 입력 시 예외 테스트 추가
shlish95 Oct 20, 2025
a010b85
feat: 빈/공백 토큰 검증 추가
shlish95 Oct 20, 2025
35289b5
test: 0과 음수 입력 시 예외 테스트 추가
shlish95 Oct 20, 2025
04d103c
feat: 0 및 음수 입력에 대한 검증 추가(양수만 허용)
shlish95 Oct 20, 2025
120350f
feat: Console 입출력 통합(Application) 및 결과 출력
shlish95 Oct 20, 2025
6cb381f
refactor: 커스텀 구분자 유효성 검증 로직 메서드로 분리(validateDelimiter)
shlish95 Oct 20, 2025
a34ce02
refactor: 커스텀 구분자 유효성 검증 로직 메서드로 분리(validateDelimiter)
shlish95 Oct 20, 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
78 changes: 77 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
# java-calculator-precourse
# 문자열 덧셈 계산기

## 🚀 기능 요구 사항

입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.

### 기본 기능
- 입력이 비어 있는 경우 `0`을 반환한다.
- 쉼표(`,`) 또는 콜론(`:`)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리하여 합산한다.
- 예: `"" → 0`, `"1,2" → 3`, `"1,2:3" → 6`
- 커스텀 구분자를 지정할 수 있다.
- `"//;\n1;2;3"` → 커스텀 구분자 `;`, 결과 `6`
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨다.
- 프로그램은 `System.exit()`를 호출하지 않는다.

---

## 🧩 예외 처리 조건

| 구분 | 설명 | 예시 |
|------|------|------|
| 빈 문자열 | 입력이 비어 있으면 `0` 반환 | `""` |
| 음수 입력 | `IllegalArgumentException` 발생 | `"-1,2,3"` |
| 0 입력 | `IllegalArgumentException` 발생 | `"0,1,2"` |
| 숫자 아님 | `IllegalArgumentException` 발생 | `"1,a,3"` |
| 연속된 구분자 | `IllegalArgumentException` 발생 | `"1,,2"` |
| 잘못된 커스텀 구분자 형식 | `IllegalArgumentException` 발생 | `"//;\n"` or `"//\n1,2"` |

---

## 🧪 TDD 진행 순서

### 1단계 (Red)
- `StringCalculatorTest` 작성
- 빈 문자열 입력 시 `0`을 반환하는 테스트 추가

### 2단계 (Green)
- 테스트 통과를 위한 최소한의 코드 작성

### 3단계 (Refactor)
- 코드 중복 제거 및 메서드 분리

---

### 전체 TDD 사이클 계획

| 단계 | 목표 | 예시 입력 | 기대 결과 |
|------|------|------------|-------------|
| 1 | 빈 문자열 처리 | `""` | `0` |
| 2 | 기본 구분자 처리 | `"1,2:3"` | `6` |
| 3 | 커스텀 구분자 처리 | `"//;\n1;2;3"` | `6` |
| 4 | 잘못된 입력 예외 처리 | `"-1,2,3"` | 예외 발생 |
| 5 | 콘솔 입출력 및 Application 통합 | 사용자 입력 `"1,2:3"` | `"결과 : 6"` 출력 |

---

## 💻 실행 예시
덧셈할 문자열을 입력해 주세요. </br>
1,2:3 </br>
결과 : 6


---

## 🔧 개발 환경

- **JDK:** 21
- **빌드 도구:** Gradle (build.gradle 수정 불가)
- **입출력:** `camp.nextstep.edu.missionutils.Console`
- **테스트:** JUnit + AssertJ (`NsTest` 기반 테스트 제공)
- **코드 스타일:** Google Java Style Guide
- 블록 들여쓰기: 4 스페이스
- 최대 줄 길이: 120자
- 줄바꿈 시 들여쓰기: +8 스페이스 이상



14 changes: 13 additions & 1 deletion src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package calculator;

import calculator.domain.StringCalculator;
import camp.nextstep.edu.missionutils.Console;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
try {
System.out.println("덧셈할 문자열을 입력해 주세요.");

String input = Console.readLine();
int result = StringCalculator.add(input);

System.out.println("결과 : " + result);
} finally {
Console.close();
}
}
}
86 changes: 86 additions & 0 deletions src/main/java/calculator/domain/StringCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package calculator.domain;

import java.util.regex.Pattern;

public final class StringCalculator {

private StringCalculator() {
}

public static int add(String input) {
if (input == null || input.isEmpty()) {
return 0;
}

if (input.startsWith("//")) {
int realNl = input.indexOf('\n');
int litNl = input.indexOf("\\n");

if (realNl < 0 && litNl < 0) {
throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다.");
}

if (realNl >= 0) {
String delimiter = input.substring(2, realNl);
validateDelimiter(delimiter);

String body = input.substring(realNl + 1);
ensureHasNumbers(body);

return sumTokens(body.split(Pattern.quote(delimiter)));
}

String delimiter = input.substring(2, litNl);
validateDelimiter(delimiter);

String body = input.substring(litNl + 2);
ensureHasNumbers(body);

return sumTokens(body.split(Pattern.quote(delimiter)));
}

return sumTokens(input.split("[,:]"));
}

private static void validateDelimiter(String delimiter) {
if (delimiter.isEmpty()) {
throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다.");
}
}

private static int sumTokens(String[] tokens) {
int sum = 0;

for (String token : tokens) {
sum += parsePositive(token);
}

return sum;
}

private static int parsePositive(String token) {
if (token == null || token.isBlank()) {
throw new IllegalArgumentException("빈 값은 허용되지 않습니다.");
}

final int number;

try {
number = Integer.parseInt(token);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("숫자만 입력 가능합니다.: " + token);
}

if (number <= 0) {
throw new IllegalArgumentException("양수만 허용됩니다.: " + token);
}

return number;
}

private static void ensureHasNumbers(String body) {
if (body == null || body.isEmpty()) {
throw new IllegalArgumentException("덧셈할 숫자가 없습니다.");
}
}
}
52 changes: 52 additions & 0 deletions src/test/java/calculator/domain/StringCalculatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package calculator.domain;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;
class StringCalculatorTest {

@Test
void 빈문자열은_0을_반환한다() {
assertEquals(0, StringCalculator.add(""));
assertEquals(0, StringCalculator.add(null));
}

@Test
void 기본구분자_콤마_콜론으로_합산한다() {
assertEquals(3, StringCalculator.add("1,2"));
assertEquals(6, StringCalculator.add("1,2,3"));
assertEquals(6, StringCalculator.add("1,2:3"));
}

@Test
void 커스텀_구분자_실제개행_지원한다() {
assertEquals(6, StringCalculator.add("//;\n1;2;3"));
}

@Test
void 커스텀_구분자_리터럴_역슬래시n_지원한다() {
assertEquals(1, StringCalculator.add("//;\\n1"));
}

@Test
void 커스텀_구분자_비어있으면_예외() {
assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("//\n1,2,3"));
}

@Test
void 숫자아님은_예외() {
assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("1,a"));
}

@Test
void 빈토큰_혹은_공백토큰은_예외() {
assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("1,,2"));
assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("1, ,2"));
}

@Test
void 영_혹은_음수는_예외() {
assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("0,1"));
assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("-1,2,3"));
}
}