diff --git a/calculator/src/main/java/com/wonu606/Main.java b/calculator/src/main/java/com/wonu606/Main.java index 945b902ef..14171d396 100644 --- a/calculator/src/main/java/com/wonu606/Main.java +++ b/calculator/src/main/java/com/wonu606/Main.java @@ -2,11 +2,10 @@ import com.wonu606.app.App; import com.wonu606.calculator.CalculatorApp; +import com.wonu606.io.ConsoleInput; import com.wonu606.io.ConsolePrinter; import com.wonu606.io.Input; import com.wonu606.io.Print; -import com.wonu606.io.ConsoleInput; -import java.io.IOException; public class Main { @@ -25,7 +24,7 @@ public static void main(String[] args) { } } - private static void runApp(App app, Input input, Print printer) throws IOException { + private static void runApp(App app, Input input, Print printer) { app.execute(input, printer); } } diff --git a/calculator/src/main/java/com/wonu606/app/App.java b/calculator/src/main/java/com/wonu606/app/App.java index 86c560082..9e0cbb124 100644 --- a/calculator/src/main/java/com/wonu606/app/App.java +++ b/calculator/src/main/java/com/wonu606/app/App.java @@ -2,9 +2,8 @@ import com.wonu606.io.Input; import com.wonu606.io.Print; -import java.io.IOException; public interface App { - void execute(Input input, Print printer) throws IOException; + void execute(Input input, Print printer); } diff --git a/calculator/src/main/java/com/wonu606/calculator/CalculatorApp.java b/calculator/src/main/java/com/wonu606/calculator/CalculatorApp.java index 7a54931cc..cb48abd3f 100644 --- a/calculator/src/main/java/com/wonu606/calculator/CalculatorApp.java +++ b/calculator/src/main/java/com/wonu606/calculator/CalculatorApp.java @@ -1,21 +1,55 @@ package com.wonu606.calculator; import com.wonu606.app.App; +import com.wonu606.calculator.calculator.PostfixCalculator; +import com.wonu606.calculator.converter.InfixToPostfixConverter; +import com.wonu606.calculator.storage.Persistence; +import com.wonu606.calculator.storage.ResultStore; +import com.wonu606.calculator.strategy.CalculationStrategy; +import com.wonu606.calculator.strategy.CalculatorStrategy; +import com.wonu606.calculator.util.CalculatorMessage; +import com.wonu606.calculator.validator.InfixValidator; import com.wonu606.io.Input; import com.wonu606.io.Print; -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; public class CalculatorApp implements App { - Input input; - Print printer; + private final Map strategies = new HashMap(); + private final Persistence store = new ResultStore(); - public void execute(Input input, Print printer) throws IOException { - this.input = input; - this.printer = printer; + public CalculatorApp() { + initStrategies(); + } + + private void initStrategies() { + strategies.put("1", new CalculationStrategy( + new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator())); + } + + public void execute(Input input, Print printer) { while (true) { - String selection = input.getInput(); + String selection = inputMenuSelection(input); + CalculatorStrategy selectedStrategy = getStrategyOrThrow(selection); + performStrategy(input, printer, selectedStrategy); } } + + private void performStrategy(Input input, Print printer, CalculatorStrategy selectedStrategy) { + selectedStrategy.execute(input, printer, store); + } + + private CalculatorStrategy getStrategyOrThrow(String selection) { + CalculatorStrategy selectedStrategy = strategies.get(selection); + if (selectedStrategy == null) { + throw new IllegalArgumentException(CalculatorMessage.INVALID_INPUT.message); + } + return selectedStrategy; + } + + private String inputMenuSelection(Input input) { + return input.getInput(); + } } diff --git a/calculator/src/main/java/com/wonu606/calculator/calculator/Calculator.java b/calculator/src/main/java/com/wonu606/calculator/calculator/Calculator.java new file mode 100644 index 000000000..7d1e9fa44 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/calculator/Calculator.java @@ -0,0 +1,8 @@ +package com.wonu606.calculator.calculator; + +import java.util.List; + +public interface Calculator { + + double calculate(List expression); +} diff --git a/calculator/src/main/java/com/wonu606/calculator/calculator/PostfixCalculator.java b/calculator/src/main/java/com/wonu606/calculator/calculator/PostfixCalculator.java new file mode 100644 index 000000000..f7798ec57 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/calculator/PostfixCalculator.java @@ -0,0 +1,42 @@ +package com.wonu606.calculator.calculator; + +import com.wonu606.calculator.model.Operator; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Objects; + +public class PostfixCalculator implements Calculator { + + @Override + public double calculate(List postfixExpression) { + Deque operandStack = new ArrayDeque<>(); + + for (String token : postfixExpression) { + handleToken(operandStack, token); + } + + return operandStack.pop(); + } + + private void handleToken(Deque operandStack, String token) { + if (isOperator(token)) { + Operator operator = Objects.requireNonNull(Operator.get(token)); + performOperation(operandStack, operator); + return; + } + operandStack.push(Double.valueOf(token)); + } + + private void performOperation(Deque operandStack, Operator operator) { + double secondOperand = operandStack.pop(); + double firstOperand = operandStack.pop(); + + double result = operator.apply(firstOperand, secondOperand); + operandStack.push(result); + } + + private boolean isOperator(String token) { + return Operator.get(token) != null; + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/converter/Converter.java b/calculator/src/main/java/com/wonu606/calculator/converter/Converter.java new file mode 100644 index 000000000..3cf3afcfc --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/converter/Converter.java @@ -0,0 +1,6 @@ +package com.wonu606.calculator.converter; + +public interface Converter { + + R convert(T input); +} diff --git a/calculator/src/main/java/com/wonu606/calculator/converter/InfixToPostfixConverter.java b/calculator/src/main/java/com/wonu606/calculator/converter/InfixToPostfixConverter.java new file mode 100644 index 000000000..03b8829b9 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/converter/InfixToPostfixConverter.java @@ -0,0 +1,56 @@ +package com.wonu606.calculator.converter; + +import com.wonu606.calculator.model.Operator; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Objects; + +public class InfixToPostfixConverter implements Converter> { + + @Override + public List convert(String infixExpression) { + Deque operatorStack = new ArrayDeque<>(); + List postfixExpression = new ArrayList<>(); + + String[] tokens = infixExpression.split("\\s"); + + for (String token : tokens) { + processTokenIntoPostfix(operatorStack, postfixExpression, token); + } + + while (!operatorStack.isEmpty()) { + postfixExpression.add(operatorStack.pop()); + } + return postfixExpression; + } + + private void processTokenIntoPostfix( + Deque operatorStack, List postfixExpression, String token) { + if (isOperator(token)) { + popWhileHigherPrecedence(operatorStack, postfixExpression, token); + operatorStack.push(token); + return; + } + postfixExpression.add(token); + } + + private void popWhileHigherPrecedence( + Deque operatorStack, List postfixExpression, String tokenOperator) { + while (!operatorStack.isEmpty() + && isHigherOrEqualPrecedence(operatorStack.peek(), tokenOperator)) { + postfixExpression.add(operatorStack.pop()); + } + } + + private boolean isHigherOrEqualPrecedence(String peekOperator, String tokenOperator) { + int peekPrecedence = Objects.requireNonNull(Operator.get(peekOperator)).precedence; + int tokenPrecedence = Objects.requireNonNull(Operator.get(tokenOperator)).precedence; + return peekPrecedence <= tokenPrecedence; + } + + private boolean isOperator(String token) { + return Operator.get(token) != null; + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/model/CalculationResult.java b/calculator/src/main/java/com/wonu606/calculator/model/CalculationResult.java new file mode 100644 index 000000000..d398c155d --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/model/CalculationResult.java @@ -0,0 +1,40 @@ +package com.wonu606.calculator.model; + +import java.util.Objects; + +public class CalculationResult { + + private final String expression; + private final double result; + + public CalculationResult(String expression, double result) { + this.expression = expression; + this.result = result; + } + + public String getExpression() { + return expression; + } + + public double getResult() { + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CalculationResult that = (CalculationResult) o; + return Double.compare(that.getResult(), getResult()) == 0 && Objects.equals( + getExpression(), that.getExpression()); + } + + @Override + public int hashCode() { + return Objects.hash(getExpression(), getResult()); + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/model/Operator.java b/calculator/src/main/java/com/wonu606/calculator/model/Operator.java new file mode 100644 index 000000000..c86f76a70 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/model/Operator.java @@ -0,0 +1,52 @@ +package com.wonu606.calculator.model; + +import com.wonu606.calculator.util.CalculatorMessage; + +public enum Operator { + ADD("+", 2) { + @Override + public double apply(double a, double b) { + return a + b; + } + }, + SUBTRACT("-", 2) { + @Override + public double apply(double a, double b) { + return a - b; + } + }, + MULTIPLY("*", 1) { + @Override + public double apply(double a, double b) { + return a * b; + } + }, + DIVIDE("/", 1) { + @Override + public double apply(double a, double b) { + if (b == 0) { + throw new ArithmeticException(CalculatorMessage.NOT_DIVISIBLE_BY_ZERO.message); + } + return a / b; + } + }; + + public final String symbol; + public final int precedence; + + Operator(String symbol, int precedence) { + this.symbol = symbol; + this.precedence = precedence; + } + + public static Operator get(String symbol) { + for (Operator operator : values()) { + if (operator.symbol.equals(symbol)) { + return operator; + } + } + return null; + } + + public abstract double apply(double a, double b); +} diff --git a/calculator/src/main/java/com/wonu606/calculator/storage/Persistence.java b/calculator/src/main/java/com/wonu606/calculator/storage/Persistence.java new file mode 100644 index 000000000..cfa0a76ba --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/storage/Persistence.java @@ -0,0 +1,15 @@ +package com.wonu606.calculator.storage; + +import com.wonu606.calculator.model.CalculationResult; +import java.util.List; + +public interface Persistence { + + void saveResult(CalculationResult calculationResult); + + CalculationResult findResult(int sequence); + + List findAllResult(); + + void clear(); +} diff --git a/calculator/src/main/java/com/wonu606/calculator/storage/ResultStore.java b/calculator/src/main/java/com/wonu606/calculator/storage/ResultStore.java new file mode 100644 index 000000000..5387b0a71 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/storage/ResultStore.java @@ -0,0 +1,35 @@ +package com.wonu606.calculator.storage; + +import com.wonu606.calculator.model.CalculationResult; +import com.wonu606.calculator.util.CalculatorMessage; +import java.util.ArrayList; +import java.util.List; + +public class ResultStore implements Persistence { + + private final List store = new ArrayList<>(); + + @Override + public void saveResult(CalculationResult calculationResult) { + store.add(calculationResult); + } + + @Override + public CalculationResult findResult(int sequence) { + try { + return store.get(sequence - 1); + } catch (IndexOutOfBoundsException exception) { + throw new IllegalArgumentException(CalculatorMessage.INVALID_ORDER.message); + } + } + + @Override + public List findAllResult() { + return new ArrayList<>(store); + } + + @Override + public void clear() { + store.clear(); + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/strategy/CalculationStrategy.java b/calculator/src/main/java/com/wonu606/calculator/strategy/CalculationStrategy.java new file mode 100644 index 000000000..d5d7cac52 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/strategy/CalculationStrategy.java @@ -0,0 +1,68 @@ +package com.wonu606.calculator.strategy; + +import com.wonu606.calculator.calculator.Calculator; +import com.wonu606.calculator.converter.Converter; +import com.wonu606.calculator.model.CalculationResult; +import com.wonu606.calculator.storage.Persistence; +import com.wonu606.calculator.util.CalculatorMessage; +import com.wonu606.calculator.validator.Validator; +import com.wonu606.io.Input; +import com.wonu606.io.Print; +import java.util.List; + +public class CalculationStrategy implements CalculatorStrategy { + + Validator validator; + Converter> converter; + Calculator calculator; + + public CalculationStrategy(Validator validator, Converter> converter, + Calculator calculator) { + this.validator = validator; + this.converter = converter; + this.calculator = calculator; + } + + @Override + public void execute(Input input, Print printer, Persistence store) { + String inputExpression = input.getInput(); + if (isNotValidExpression(inputExpression)) { + printer.print(CalculatorMessage.INVALID_INPUT.message); + return; + } + + List convertedExpression = converter.convert(inputExpression); + try { + double result = calculator.calculate(convertedExpression); + + store.saveResult(new CalculationResult(inputExpression, result)); + printCalculationResult(printer, result); + } catch (ArithmeticException exception) { + printer.print(exception.getMessage()); + } + } + + private static void printCalculationResult(Print printer, double result) { + String message = String.valueOf(roundResultToInt(result)); + if (isResultOverflow(result)) { + message = CalculatorMessage.OVERFLOW_OCCURS.message; + } + printer.print(message); + } + + private static int roundResultToInt(double result) { + return (int) Math.round(result); + } + + private static boolean isResultOverflow(double result) { + return Double.isInfinite(result) || (isIntegerOverflow(result)); + } + + private static boolean isIntegerOverflow(double result) { + return result > Integer.MAX_VALUE; + } + + private boolean isNotValidExpression(String inputExpression) { + return !validator.isValid(inputExpression); + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/strategy/CalculatorStrategy.java b/calculator/src/main/java/com/wonu606/calculator/strategy/CalculatorStrategy.java new file mode 100644 index 000000000..54d0907e2 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/strategy/CalculatorStrategy.java @@ -0,0 +1,10 @@ +package com.wonu606.calculator.strategy; + +import com.wonu606.calculator.storage.Persistence; +import com.wonu606.io.Input; +import com.wonu606.io.Print; + +public interface CalculatorStrategy { + + void execute(Input input, Print printer, Persistence store); +} diff --git a/calculator/src/main/java/com/wonu606/calculator/util/CalculatorMessage.java b/calculator/src/main/java/com/wonu606/calculator/util/CalculatorMessage.java new file mode 100644 index 000000000..8333dfeff --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/util/CalculatorMessage.java @@ -0,0 +1,19 @@ +package com.wonu606.calculator.util; + +public enum CalculatorMessage { + INVALID_ORDER("잘못된 순번입니다."), + INVALID_INPUT("잘못된 입력입니다."), + NOT_DIVISIBLE_BY_ZERO("0으로 나눌 수 없습니다."), + OVERFLOW_OCCURS("계산 중 Overflow가 발생했습니다."); + + public final String message; + + CalculatorMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return message; + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/validator/InfixValidator.java b/calculator/src/main/java/com/wonu606/calculator/validator/InfixValidator.java new file mode 100644 index 000000000..611d7e2b2 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/validator/InfixValidator.java @@ -0,0 +1,53 @@ +package com.wonu606.calculator.validator; + +import com.wonu606.calculator.model.Operator; +import java.util.ArrayList; +import java.util.List; + +public class InfixValidator implements Validator { + + private static boolean isExpectedNumericPosition(int position) { + return position % 2 == 0; + } + + @Override + public boolean isValid(String expression) { + String[] tokens = expression.split("\\s"); + List operators = getOperators(); + + for (int i = 0; i < tokens.length; i++) { + if (isInvalidToken(i, tokens[i], operators)) { + return false; + } + } + return true; + } + + private boolean isInvalidToken(int position, String str, List operators) { + if (isExpectedNumericPosition(position)) { + return !isNumeric(str); + } + return !isOperator(operators, str); + } + + private List getOperators() { + List operators = new ArrayList<>(); + for (Operator operator : Operator.values()) { + operators.add(operator.symbol); + } + return operators; + } + + private Boolean isNumeric(String str) { + try { + Double.parseDouble(str); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + private boolean isOperator(List operators, String expectedOperator) { + return operators.contains(expectedOperator); + } +} diff --git a/calculator/src/main/java/com/wonu606/calculator/validator/Validator.java b/calculator/src/main/java/com/wonu606/calculator/validator/Validator.java new file mode 100644 index 000000000..e98119545 --- /dev/null +++ b/calculator/src/main/java/com/wonu606/calculator/validator/Validator.java @@ -0,0 +1,6 @@ +package com.wonu606.calculator.validator; + +public interface Validator { + + boolean isValid(String expression); +} diff --git a/calculator/src/main/java/com/wonu606/io/ConsoleInput.java b/calculator/src/main/java/com/wonu606/io/ConsoleInput.java index 38a712bad..31af5a279 100644 --- a/calculator/src/main/java/com/wonu606/io/ConsoleInput.java +++ b/calculator/src/main/java/com/wonu606/io/ConsoleInput.java @@ -1,6 +1,5 @@ package com.wonu606.io; -import java.io.IOException; import java.util.Scanner; public class ConsoleInput implements Input { @@ -8,7 +7,7 @@ public class ConsoleInput implements Input { Scanner scanner = new Scanner(System.in); @Override - public String getInput() throws IOException { + public String getInput() { return scanner.nextLine(); } diff --git a/calculator/src/main/java/com/wonu606/io/Input.java b/calculator/src/main/java/com/wonu606/io/Input.java index f6942d541..88dc18ad8 100644 --- a/calculator/src/main/java/com/wonu606/io/Input.java +++ b/calculator/src/main/java/com/wonu606/io/Input.java @@ -1,10 +1,8 @@ package com.wonu606.io; -import java.io.IOException; - public interface Input { - String getInput() throws IOException; + String getInput(); void tearDown(); } diff --git a/calculator/src/test/java/com/wonu606/calculator/calculator/PostCalculatorTest.java b/calculator/src/test/java/com/wonu606/calculator/calculator/PostCalculatorTest.java new file mode 100644 index 000000000..ef472b427 --- /dev/null +++ b/calculator/src/test/java/com/wonu606/calculator/calculator/PostCalculatorTest.java @@ -0,0 +1,86 @@ +package com.wonu606.calculator.calculator; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PostCalculatorTest { + + @Test + @DisplayName("연산자가 1개일 경우") + void testSingle() { + // given + Calculator calculator = new PostfixCalculator(); + List actualList = Arrays.asList("-3", "5.0", "+"); + double expected = -3 + 5.0; + + // when + double result = calculator.calculate(actualList); + + // then + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("같은 우선 순위일 경우") + void testPrecedenceEquals() { + // given + Calculator calculator = new PostfixCalculator(); + List actualList = Arrays.asList("3", "5", "+", "2", "-"); + double expected = 3 + 5 - 2; + + // when + double result = calculator.calculate(actualList); + + // then + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("높은 우선 순위가 뒤에 올 경우") + void testHigherPrecedenceFollows() { + // given + Calculator calculator = new PostfixCalculator(); + List actualList = Arrays.asList("3", "5", "2", "*", "+"); + double expected = 3 + 5 * 2; + + // when + double result = calculator.calculate(actualList); + + // then + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("높은 우선 순위가 앞에 올 경우") + void testHigherPrecedenceComesFirst() { + // given + Calculator calculator = new PostfixCalculator(); + List actualList = Arrays.asList("3", "5", "*", "2", "+"); + double expected = 3 * 5 + 2; + + // when + double result = calculator.calculate(actualList); + + // then + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("소수가 나올 경우") + void testIfFraction() { + // given + Calculator calculator = new PostfixCalculator(); + List actualList = Arrays.asList("3", "5", "/", "2", "+"); + double expected = 3.0 / 5 + 2; + + // when + double result = calculator.calculate(actualList); + + // then + assertThat(result).isEqualTo(expected); + } +} \ No newline at end of file diff --git a/calculator/src/test/java/com/wonu606/calculator/converter/InfixToPostfixConverterTest.java b/calculator/src/test/java/com/wonu606/calculator/converter/InfixToPostfixConverterTest.java new file mode 100644 index 000000000..0ed927670 --- /dev/null +++ b/calculator/src/test/java/com/wonu606/calculator/converter/InfixToPostfixConverterTest.java @@ -0,0 +1,71 @@ +package com.wonu606.calculator.converter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class InfixToPostfixConverterTest { + + @Test + @DisplayName("연산자가 1개일 경우") + void testSingle() { + // given + Converter> converter = new InfixToPostfixConverter(); + List expectedList = Arrays.asList("-3", "5.0", "+"); + String infixExpression = "-3 + 5.0"; + + // when + List actualList = converter.convert(infixExpression); + + // then + assertThat(actualList).isEqualTo(expectedList); + } + + @Test + @DisplayName("같은 우선 순위일 경우") + void testPrecedenceEquals() { + // given + Converter> converter = new InfixToPostfixConverter(); + List expectedList = Arrays.asList("3", "5", "+", "2", "-"); + String infixExpression = "3 + 5 - 2"; + + // when + List actualList = converter.convert(infixExpression); + + // then + assertThat(actualList).isEqualTo(expectedList); + } + + @Test + @DisplayName("높은 우선 순위가 뒤에 올 경우") + void testHigherPrecedenceFollows() { + // given + Converter> converter = new InfixToPostfixConverter(); + List expectedList = Arrays.asList("3", "5", "2", "*", "+"); + String infixExpression = "3 + 5 * 2"; + + // when + List actualList = converter.convert(infixExpression); + + // then + assertThat(actualList).isEqualTo(expectedList); + } + + @Test + @DisplayName("높은 우선 순위가 앞에 올 경우") + void testHigherPrecedenceComesFirst() { + // given + Converter> converter = new InfixToPostfixConverter(); + List expectedList = Arrays.asList("3", "5", "*", "2", "+"); + String infixExpression = "3 * 5 + 2"; + + // when + List actualList = converter.convert(infixExpression); + + // then + assertThat(actualList).isEqualTo(expectedList); + } +} \ No newline at end of file diff --git a/calculator/src/test/java/com/wonu606/calculator/storage/ResultStoreTest.java b/calculator/src/test/java/com/wonu606/calculator/storage/ResultStoreTest.java new file mode 100644 index 000000000..1ffb44188 --- /dev/null +++ b/calculator/src/test/java/com/wonu606/calculator/storage/ResultStoreTest.java @@ -0,0 +1,113 @@ +package com.wonu606.calculator.storage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.wonu606.calculator.model.CalculationResult; +import com.wonu606.calculator.util.CalculatorMessage; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ResultStoreTest { + + @Test + @DisplayName("저장") + void testSave() { + // given + Persistence store = new ResultStore(); + + // when + CalculationResult calculationResult = new CalculationResult("2 + 2", 4.0d); + store.saveResult(calculationResult); + + // then + assertThat(store.findAllResult().size()).isEqualTo(1); + } + + @Test + @DisplayName("순서로 결과 찾기") + void testFind() { + // given + Persistence store = new ResultStore(); + + // when + CalculationResult saveResult = new CalculationResult("5 + 10", 15.0d); + store.saveResult(saveResult); + + // then + CalculationResult foundResult = store.findResult(1); + assertThat(foundResult).isEqualTo(saveResult); + } + + @Test + @DisplayName("1 미만의 수로 찾은 경우 예외 발생") + void testFindNumberLessThan1() { + // given + Persistence store = new ResultStore(); + + // then + CalculationResult saveResult = new CalculationResult("5 + 10", 15.0d); + store.saveResult(saveResult); + + // then + assertThatThrownBy(() -> store.findResult(0)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(CalculatorMessage.INVALID_ORDER.message); + } + + @Test + @DisplayName("사이즈 초과의 수로 찾은 경우 예외 발생") + void testFindNumberMoreThanSize() { + // given + Persistence store = new ResultStore(); + + // when + CalculationResult saveResult = new CalculationResult("5 + 10", 15.0d); + store.saveResult(saveResult); + + // then + assertThatThrownBy(() -> store.findResult(store.findAllResult().size() + 1)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(CalculatorMessage.INVALID_ORDER.message); + } + + @Test + @DisplayName("모두 찾기") + void testFindAll() { + // given + Persistence store = new ResultStore(); + + // when + List results = new ArrayList<>(); + results.add(new CalculationResult("5 + 6", 11.0d)); + results.add(new CalculationResult("5 + 6", 11.0d)); + + for (CalculationResult result : results) { + store.saveResult(result); + } + + // then + List foundResults = store.findAllResult(); + for (CalculationResult foundResult : foundResults) { + assertThat(results.indexOf(foundResult)).isNotEqualTo(-1); + } + } + + @Test + @DisplayName("클리어") + void testClear() { + // given + Persistence store = new ResultStore(); + + // when + store.saveResult(new CalculationResult("11 + 6", 17.0d)); + store.saveResult(new CalculationResult("11 + 10", 21.0d)); + + // then + assertThat(store.findAllResult().size()).isNotEqualTo(0); + store.clear(); + assertThat(store.findAllResult().size()).isEqualTo(0); + } +} diff --git a/calculator/src/test/java/com/wonu606/calculator/strategy/CalculationStrategyTest.java b/calculator/src/test/java/com/wonu606/calculator/strategy/CalculationStrategyTest.java new file mode 100644 index 000000000..de522ff80 --- /dev/null +++ b/calculator/src/test/java/com/wonu606/calculator/strategy/CalculationStrategyTest.java @@ -0,0 +1,135 @@ +package com.wonu606.calculator.strategy; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.wonu606.calculator.calculator.PostfixCalculator; +import com.wonu606.calculator.converter.InfixToPostfixConverter; +import com.wonu606.calculator.storage.ResultStore; +import com.wonu606.calculator.util.CalculatorMessage; +import com.wonu606.calculator.validator.InfixValidator; +import com.wonu606.io.ConsoleInput; +import com.wonu606.io.ConsolePrinter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CalculationStrategyTest { + + @Test + @DisplayName("정상 입력") + void testNormalInput() { + // given + CalculatorStrategy calculator = new CalculationStrategy( + new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator()); + + String expression = "2 + 3"; + InputStream in = new ByteArrayInputStream(expression.getBytes()); + System.setIn(in); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream out = System.out; + System.setOut(new PrintStream(outContent)); + + // when + calculator.execute(new ConsoleInput(), new ConsolePrinter(), new ResultStore()); + + // then + String expectedResult = 5 + "\n"; + assertThat(expectedResult).isEqualTo(outContent.toString()); + } + + @Test + @DisplayName("Double 오버 플로우 발생") + void testDoubleOverflowOccurs() { + // given + CalculatorStrategy calculator = new CalculationStrategy( + new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator()); + + String expression = Double.MAX_VALUE + " + 1"; + InputStream in = new ByteArrayInputStream(expression.getBytes()); + System.setIn(in); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream out = System.out; + System.setOut(new PrintStream(outContent)); + + // when + calculator.execute(new ConsoleInput(), new ConsolePrinter(), new ResultStore()); + + // then + String expectedResult = CalculatorMessage.OVERFLOW_OCCURS.message + "\n"; + assertThat(expectedResult).isEqualTo(outContent.toString()); + } + + @Test + @DisplayName("Integer 오버 플로우 발생") + void testIntegerOverflowOccurs() { + // given + CalculatorStrategy calculator = new CalculationStrategy( + new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator()); + + String expression = Integer.MAX_VALUE + " + 1"; + InputStream in = new ByteArrayInputStream(expression.getBytes()); + System.setIn(in); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream out = System.out; + System.setOut(new PrintStream(outContent)); + + // when + calculator.execute(new ConsoleInput(), new ConsolePrinter(), new ResultStore()); + + // then + String expectedResult = CalculatorMessage.OVERFLOW_OCCURS.message + "\n"; + assertThat(expectedResult).isEqualTo(outContent.toString()); + } + + @Test + @DisplayName("0으로 나눌 경우 오류 발생") + void testDividedByZero() { + // given + CalculatorStrategy calculator = new CalculationStrategy( + new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator()); + + String expression = "10 / 0"; + InputStream in = new ByteArrayInputStream(expression.getBytes()); + System.setIn(in); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream out = System.out; + System.setOut(new PrintStream(outContent)); + + // when + calculator.execute(new ConsoleInput(), new ConsolePrinter(), new ResultStore()); + + // then + String expectedResult = CalculatorMessage.NOT_DIVISIBLE_BY_ZERO.message + "\n"; + assertThat(expectedResult).isEqualTo(outContent.toString()); + } + + @Test + @DisplayName("중위 연산식이 아닌 경우") + void testIsNotInfixExpression() { + // given + CalculatorStrategy calculator = new CalculationStrategy( + new InfixValidator(), new InfixToPostfixConverter(), new PostfixCalculator()); + + String expression = "3 5 +"; + InputStream in = new ByteArrayInputStream(expression.getBytes()); + System.setIn(in); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream out = System.out; + System.setOut(new PrintStream(outContent)); + + // when + calculator.execute(new ConsoleInput(), new ConsolePrinter(), new ResultStore()); + + // then + String expectedResult = CalculatorMessage.INVALID_INPUT.message + "\n"; + assertThat(expectedResult).isEqualTo(outContent.toString()); + } +} diff --git a/calculator/src/test/java/com/wonu606/calculator/validator/InfixValidatorTest.java b/calculator/src/test/java/com/wonu606/calculator/validator/InfixValidatorTest.java new file mode 100644 index 000000000..036f59154 --- /dev/null +++ b/calculator/src/test/java/com/wonu606/calculator/validator/InfixValidatorTest.java @@ -0,0 +1,32 @@ +package com.wonu606.calculator.validator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class InfixValidatorTest { + + @Test + void testNotInfix() { + // given + Validator validator = new InfixValidator(); + + // when + String expression = "1 3 +"; + + // then + assertThat(validator.isValid(expression)).isFalse(); + } + + @Test + void testInfix() { + // given + Validator validator = new InfixValidator(); + + // when + String expression = "1 + 3 + 2 * -3 / -123"; + + // then + assertThat(validator.isValid(expression)).isTrue(); + } +} diff --git a/calculator/src/test/java/com/wonu606/io/ConsoleInputTest.java b/calculator/src/test/java/com/wonu606/io/ConsoleInputTest.java index 783dacddc..b1c80b440 100644 --- a/calculator/src/test/java/com/wonu606/io/ConsoleInputTest.java +++ b/calculator/src/test/java/com/wonu606/io/ConsoleInputTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,7 +11,7 @@ class ConsoleInputTest { @Test @DisplayName("메뉴 입력") - void testMenuSelectionInput() throws IOException { + void testMenuSelectionInput() { String menuSelection = "1"; InputStream in = new ByteArrayInputStream(menuSelection.getBytes()); System.setIn(in); @@ -24,7 +23,7 @@ void testMenuSelectionInput() throws IOException { @Test @DisplayName("표현식 입력") - void testExpressionInput() throws IOException { + void testExpressionInput() { String expression = "2 + 3"; InputStream in = new ByteArrayInputStream(expression.getBytes()); System.setIn(in); @@ -36,7 +35,7 @@ void testExpressionInput() throws IOException { @Test @DisplayName("개행 입력") - void testNewLineInput() throws IOException { + void testNewLineInput() { String newLine = "\n"; InputStream in = new ByteArrayInputStream(newLine.getBytes()); System.setIn(in);