diff --git a/README.md b/README.md index bd90ef0247..6d187226fc 100644 --- a/README.md +++ b/README.md @@ -1 +1,140 @@ -# java-calculator-precourse \ No newline at end of file +# java-calculator-precourse + +## 문자열 덧셈 계산기 + +입력한 문자열에서 **숫자를 추출하여 합산하는 계산기**를 구현한다. +기본 구분자는 쉼표(`,`)와 콜론(`:`)이며, +사용자는 `"//[구분자]\\n"` 형식으로 **커스텀 구분자**를 지정할 수 있다. +잘못된 입력에 대해서는 `IllegalArgumentException`을 발생시키고 프로그램을 종료한다. + +--- + +## 1. 기능 구현 목록 + +### 1. 입력 +- [x] 콘솔에서 문자열을 입력받는다 + - `camp.nextstep.edu.missionutils.Console.readLine()`을 사용 + - 예: `"1,2:3"` + + +### 2. 입력 검증 +- [x] null, 빈 문자열, 커스텀 구분자 헤더 형식을 검증한다 → `IllegalArgumentException` +- [x] null 입력 → 예외 발생 +- [x] 빈 문자열(`""`) 입력 → 정상 처리 +- [x] 커스텀 구분자 형식 오류 (예: `"//;\n"` 누락) → 예외 발생 +- [ ] 비숫자/음수 검증은 문자열 파싱 단계에서 수행 + + +### 3. 구분자 파싱 +- [x] 입력값의 맨 앞에 `"//"`가 있고, 그 뒤에 `"\n"`이 있으면 커스텀 구분자로 인식한다 + - `"//;\n1;2;3"` → 구분자: `;` → 결과: `6` +- [x] 커스텀 구분자는 **여러 문자를 허용**한다 + - `"//***\n1***2***3"` → 결과: `6` +- [x] 커스텀 구분자에 공백 포함 시 → 예외 발생 `IllegalArgumentException` +- [x] 예약어(`//`, `\n`, `,`, `:`) 사용 불가 → 예외 발생 `IllegalArgumentException` +- [x] 커스텀 구분자 식별에 실패할 경우 → `IllegalArgumentException` + + +### 4. 문자열 파싱 +- [x] 구분자(기본 또는 커스텀)를 기준으로 문자열을 분리한다 +- [x] 분리된 각 요소가 숫자로만 이루어져 있는지 검사한다 + - `"a"`, `"--"`, `"-2"` 등 → `IllegalArgumentException` +- [x] 숫자로 변환 불가 시 → `IllegalArgumentException` +- [x] 정상 입력이면 문자열 배열을 반환한다 + - 입력: `"1,2:3"` → 출력: `["1", "2", "3"]` + +### 5. 계산 +- [x] 문자열 배열을 정수 배열로 변환한다 +- [x] 모든 정수를 합산하여 결과를 반환한다 +- [x] 음수 또는 비정상 입력 시 → `IllegalArgumentException` + - 입력: `["1", "-2", "3"]` → 예외 발생 + - 입력: `["1", "a", "3"]` → 예외 발생 + + +### 6. 출력 +- [x] 최종 합산 결과를 `"결과 : X"` 형식으로 출력한다 + - 예: `"결과 : 6"` + +--- + +## 2. 예외 처리 정책 + +### 1. 입력 단계 +| 조건 | 처리 방식 | 메시지 | +|------|----------------------------|-------------| +| null 입력 | `IllegalArgumentException` | `"입력값이 존재하지 않습니다."` | +| 빈 문자열 | 정상 처리 | - | +| 커스텀 구분자 형식 오류 | `IllegalArgumentException` | `"커스텀 구분자 형식이 올바르지 않습니다."` | + + +### 2. 검증 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 음수 입력 | `IllegalArgumentException` | `"음수는 입력할 수 없습니다."` | +| 비숫자 포함 | `IllegalArgumentException` | `"숫자와 구분자만 입력해주세요."` | + + +### 3. 구분자 파싱 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 커스텀 구분자 식별 실패 | `IllegalArgumentException` | `"커스텀 구분자 형식이 올바르지 않습니다."` | +| 공백 포함 | `IllegalArgumentException` | `"공백은 구분자로 사용할 수 없습니다."` | +| 예약어(`//`, `\n`, `,`, `:`) 포함 | `IllegalArgumentException` | `"예약어는 구분자로 사용할 수 없습니다."` | + + +### 4. 문자열 파싱 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 숫자 변환 실패 | `IllegalArgumentException` | `"숫자 형식이 올바르지 않습니다."` | +| 음수 존재 | `IllegalArgumentException` | `"음수는 입력할 수 없습니다."` | + + +### 5. 계산 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 파싱 결과가 비어 있음 | `IllegalArgumentException` | `"계산할 숫자가 없습니다."` | + + +--- + +## 3. 프로젝트 구조 + +``` +src +└─ main/java/calculator + ├─ Application.java # 실행 진입점(main) + │ + ├─ controller + │ └─ CalculatorController.java # 전체 흐름 제어 + │ + ├─ view + │ ├─ InputView.java # 사용자 입력 + │ └─ OutputView.java # 결과 출력 + │ + ├─ model + │ ├─ calculator # 문자열 파싱 및 합산 로직 + │ │ ├─ Calculator.java # 계산 로직(덧셈) + │ │ └─ Result.java # 계산 결과를 값 객체로 캡슐화 + │ ├─ parser + │ │ ├─ DelimiterParser.java # 기본/커스텀 구분자 추출 + │ │ ├─ NumberParser.java # 문자열 파싱, 숫자 배열로 변환 + │ │ └─ DelimiterPattern.java # 커스텀 구분자 패턴 정의 + │ └─ validation + │ └─ Validator.java # 입력값 유효성 검사 + │ + └─ util + ├─ Constants.java # 기본 구분자, 정규식 패턴 등의 상수 + └─ ExceptionMessages.java # 예외 메시지 +``` + +--- +## 4. 품질 요구사항 + +- **코드 컨벤션 준수** + - Java Style Guide + - Git Commit Convention +- **단일 책임 원칙 준수** +- **예외 명세화** + - ExceptionMessages를 도입해 예외 메시지를 상수로 관리 + - 하드코딩된 메시지를 제거하여 유지보수성과 일관성 강화 + diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb40..bdff7930e1 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -1,7 +1,9 @@ package calculator; +import calculator.controller.CalculatorController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + new CalculatorController().run(); } } diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java new file mode 100644 index 0000000000..2c8508faee --- /dev/null +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -0,0 +1,48 @@ +package calculator.controller; + +import calculator.model.calculator.Calculator; +import calculator.model.calculator.Result; +import calculator.model.parser.DelimiterParseResult; +import calculator.model.parser.DelimiterParser; +import calculator.model.parser.NumberParser; +import calculator.model.validation.Validator; +import calculator.view.InputView; +import calculator.view.OutputView; + +public class CalculatorController { + + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + + public void run() { + String input = readInput(); + Result result = processInput(input); + printResult(result); + } + + /** 사용자 입력 */ + private String readInput() { + return inputView.read(); + } + + /** 입력 처리 로직 (공통 사용: run() + testRun()) */ + private Result processInput(String input) { + Validator.validateInput(input); + + DelimiterParseResult parseResult = DelimiterParser.parse(input); + String[] tokens = NumberParser.parse(parseResult.getBody(), parseResult.getDelimiters()); + + return Calculator.calculate(tokens); + } + + /** 결과 출력 */ + private void printResult(Result result) { + outputView.printResult(result); + } + + /** 테스트 전용 메서드 (콘솔 I/O 배제) */ + public String testRun(String input) { + Result result = processInput(input); + return "결과 : " + result.getValue(); + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/calculator/Calculator.java b/src/main/java/calculator/model/calculator/Calculator.java new file mode 100644 index 0000000000..3d0ceeaadd --- /dev/null +++ b/src/main/java/calculator/model/calculator/Calculator.java @@ -0,0 +1,40 @@ +package calculator.model.calculator; + +import calculator.util.ExceptionMessages; +import java.util.Arrays; + +/** + * 파싱된 문자열 토큰을 정수로 변환해 합산한다 + */ +public class Calculator { + + private Calculator() {} + + public static Result calculate(String[] tokens) { + if (tokens == null || tokens.length == 0) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_EMPTY.get()); + } + + int sum = Arrays.stream(tokens) + .mapToInt(Calculator::parseAndValidate) + .sum(); + + return new Result(sum); + } + + private static int parseAndValidate(String token) { + if (token == null || token.isBlank()) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_EMPTY.get()); + } + + try { + int number = Integer.parseInt(token); + if (number < 0) { + throw new IllegalArgumentException(ExceptionMessages.NEGATIVE_NUMBER.get()); + } + return number; + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_FORMAT.get()); + } + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/calculator/Result.java b/src/main/java/calculator/model/calculator/Result.java new file mode 100644 index 0000000000..3b54b8702e --- /dev/null +++ b/src/main/java/calculator/model/calculator/Result.java @@ -0,0 +1,18 @@ +package calculator.model.calculator; + +public class Result { + private final int value; + + public Result(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return "결과 : " + value; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/DelimiterParseResult.java b/src/main/java/calculator/model/parser/DelimiterParseResult.java new file mode 100644 index 0000000000..6c9a621eaa --- /dev/null +++ b/src/main/java/calculator/model/parser/DelimiterParseResult.java @@ -0,0 +1,21 @@ +package calculator.model.parser; + +import java.util.List; + +public class DelimiterParseResult { + private final List delimiters; + private final String body; + + public DelimiterParseResult(List delimiters, String body) { + this.delimiters = delimiters; + this.body = body; + } + + public List getDelimiters() { + return delimiters; + } + + public String getBody() { + return body; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/DelimiterParser.java b/src/main/java/calculator/model/parser/DelimiterParser.java new file mode 100644 index 0000000000..f6c01ddf16 --- /dev/null +++ b/src/main/java/calculator/model/parser/DelimiterParser.java @@ -0,0 +1,73 @@ +package calculator.model.parser; + +import calculator.util.Constants; +import calculator.util.ExceptionMessages; +import java.util.Arrays; +import java.util.List; + +public class DelimiterParser { + + private static final List DEFAULT_DELIMITERS = List.of( + Constants.DEFAULT_DELIMITER_COMMA.get(), + Constants.DEFAULT_DELIMITER_COLON.get() + ); + + private DelimiterParser() {} + + /** + * 입력 문자열에서 구분자 목록과 본문(body)을 함께 추출한다 + * + * @param input 전체 입력 문자열 + * @return 구분자 목록과 본문을 담은 DelimiterParseResult + */ + public static DelimiterParseResult parse(String input) { + // 개행 문자 이스케이프 처리 + input = input.replace( + Constants.CUSTOM_NEWLINE_ESCAPE.get(), + Constants.CUSTOM_NEWLINE_ACTUAL.get() + ); + + // 커스텀 구분자가 없는 경우 + if (!input.startsWith(Constants.CUSTOM_PREFIX.get())) { + return new DelimiterParseResult(DEFAULT_DELIMITERS, input); + } + + int newlineIndex = input.indexOf(Constants.CUSTOM_NEWLINE_ACTUAL.get()); + if (newlineIndex == -1) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_CUSTOM_FORMAT.get()); + } + + String customDelimiter = input.substring( + Constants.CUSTOM_PREFIX.get().length(), + newlineIndex + ); + + validateCustomDelimiter(customDelimiter); + + List delimiters = Arrays.asList( + customDelimiter, + Constants.DEFAULT_DELIMITER_COMMA.get(), + Constants.DEFAULT_DELIMITER_COLON.get() + ); + + String body = input.substring(newlineIndex + 1); + + return new DelimiterParseResult(delimiters, body); + } + + /** + * 커스텀 구분자 유효성 검사 + */ + private static void validateCustomDelimiter(String delimiter) { + if (delimiter.isBlank() || delimiter.contains(" ")) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_CUSTOM_WHITESPACE.get()); + } + + if (delimiter.contains(Constants.DEFAULT_DELIMITER_COMMA.get()) + || delimiter.contains(Constants.DEFAULT_DELIMITER_COLON.get()) + || delimiter.contains(Constants.CUSTOM_PREFIX.get()) + || delimiter.contains(Constants.CUSTOM_NEWLINE_ACTUAL.get())) { + throw new IllegalArgumentException(ExceptionMessages.INVALID_CUSTOM_RESERVED.get()); + } + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/DelimiterPattern.java b/src/main/java/calculator/model/parser/DelimiterPattern.java new file mode 100644 index 0000000000..e6396e4f27 --- /dev/null +++ b/src/main/java/calculator/model/parser/DelimiterPattern.java @@ -0,0 +1,20 @@ +package calculator.model.parser; + +import java.util.List; +import java.util.stream.Collectors; + +public class DelimiterPattern { + private static final String REGEX_SPECIALS = "([\\\\^$.|?*+()\\[\\]])"; + + // 모든 구분자를 OR (|)로 연결해서 하나의 정규식 패턴 생성 + public static String buildPattern(List delimiters) { + return delimiters.stream() + .map(DelimiterPattern::escapeSpecialChars) + .collect(Collectors.joining("|")); + } + + // 정규식 예약문자(\, *, [, ]) 등을 이스케이프 처리 + private static String escapeSpecialChars(String delimiter) { + return delimiter.replaceAll(REGEX_SPECIALS, "\\\\$1"); + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/NumberParser.java b/src/main/java/calculator/model/parser/NumberParser.java new file mode 100644 index 0000000000..e1e29c5484 --- /dev/null +++ b/src/main/java/calculator/model/parser/NumberParser.java @@ -0,0 +1,33 @@ +package calculator.model.parser; + +import calculator.util.ExceptionMessages; +import java.util.List; + +public class NumberParser { + + private NumberParser() {} + + public static String[] parse(String body, List delimiters) { + if (body.isEmpty()) { + return new String[]{"0"}; + } + + // 구분자 정규식 생성 + String regex = DelimiterPattern.buildPattern(delimiters); + + // 구분자로 분리 + String[] tokens = body.split(regex); + + // 검증: 비숫자, 음수 체크 + for (String token : tokens) { + if (!token.matches("^-?\\d+$")) { // 음수 기호 허용 + throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_FORMAT.get()); + } + if (Integer.parseInt(token) < 0) { + throw new IllegalArgumentException(ExceptionMessages.NEGATIVE_NUMBER.get()); + } + } + + return tokens; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/validation/Validator.java b/src/main/java/calculator/model/validation/Validator.java new file mode 100644 index 0000000000..0c916e7dc5 --- /dev/null +++ b/src/main/java/calculator/model/validation/Validator.java @@ -0,0 +1,27 @@ +package calculator.model.validation; + +import calculator.util.ExceptionMessages; + +public class Validator { + + private Validator() { + } + + /** + * 입력값이 존재하고, 기본 구조가 올바른지 확인한다 + * + * @param input 사용자 입력 문자열 + * @throws IllegalArgumentException 잘못된 형식일 경우 + */ + public static void validateInput(String input) { + if (input == null) { + throw new IllegalArgumentException(ExceptionMessages.INPUT_NULL.get() + ); + } + + // 빈 문자열은 계산 결과 0으로 처리되므로 정상 처리 + if (input.isEmpty()) { + return; + } + } +} \ No newline at end of file diff --git a/src/main/java/calculator/util/Constants.java b/src/main/java/calculator/util/Constants.java new file mode 100644 index 0000000000..3f8ac62477 --- /dev/null +++ b/src/main/java/calculator/util/Constants.java @@ -0,0 +1,34 @@ +package calculator.util; + +/** + * 문자열 계산기에서 사용되는 상수 모음 + */ +public enum Constants { + + // 기본 구분자 + DEFAULT_DELIMITER_COMMA(","), + DEFAULT_DELIMITER_COLON(":"), + + // 커스텀 구분자 + CUSTOM_PREFIX("//"), + CUSTOM_NEWLINE_ESCAPE("\\n"), // 입력에 들어오는 "\n" + CUSTOM_NEWLINE_ACTUAL("\n"), // 실제 개행 문자 + CUSTOM_SEPARATOR_PATTERN("//(.)\n(.*)"), + + // 입출력 메시지 + INPUT_PROMPT("덧셈할 문자열을 입력해 주세요."), + RESULT_PREFIX("결과 : "), + + // 기본값 + EMPTY_INPUT_RESULT("0"); + + private final String value; + + Constants(String value) { + this.value = value; + } + + public String get() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/util/ExceptionMessages.java b/src/main/java/calculator/util/ExceptionMessages.java new file mode 100644 index 0000000000..7748b53a25 --- /dev/null +++ b/src/main/java/calculator/util/ExceptionMessages.java @@ -0,0 +1,24 @@ +package calculator.util; + +public enum ExceptionMessages { + + INPUT_NULL("입력값이 존재하지 않습니다."), + INPUT_EMPTY("계산할 숫자가 없습니다."), + + INVALID_CUSTOM_FORMAT("커스텀 구분자 형식이 올바르지 않습니다."), + INVALID_CUSTOM_WHITESPACE("공백은 구분자로 사용할 수 없습니다."), + INVALID_CUSTOM_RESERVED("예약어는 구분자로 사용할 수 없습니다."), + + INVALID_NUMBER_FORMAT("숫자 형식이 올바르지 않습니다."), + NEGATIVE_NUMBER("음수는 입력할 수 없습니다."); + + private final String message; + + ExceptionMessages(String message) { + this.message = message; + } + + public String get() { + return message; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/view/InputView.java b/src/main/java/calculator/view/InputView.java new file mode 100644 index 0000000000..035e9e6a7f --- /dev/null +++ b/src/main/java/calculator/view/InputView.java @@ -0,0 +1,12 @@ +package calculator.view; + +import camp.nextstep.edu.missionutils.Console; +import calculator.util.Constants; + +public class InputView { + + public String read() { + System.out.println(Constants.INPUT_PROMPT.get()); + return Console.readLine(); + } +} \ No newline at end of file diff --git a/src/main/java/calculator/view/OutputView.java b/src/main/java/calculator/view/OutputView.java new file mode 100644 index 0000000000..d09a6d002f --- /dev/null +++ b/src/main/java/calculator/view/OutputView.java @@ -0,0 +1,11 @@ +package calculator.view; + +import calculator.model.calculator.Result; +import calculator.util.Constants; + +public class OutputView { + + public void printResult(Result result) { + System.out.println(Constants.RESULT_PREFIX.get() + result.getValue()); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/controller/CalculatorControllerTest.java b/src/test/java/calculator/controller/CalculatorControllerTest.java new file mode 100644 index 0000000000..80ea17810a --- /dev/null +++ b/src/test/java/calculator/controller/CalculatorControllerTest.java @@ -0,0 +1,37 @@ +package calculator.controller; + +import calculator.util.ExceptionMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorControllerTest { + + @Test + void 정상_입력_흐름_테스트() { + CalculatorController controller = new CalculatorController(); + // given + String input = "1,2:3"; + // when + String result = controller.testRun(input); + // then + assertEquals("결과 : 6", result); + } + + @Test + void 커스텀_구분자_입력_흐름_테스트() { + CalculatorController controller = new CalculatorController(); + String input = "//;\n1;2;3"; + String result = controller.testRun(input); + assertEquals("결과 : 6", result); + } + + @Test + void 비정상_입력_예외_테스트() { + CalculatorController controller = new CalculatorController(); + String input = "1,a,3"; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> controller.testRun(input)); + assertEquals(ExceptionMessages.INVALID_NUMBER_FORMAT.get(), e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/calculator/CalculatorTest.java b/src/test/java/calculator/model/calculator/CalculatorTest.java new file mode 100644 index 0000000000..9665588b6d --- /dev/null +++ b/src/test/java/calculator/model/calculator/CalculatorTest.java @@ -0,0 +1,39 @@ +package calculator.model.calculator; + +import calculator.util.ExceptionMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorTest { + + @Test + void 정상_입력_합산() { + String[] tokens = {"1", "2", "3"}; + Result result = Calculator.calculate(tokens); + assertEquals(6, result.getValue()); + } + + @Test + void 음수_입력_예외() { + String[] tokens = {"1", "-2", "3"}; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Calculator.calculate(tokens)); + assertEquals(ExceptionMessages.NEGATIVE_NUMBER.get(), e.getMessage()); + } + + @Test + void 비숫자_입력_예외() { + String[] tokens = {"1", "a", "3"}; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Calculator.calculate(tokens)); + assertEquals(ExceptionMessages.INVALID_NUMBER_FORMAT.get(), e.getMessage()); + } + + @Test + void 비어있는_토큰_배열_예외() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Calculator.calculate(new String[]{})); + assertEquals(ExceptionMessages.INPUT_EMPTY.get(), e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/DelimiterParserTest.java b/src/test/java/calculator/model/parser/DelimiterParserTest.java new file mode 100644 index 0000000000..aeb9dffb14 --- /dev/null +++ b/src/test/java/calculator/model/parser/DelimiterParserTest.java @@ -0,0 +1,45 @@ +package calculator.model.parser; + +import calculator.util.ExceptionMessages; +import org.junit.jupiter.api.Test; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class DelimiterParserTest { + + @Test + void 기본_구분자만_반환() { + DelimiterParseResult result = DelimiterParser.parse("1,2:3"); + assertEquals(List.of(",", ":"), result.getDelimiters()); + assertEquals("1,2:3", result.getBody()); + } + + @Test + void 커스텀_구분자_정상_파싱() { + DelimiterParseResult result = DelimiterParser.parse("//;\n1;2;3"); + assertTrue(result.getDelimiters().contains(";")); + assertEquals("1;2;3", result.getBody()); + } + + @Test + void 여러문자_커스텀_구분자_허용() { + DelimiterParseResult result = DelimiterParser.parse("//***\n1***2***3"); + assertTrue(result.getDelimiters().contains("***")); + assertEquals("1***2***3", result.getBody()); + } + + @Test + void 공백_포함_예외() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> DelimiterParser.parse("// ;\n1;2;3")); + assertEquals(ExceptionMessages.INVALID_CUSTOM_WHITESPACE.get(), e.getMessage()); + } + + @Test + void 예약어_포함_예외() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> DelimiterParser.parse("////\n1,2,3")); + assertEquals(ExceptionMessages.INVALID_CUSTOM_RESERVED.get(), e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/NumberParserTest.java b/src/test/java/calculator/model/parser/NumberParserTest.java new file mode 100644 index 0000000000..3d6f50841e --- /dev/null +++ b/src/test/java/calculator/model/parser/NumberParserTest.java @@ -0,0 +1,46 @@ +package calculator.model.parser; + +import calculator.util.ExceptionMessages; +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +class NumberParserTest { + + @Test + void 기본_구분자로_문자열_분리() { + List delimiters = List.of(",", ":"); + String[] result = NumberParser.parse("1,2:3", delimiters); + assertArrayEquals(new String[]{"1", "2", "3"}, result); + } + + @Test + void 커스텀_구분자로_문자열_분리() { + List delimiters = List.of("***"); + String[] result = NumberParser.parse("1***2***3", delimiters); + assertArrayEquals(new String[]{"1", "2", "3"}, result); + } + + @Test + void 음수_입력_예외() { + List delimiters = List.of(",", ":"); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> NumberParser.parse("1,-2,3", delimiters)); + assertEquals(ExceptionMessages.NEGATIVE_NUMBER.get(), e.getMessage()); + } + + @Test + void 비숫자_입력_예외() { + List delimiters = List.of(",", ":"); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> NumberParser.parse("1,a,3", delimiters)); + assertEquals(ExceptionMessages.INVALID_NUMBER_FORMAT.get(), e.getMessage()); + } + + @Test + void 빈_입력은_0으로_처리() { + List delimiters = List.of(",", ":"); + String[] result = NumberParser.parse("", delimiters); + assertArrayEquals(new String[]{"0"}, result); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/validation/ValidatorTest.java b/src/test/java/calculator/model/validation/ValidatorTest.java new file mode 100644 index 0000000000..e28918b22f --- /dev/null +++ b/src/test/java/calculator/model/validation/ValidatorTest.java @@ -0,0 +1,30 @@ +package calculator.model.validation; + +import calculator.util.ExceptionMessages; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ValidatorTest { + + @Test + void 정상_입력_검증() { + assertDoesNotThrow(() -> Validator.validateInput("1,2:3")); + } + + @Test + void null_입력_예외() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Validator.validateInput(null)); + assertEquals(ExceptionMessages.INPUT_NULL.get(), e.getMessage()); + } + + @Test + void 빈_문자열_허용() { + assertDoesNotThrow(() -> Validator.validateInput("")); + } + + @Test + void 커스텀_구분자_형식_정상() { + assertDoesNotThrow(() -> Validator.validateInput("//;\n1;2;3")); + } +} \ No newline at end of file