diff --git a/.gitignore b/.gitignore index 76611b07..8e0b9acb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ !gradle/wrapper/gradle-wrapper.jar /out/ /target/ +/src/main/java/nickname.txt +/src/main/java/study +/src/test/java/study/CabenetTest.java + ### STS ### .apt_generated diff --git a/README.md b/README.md index 7d742d97..946f95cf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,36 @@ # java-calculator 문자열 계산기 미션 저장소 -## 우아한테크코스 코드리뷰 -* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) \ No newline at end of file +## 기능적 요구사항 +- 문자열을 입력 받아 공백 단위로 split 하는 함수를 만든다 + - 입력이 정상적인지 확인 + - 입력이 없는지 + - 숫자와 연산자 외의 다른 문자가 있는지 + - split 된 결과가 정상적인지 확인 + - 숫자와 연산자의 순서가 맞는지 + +- 사칙연산을 하는 함수를 만든다 + - 나눗셈의 경우 0으로 나누는 경우에 대한 예외 처리 + - 입력 값이 자료형의 범위를 넘어 가는 경우에 대한 예외 처리 + +- split 된 결과 값이 순서대로 연산이 되는 함수를 만든다 + +- 연산 결과를 출력하는 함수를 만든다 + +- 단위별로 테스트 하는 함수를 만든다 + - split 테스트를 진행한다 + - 입력으로 null, 공백, 유효하지 않은 double 범위의 수, 숫자와 연산자 외의 문자열 + - split 할 수 없는 문자열, 숫자와 연산자 순서가 바뀐 문자열, 정상 계산식 + - 사칙연산 테스트 + - 사칙연산의 결과 값이 실제 값과 맞는지 테스트 + - 0으로 나누었을 때, double 범위를 넘어가는 결과 값 + + +## 비기능적 요구사항 +- indent 2까지 허용한다. +- 메소드는 15줄 까지 허용한다. +- switch문 사용하지 않는다. +- 하드코딩하지 않는다. +- 변수와 메소드 명명은 컨벤션에 따른다. +- 자바 코드 컨벤션을 지키면서 프로그래밍 한다. +- 함수의 인자 수를 3개까지만 허용한다. diff --git a/src/main/java/calculator/Calculator.java b/src/main/java/calculator/Calculator.java new file mode 100644 index 00000000..bc7cf61a --- /dev/null +++ b/src/main/java/calculator/Calculator.java @@ -0,0 +1,13 @@ +package calculator; + +public class Calculator { + public double calculate(String[] splitedInput) { + double left = Double.valueOf(splitedInput[0]); + for (int i = 1; i < splitedInput.length; i = i + 2) { + String operator = splitedInput[i]; + double right = Double.valueOf(splitedInput[i + 1]); + left = Operator.find(operator).calculate(left, right); + } + return left; + } +} diff --git a/src/main/java/calculator/Constant.java b/src/main/java/calculator/Constant.java new file mode 100644 index 00000000..f42e493c --- /dev/null +++ b/src/main/java/calculator/Constant.java @@ -0,0 +1,8 @@ +package calculator; + +public class Constant { + public final static int ZERO = 0; + + public final static String BLANK = " "; + public final static String EMPTY_STRING = ""; +} diff --git a/src/main/java/calculator/Main.java b/src/main/java/calculator/Main.java new file mode 100644 index 00000000..6885e51f --- /dev/null +++ b/src/main/java/calculator/Main.java @@ -0,0 +1,17 @@ +package calculator; + +public class Main { + public static void main(String[] args) { + UserInputScanner userInputScanner = new UserInputScanner(); + Calculator calculator = new Calculator(); + Output output = new Output(); + + try { + String[] splitString = userInputScanner.splitUserInputString(); + double result = calculator.calculate(splitString); + output.printResult(result); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/src/main/java/calculator/Operator.java b/src/main/java/calculator/Operator.java new file mode 100644 index 00000000..e9eb8099 --- /dev/null +++ b/src/main/java/calculator/Operator.java @@ -0,0 +1,47 @@ +package calculator; + +import java.util.Arrays; +import java.util.function.BiFunction; + +public enum Operator { + PLUS("+", + (left, right) -> (left + right)), + MINUS("-", + (left, right) -> (left - right)), + MULTIPLICATION("*", + (left, right) -> (left * right)), + DIVISION("/", + (left, right) -> (left / right)); + + private String symbol; + private BiFunction expression; + + Operator(String symbol, BiFunction expression) { + this.symbol = symbol; + this.expression = expression; + } + + private String getSymbol() { + return this.symbol; + } + + public static Operator find(String operator) { + return Arrays.stream(Operator.values()) + .filter(op -> op.getSymbol().equals(operator)) + .findFirst() + .get(); + } + + public double calculate(double left, double right) { + return expression.apply(left, right); + } + + public static boolean isRightOperator(String operator) { + return Arrays.stream(Operator.values()) + .anyMatch(op -> op.getSymbol().equals(operator)); + } + + public static boolean isDivisionOperator(String operator) { + return "/".equals(operator); + } +} diff --git a/src/main/java/calculator/Output.java b/src/main/java/calculator/Output.java new file mode 100644 index 00000000..578721a9 --- /dev/null +++ b/src/main/java/calculator/Output.java @@ -0,0 +1,7 @@ +package calculator; + +public class Output { + public void printResult(double result) { + System.out.println("결과 값은 : " + result); + } +} diff --git a/src/main/java/calculator/UserInputScanner.java b/src/main/java/calculator/UserInputScanner.java new file mode 100644 index 00000000..b55ec451 --- /dev/null +++ b/src/main/java/calculator/UserInputScanner.java @@ -0,0 +1,22 @@ +package calculator; + +import java.util.Scanner; + +public class UserInputScanner { + private ValidityInspector validityInspector = new ValidityInspector(); + private Scanner scanner = new Scanner(System.in); + + private String inputStringToUser() { + System.out.print("계산식을 입력하시오: "); + String inputLine = scanner.nextLine(); + validityInspector.checkUserInputIsBlankOrEmpty(inputLine); + return inputLine; + } + + public String[] splitUserInputString() { + String input = inputStringToUser(); + String[] splitInput = input.split(Constant.BLANK); + validityInspector.checkCanConvertUserInputToNumberAndOperator(splitInput); + return splitInput; + } +} diff --git a/src/main/java/calculator/ValidityInspector.java b/src/main/java/calculator/ValidityInspector.java new file mode 100644 index 00000000..f2442ccc --- /dev/null +++ b/src/main/java/calculator/ValidityInspector.java @@ -0,0 +1,60 @@ +package calculator; + +public class ValidityInspector { + public void checkUserInputIsBlankOrEmpty(String input) { + if (isBlank(input) || isEmpty(input)) { + throw new IllegalArgumentException("공백 또는 빈 문자열을 입력하셨습니다."); + } + } + + private boolean isEmpty(String input) { + return input.equals(Constant.EMPTY_STRING); + } + + private boolean isBlank(String input) { + return input.equals(Constant.BLANK); + } + + public void checkCanConvertUserInputToNumberAndOperator(String[] splitedInput) { + checkSplitedInputEmpty(splitedInput); + String firstClause = splitedInput[0]; + checkCorrectDoubleNumber(firstClause); + try { + for (int i = 1; i < splitedInput.length; i = i + 2) { + String operator = splitedInput[i]; + String secondClause = splitedInput[i + 1]; + checkDivideByZero(operator, secondClause); + checkCorrectOperator(operator); + checkCorrectDoubleNumber(secondClause); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new ArrayIndexOutOfBoundsException("잘못된 계산식을 입력하였습니다."); + } + } + + private void checkCorrectDoubleNumber(String s) { + try { + Double.valueOf(s); + } catch (Exception e) { + throw new IllegalArgumentException("잘못된 수를 입력하였습니다."); + } + } + + private void checkCorrectOperator(String s) { + if (!Operator.isRightOperator(s)) { + throw new IllegalArgumentException("잘못된 연산자를 입력하였습니다."); + } + } + + private void checkSplitedInputEmpty(String[] splitedInput) { + if (splitedInput.length == 0) { + throw new IllegalArgumentException("공백을 입력하였습니다."); + } + } + + private void checkDivideByZero(String operator, String secondClause) { + if (Operator.isDivisionOperator(operator) && Integer.toString(Constant.ZERO).equals(secondClause)) { + throw new IllegalArgumentException("0으로 나누는 식을 입력하셨습니다."); + } + } +} diff --git a/src/test/java/calculator/CalculatorTest.java b/src/test/java/calculator/CalculatorTest.java new file mode 100644 index 00000000..c7599cb9 --- /dev/null +++ b/src/test/java/calculator/CalculatorTest.java @@ -0,0 +1,64 @@ +package calculator; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class CalculatorTest { + private Calculator calculator; + private ValidityInspector validityInspector; + + @BeforeEach + public void setUp() { + calculator = new Calculator(); + validityInspector = new ValidityInspector(); + } + + @ParameterizedTest + @ValueSource(strings = {" ", ""}) + public void checkUserInputIsBlankOrEmptyTest(String input) { + Assertions.assertThatThrownBy(() -> { + validityInspector.checkUserInputIsBlankOrEmpty(input); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("공백 또는 빈 문자열을 입력하셨습니다."); + } + + @ParameterizedTest + @ValueSource(strings = {" ", "q + w + e", "1+2+3", "+ + 2", "1 abcd 2", "2 * 65 / 0", "2 + 3/", "/1 + 55", "1 + a3"}) + public void checkCanConvertUserInputToNumberAndOperatorTest(String input) { + String[] splitData = input.split(Constant.BLANK); + Assertions.assertThatThrownBy(() -> { + validityInspector.checkCanConvertUserInputToNumberAndOperator(splitData); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void addTest() { + String[] numericalExpression = {"1", "+", "2"}; + Double result = calculator.calculate(numericalExpression); + Assertions.assertThat(result).isEqualTo(3); + } + + @Test + public void subtractTest() { + String[] numericalExpression = {"1", "-", "2"}; + Double result = calculator.calculate(numericalExpression); + Assertions.assertThat(result).isEqualTo(-1); + } + + @Test + public void multipleTest() { + String[] numericalExpression = {"1", "*", "2"}; + Double result = calculator.calculate(numericalExpression); + Assertions.assertThat(result).isEqualTo(2); + } + + @Test + public void divideTest() { + String[] numericalExpression = {"1", "/", "2"}; + Double result = calculator.calculate(numericalExpression); + Assertions.assertThat(result).isEqualTo(0.5d); + } +} diff --git a/src/test/java/empty.txt b/src/test/java/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/src/test/java/study/SetTest.java b/src/test/java/study/SetTest.java new file mode 100644 index 00000000..5414d654 --- /dev/null +++ b/src/test/java/study/SetTest.java @@ -0,0 +1,44 @@ +package study; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SetTest { + private Set numbers; + + @BeforeEach + void setUp() { + numbers = new HashSet<>(); + numbers.add(1); + numbers.add(1); + numbers.add(2); + numbers.add(3); + } + + @Test + void sizeTest() { + int size = numbers.size(); + Assertions.assertThat(size).isEqualTo(4); + } + + @ParameterizedTest + @ValueSource(ints = {1, 1, 2, 3}) + void containTest(int number) { + assertTrue(numbers.contains(number)); + } + + @ParameterizedTest + @CsvSource(value = {"ravie-RAVIE", "Lavine-LAVINE", "orange-ORANKE", "dog-DoG"}, delimiter = '-') + void equalUpperCase(String input, String upperCase){ + Assertions.assertThat(upperCase).isEqualTo(input.toUpperCase()); + } +} diff --git a/src/test/java/study/StringTest.java b/src/test/java/study/StringTest.java new file mode 100644 index 00000000..1bc3624f --- /dev/null +++ b/src/test/java/study/StringTest.java @@ -0,0 +1,33 @@ +package study; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.assertj.core.api.Assertions; + +public class StringTest { + @Test + void split() { + String value = "1,2"; + String[] result = value.split(","); + Assertions.assertThat(result).contains("1"); + Assertions.assertThat(result).contains("2"); + String[] temp = {"1", "2"}; + Assertions.assertThat(result).containsExactly(temp); + } + + @Test + void substring() { + String value = "(1,2)"; + String result = value.substring(1, value.length() - 1); + Assertions.assertThat(result).contains("1,2"); + } + + @Test + @DisplayName("charAt method out of bounds test") + void charAtTest() { + String value = "abc"; + Assertions.assertThatThrownBy(() -> { + value.charAt(10); + }).isInstanceOf(StringIndexOutOfBoundsException.class).hasMessageContaining("String index out of range"); + } +}