diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..626fc284b --- /dev/null +++ b/.gitignore @@ -0,0 +1,218 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Gradle template +.gradle +**/build/ +!src/**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..c64ae18c9 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,48 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java application project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.1.1/userguide/building_java_projects.html + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + // Apply the application plugin to add support for building a CLI application in Java. + id 'application' +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + // This dependency is used by the application. + implementation 'com.google.guava:guava:31.1-jre' + + // https://mvnrepository.com/artifact/org.assertj/assertj-core + testImplementation 'org.assertj:assertj-core:3.24.2' +} + +testing { + suites { + // Configure the built-in test suite + test { + // Use JUnit Jupiter test framework + useJUnitJupiter('5.9.1') + } + } +} + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + +application { + // Define the main class for the application. + mainClass = 'com.programmers.App' +} diff --git a/app/src/main/java/com/programmers/CalculatorApplication.java b/app/src/main/java/com/programmers/CalculatorApplication.java new file mode 100644 index 000000000..6b5e760e4 --- /dev/null +++ b/app/src/main/java/com/programmers/CalculatorApplication.java @@ -0,0 +1,15 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package com.programmers; + +import com.programmers.controller.CalculatorController; + +public class CalculatorApplication { + + public static void main(String[] args) { + CalculatorController calculatorController = new CalculatorController(); + calculatorController.run(); + } + +} diff --git a/app/src/main/java/com/programmers/calculator/Calculator.java b/app/src/main/java/com/programmers/calculator/Calculator.java new file mode 100644 index 000000000..369a2148a --- /dev/null +++ b/app/src/main/java/com/programmers/calculator/Calculator.java @@ -0,0 +1,20 @@ +package com.programmers.calculator; + +import java.util.List; + +public class Calculator { + + private final PostfixConversion postfixConversion; + private final PostfixCalculator postfixCalculator; + + public Calculator() { + postfixConversion = new PostfixConversion(); + postfixCalculator = new PostfixCalculator(); + } + + public Integer calculate(Formula formula) { + List postfix = postfixConversion.changeInfixToPostfix(formula.getInfixListFormula()); + return postfixCalculator.calculatePostfix(postfix); + } + +} diff --git a/app/src/main/java/com/programmers/calculator/Formula.java b/app/src/main/java/com/programmers/calculator/Formula.java new file mode 100644 index 000000000..40b4aa55a --- /dev/null +++ b/app/src/main/java/com/programmers/calculator/Formula.java @@ -0,0 +1,44 @@ +package com.programmers.calculator; + +import com.programmers.exception.InvalidFormulaException; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class Formula { + + private static final Pattern FORMULA_REGEX = Pattern.compile("([0-9] [+|\\-|*|/] )+[0-9]"); + private static final String EQUAL = " = "; + + private final Calculator calculator; + + private String formula; + private Integer result; + + public Formula(String formula) { + isValid(formula); + this.formula = formula; + this.calculator = new Calculator(); + } + + public List getInfixListFormula() { + return Arrays.stream(formula.split(" ")) + .collect(Collectors.toList()); + } + + public void isValid(String formula) { + if (!FORMULA_REGEX.matcher(formula).matches()) throw new InvalidFormulaException("유효하지 않은 수식 입력입니다."); + } + + public Integer calcualteFormula() { + result = calculator.calculate(this); + return result; + } + + public String toString() { + return formula + EQUAL + result; + } + +} diff --git a/app/src/main/java/com/programmers/calculator/PostfixCalculator.java b/app/src/main/java/com/programmers/calculator/PostfixCalculator.java new file mode 100644 index 000000000..6c7c44bc5 --- /dev/null +++ b/app/src/main/java/com/programmers/calculator/PostfixCalculator.java @@ -0,0 +1,38 @@ +package com.programmers.calculator; + +import com.programmers.util.Operator; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.regex.Pattern; + +public class PostfixCalculator { + + private static final Pattern NUMBER = Pattern.compile("\\d+"); + + public Integer calculatePostfix(List postfix) { + Deque deque = new ArrayDeque<>(); + + postfix.stream() + .forEach(s -> { calculatePostfixWithDeque(s, deque); }); + + return deque.pollLast(); + } + + private void calculatePostfixWithDeque(String s, Deque deque) { + if (NUMBER.matcher(s).matches()) { + deque.addLast(Integer.parseInt(s)); + return; + } + // 연산자 일 경우 + Integer op1 = deque.pollLast(); + Integer op2 = deque.pollLast(); + + Operator calc = Operator.of(s); + Integer result = calc.calculateOperation(op2, op1); + + deque.addLast(result); + } + +} diff --git a/app/src/main/java/com/programmers/calculator/PostfixConversion.java b/app/src/main/java/com/programmers/calculator/PostfixConversion.java new file mode 100644 index 000000000..7d0d48262 --- /dev/null +++ b/app/src/main/java/com/programmers/calculator/PostfixConversion.java @@ -0,0 +1,49 @@ +package com.programmers.calculator; + +import com.programmers.util.Operator; + +import java.util.List; +import java.util.ArrayList; +import java.util.Deque; +import java.util.ArrayDeque; +import java.util.regex.Pattern; + +public class PostfixConversion { + + private static final Pattern NUMBER = Pattern.compile("\\d+"); + + public List changeInfixToPostfix(List infix) { + List postfix = new ArrayList<>(); + Deque deque = new ArrayDeque<>(); + + infix.stream() + .forEach(s -> { makePostfixWithDeque(s, postfix, deque); }); + + // 스택에 남아있는 연산자 모두 집어 넣음 + while (!deque.isEmpty()) postfix.add(deque.pollLast()); + + return postfix; + } + + private void makePostfixWithDeque(String s, List postfix, Deque deque) { + if (NUMBER.matcher(s).matches()) { // 숫자일 경우 + postfix.add(s); + return; + } + // 숫자 아닐 경우 + Operator operator = Operator.of(s); + int priority = operator.getPriority(); + // 덱이 비었거나 + while (!deque.isEmpty()) { + Operator topOperator = Operator.of(deque.peekLast()); + int topPriority = topOperator.getPriority(); + + // top의 우선 순위가 높거나 같을 경우 + if (topPriority >= priority) postfix.add(deque.pollLast()); + // top의 우선 순위가 낮을 경우 + else break; + } + deque.addLast(s); // 연산자 넣음 + } + +} diff --git a/app/src/main/java/com/programmers/controller/CalculatorController.java b/app/src/main/java/com/programmers/controller/CalculatorController.java new file mode 100644 index 000000000..7fbd01b00 --- /dev/null +++ b/app/src/main/java/com/programmers/controller/CalculatorController.java @@ -0,0 +1,65 @@ +package com.programmers.controller; + +import com.programmers.service.CalculatorService; +import com.programmers.ui.InputView; +import com.programmers.ui.OutputView; +import com.programmers.util.Menu; + +import java.io.IOException; +import java.util.List; + +public class CalculatorController { + + private final InputView inputView; + private final OutputView outputView; + private final CalculatorService calculatorService; + + public CalculatorController() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + this.calculatorService = new CalculatorService(); + } + + public void run() { + while (true) { + try { + outputView.init(); + String select = inputView.select(); + Menu menu = Menu.of(select); + if (menu == Menu.VIEW) { + view(); + } else if (menu == Menu.CALCULATE) { + calculate(); + } else if (menu == Menu.EXIT) { + exit(); + break; + } + } catch(Exception e){ + outputView.printExceptionMessage(e.getMessage()); + if (e instanceof IOException) break; + } + } + + try { + inputView.close(); + } catch (Exception e) { + outputView.printExceptionMessage(e.getMessage()); + } + } + + private void calculate() { + String formula = inputView.formula(); + Integer result = calculatorService.calculate(formula); + outputView.viewResult(result); + } + + private void view() { + List record = calculatorService.view(); + outputView.viewRecord(record); + } + + private void exit() { + outputView.exit(); + } + +} diff --git a/app/src/main/java/com/programmers/exception/AbnormalTerminationException.java b/app/src/main/java/com/programmers/exception/AbnormalTerminationException.java new file mode 100644 index 000000000..e8a6e142e --- /dev/null +++ b/app/src/main/java/com/programmers/exception/AbnormalTerminationException.java @@ -0,0 +1,11 @@ +package com.programmers.exception; + +import java.io.IOException; + +public class AbnormalTerminationException extends IOException { + + public AbnormalTerminationException(String message) { + super(message); + } + +} diff --git a/app/src/main/java/com/programmers/exception/DivisionByZeroException.java b/app/src/main/java/com/programmers/exception/DivisionByZeroException.java new file mode 100644 index 000000000..a723be491 --- /dev/null +++ b/app/src/main/java/com/programmers/exception/DivisionByZeroException.java @@ -0,0 +1,9 @@ +package com.programmers.exception; + +public class DivisionByZeroException extends ArithmeticException { + + public DivisionByZeroException(String message) { + super(message); + } + +} diff --git a/app/src/main/java/com/programmers/exception/InvalidFormulaException.java b/app/src/main/java/com/programmers/exception/InvalidFormulaException.java new file mode 100644 index 000000000..f7cee22b8 --- /dev/null +++ b/app/src/main/java/com/programmers/exception/InvalidFormulaException.java @@ -0,0 +1,9 @@ +package com.programmers.exception; + +public class InvalidFormulaException extends NullPointerException { + + public InvalidFormulaException(String message) { + super(message); + } + +} diff --git a/app/src/main/java/com/programmers/exception/NotFoundMenuException.java b/app/src/main/java/com/programmers/exception/NotFoundMenuException.java new file mode 100644 index 000000000..56d1cc896 --- /dev/null +++ b/app/src/main/java/com/programmers/exception/NotFoundMenuException.java @@ -0,0 +1,9 @@ +package com.programmers.exception; + +public class NotFoundMenuException extends NullPointerException { + + public NotFoundMenuException(String message) { + super(message); + } + +} diff --git a/app/src/main/java/com/programmers/exception/NotFoundOperatorException.java b/app/src/main/java/com/programmers/exception/NotFoundOperatorException.java new file mode 100644 index 000000000..fb8cc9ae3 --- /dev/null +++ b/app/src/main/java/com/programmers/exception/NotFoundOperatorException.java @@ -0,0 +1,9 @@ +package com.programmers.exception; + +public class NotFoundOperatorException extends NullPointerException { + + public NotFoundOperatorException(String message) { + super(message); + } + +} diff --git a/app/src/main/java/com/programmers/service/CalculatorService.java b/app/src/main/java/com/programmers/service/CalculatorService.java new file mode 100644 index 000000000..11523af8e --- /dev/null +++ b/app/src/main/java/com/programmers/service/CalculatorService.java @@ -0,0 +1,27 @@ +package com.programmers.service; + +import com.programmers.storage.Storage; +import com.programmers.calculator.Formula; + +import java.util.List; + +public class CalculatorService { + + private final Storage storage; + + public CalculatorService() { + this.storage = new Storage(); + } + + public Integer calculate(String stringOfFormula) { + Formula formula = new Formula(stringOfFormula); + Integer result = formula.calcualteFormula(); + storage.save(formula); + return result; + } + + public List view() { + return storage.findAll(); + } + +} diff --git a/app/src/main/java/com/programmers/storage/Storage.java b/app/src/main/java/com/programmers/storage/Storage.java new file mode 100644 index 000000000..8a4f39d9e --- /dev/null +++ b/app/src/main/java/com/programmers/storage/Storage.java @@ -0,0 +1,26 @@ +package com.programmers.storage; + +import com.programmers.calculator.Formula; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Storage { + + private final static List list = new ArrayList<>(); + + public void save(Formula formula) { + list.add(formula); + } + + public List findAll() { + if (list.size() == 0) { + return null; + } + return list.stream() + .map(Formula::toString) + .collect(Collectors.toList()); + } + +} diff --git a/app/src/main/java/com/programmers/ui/InputView.java b/app/src/main/java/com/programmers/ui/InputView.java new file mode 100644 index 000000000..c1a59ca72 --- /dev/null +++ b/app/src/main/java/com/programmers/ui/InputView.java @@ -0,0 +1,47 @@ +package com.programmers.ui; + +import com.programmers.exception.AbnormalTerminationException; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class InputView { + + private final BufferedReader br; + + public InputView() { + br = new BufferedReader(new InputStreamReader(System.in)); + } + + public String select() { + try { + return br.readLine(); + } catch (IOException e) { + throwAbnormalTerminationException(); + } + return null; + } + + public String formula() { + try { + return br.readLine(); + } catch (IOException e) { + throwAbnormalTerminationException(); + } + return null; + } + + public void close() { + try { + br.close(); + } catch (IOException e) { + throwAbnormalTerminationException(); + } + } + + private AbnormalTerminationException throwAbnormalTerminationException() { + return new AbnormalTerminationException("입력에 예외가 발생하여 비정상적으로 종료되었습니다."); + } + +} diff --git a/app/src/main/java/com/programmers/ui/OutputView.java b/app/src/main/java/com/programmers/ui/OutputView.java new file mode 100644 index 000000000..979514513 --- /dev/null +++ b/app/src/main/java/com/programmers/ui/OutputView.java @@ -0,0 +1,34 @@ +package com.programmers.ui; + +import java.util.List; + +public class OutputView { + + public void init() { + System.out.println("1. 조회"); + System.out.println("2. 계산"); + System.out.println("3. 종료"); + System.out.print("\n선택 : "); + } + + public void viewResult(Integer result) { + System.out.println(result + "\n"); + } + + public void viewRecord(List record) { + System.out.print("\n"); + if (record != null) { + record.stream().forEach(System.out::println); + System.out.print("\n"); + } + } + + public void exit() { + System.out.println("\n프로그램을 종료합니다."); + } + + public void printExceptionMessage(String message) { + System.out.println(message); + } + +} diff --git a/app/src/main/java/com/programmers/util/Menu.java b/app/src/main/java/com/programmers/util/Menu.java new file mode 100644 index 000000000..c5432bdf4 --- /dev/null +++ b/app/src/main/java/com/programmers/util/Menu.java @@ -0,0 +1,26 @@ +package com.programmers.util; + +import com.programmers.exception.NotFoundMenuException; + +import java.util.Arrays; + +public enum Menu { + + VIEW("1"), + CALCULATE("2"), + EXIT("3"); + + private final String select; + + Menu(String select) { + this.select = select; + } + + public static Menu of(String select) { + return Arrays.stream(Menu.values()) + .filter(menu -> menu.select.equals(select)) + .findFirst() + .orElseThrow(() -> new NotFoundMenuException("메뉴에서 없는 선택지입니다.")); + } + +} diff --git a/app/src/main/java/com/programmers/util/Operator.java b/app/src/main/java/com/programmers/util/Operator.java new file mode 100644 index 000000000..608c29596 --- /dev/null +++ b/app/src/main/java/com/programmers/util/Operator.java @@ -0,0 +1,47 @@ +package com.programmers.util; + +import com.programmers.exception.DivisionByZeroException; +import com.programmers.exception.NotFoundOperatorException; + +import java.util.Arrays; +import java.util.function.BiFunction; + +public enum Operator { + + PLUS("+", -1, (a, b) -> { return a + b; }), + MINUS("-", -1, (a, b) -> { return a - b; }), + DIVIDE("/", 1, (a, b) -> { + try { + return a / b; + } catch (ArithmeticException e) { + throw new DivisionByZeroException("0으로 나눌 수 없습니다."); + } + }), + MUTIPLY("*", 1, (a, b) -> { return a * b; }); + + private final String operator; + private final int priority; + private final BiFunction calculation; + + Operator(String operator, int priority, BiFunction calculation) { + this.operator = operator; + this.priority = priority; + this.calculation = calculation; + } + + public static Operator of(String operator) { + return Arrays.stream(Operator.values()) + .filter(o -> o.operator.equals(operator)) + .findFirst() + .orElseThrow(() -> new NotFoundOperatorException("찾을 수 없는 연산자입니다.")); + } + + public int getPriority() { + return priority; + } + + public Integer calculateOperation(Integer op1, Integer op2) { + return calculation.apply(op1, op2); + } + +} diff --git a/app/src/test/java/com/programmers/calculator/PostfixCalculatorTest.java b/app/src/test/java/com/programmers/calculator/PostfixCalculatorTest.java new file mode 100644 index 000000000..ed2922961 --- /dev/null +++ b/app/src/test/java/com/programmers/calculator/PostfixCalculatorTest.java @@ -0,0 +1,28 @@ +package com.programmers.calculator; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class PostfixCalculatorTest { + + PostfixCalculator postfixCalculator = new PostfixCalculator(); + + @Test + @DisplayName("후위 표기식을 계산") + void calcPostfix() { + // given + List postfix = List.of("1", "2", "*", "4", "2", "/", "+"); + + // when + Integer answer = postfixCalculator.calculatePostfix(postfix); + + // then + assertThat(answer).isEqualTo(4); + } + + +} \ No newline at end of file diff --git a/app/src/test/java/com/programmers/calculator/PostfixConversionTest.java b/app/src/test/java/com/programmers/calculator/PostfixConversionTest.java new file mode 100644 index 000000000..1d0454afd --- /dev/null +++ b/app/src/test/java/com/programmers/calculator/PostfixConversionTest.java @@ -0,0 +1,29 @@ +package com.programmers.calculator; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class PostfixConversionTest { + + PostfixConversion postfixConversion = new PostfixConversion(); + + @Test + @DisplayName("중위 표기식을 후위 표기식으로 변환") + void infixTopostfix() { + // given + List infix1 = List.of("1", "*", "2", "+", "3", "/", "4"); + + // when + List postfix = postfixConversion.changeInfixToPostfix(infix1); + + // then + assertThat(postfix) + // 순서도 똑같이 + .containsExactly("1", "2", "*", "3", "4", "/", "+"); + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/programmers/storage/StorageTest.java b/app/src/test/java/com/programmers/storage/StorageTest.java new file mode 100644 index 000000000..43ba2ec3b --- /dev/null +++ b/app/src/test/java/com/programmers/storage/StorageTest.java @@ -0,0 +1,43 @@ +package com.programmers.storage; + +import com.programmers.calculator.Formula; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class StorageTest { + + Storage storage = new Storage(); + + @BeforeEach + void setup() { + Formula formula1 = new Formula("1 * 2"); + formula1.calcualteFormula(); + storage.save(formula1); + + Formula formula2 = new Formula("2 / 2"); + formula2.calcualteFormula(); + storage.save(formula2); + + Formula formula3 = new Formula("1 * 2"); + formula3.calcualteFormula(); + storage.save(formula3); + } + + @Test + @DisplayName("저장을 한다.") + void save() { + // when + List record = storage.findAll(); + + // then + assertThat(record) + .isNotEmpty() + .containsExactly("1 * 2 = 2", "2 / 2 = 1", "1 * 2 = 2"); + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/programmers/util/FormulaTest.java b/app/src/test/java/com/programmers/util/FormulaTest.java new file mode 100644 index 000000000..34a5016d4 --- /dev/null +++ b/app/src/test/java/com/programmers/util/FormulaTest.java @@ -0,0 +1,58 @@ +package com.programmers.util; + +import com.programmers.calculator.Formula; +import com.programmers.exception.InvalidFormulaException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +class FormulaTest { + + @Test + @DisplayName("문자열 연산을 리스트 연산으로 바꾸어준다.") + void changeStrFormulaToListFormula() { + // given + String strExpression = "3 * 4 / 2"; + + // when + Formula formula = new Formula(strExpression); + List listExpression = formula.getInfixListFormula(); + + // then + assertThat(listExpression) + .containsExactly("3", "*", "4", "/", "2"); + } + + @Test + @DisplayName("수식을 계산한 결과와 함께 문자열로 만든다.") + void changeListFormulaToStrFormula() { + // given + Formula formula = new Formula("3 * 4 / 2"); + Integer result = formula.calcualteFormula(); + + // when + String formulaOfString = formula.toString(); + + // then + assertThat(formulaOfString) + .isEqualTo("3 * 4 / 2 = 6"); + } + + @ParameterizedTest + @DisplayName("잘못된 입력은 예외를 던진다.") + @ValueSource(strings = {"1 + 1", "1 + ", "1 ! 1", " + 1", " ", "111 + 111"}) + void throwWhenInvalidInput(String invalidInput) { + assertThatThrownBy(() -> { + // when + Formula formula = new Formula(invalidInput); + // then + }).isExactlyInstanceOf(InvalidFormulaException.class) + .hasMessage("유효하지 않은 수식 입력입니다."); + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/programmers/util/MenuTest.java b/app/src/test/java/com/programmers/util/MenuTest.java new file mode 100644 index 000000000..0a8eb3d06 --- /dev/null +++ b/app/src/test/java/com/programmers/util/MenuTest.java @@ -0,0 +1,34 @@ +package com.programmers.util; + +import com.programmers.exception.NotFoundMenuException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.*; + +class MenuTest { + + @ParameterizedTest + @DisplayName("올바른 입력을 할 경우 해당되는 메뉴를 반환한다.") + @CsvSource(value = {"1 VIEW", "2 CALCULATE", "3 EXIT"}, delimiter = ' ') + void correctMenu(String select, Menu menu) { + assertThat(Menu.of(select)) + .isEqualTo(menu); + } + + @Test + @DisplayName("선택지에 없는 숫자를 입력하면 NotFoundMenuException을 던진다.") + void throwNotFoundMenuException() { + // given + String select = "4"; + + // then + assertThatThrownBy(() -> { + Menu.of(select); + }).isExactlyInstanceOf(NotFoundMenuException.class) + .hasMessage("메뉴에서 없는 선택지입니다."); + } + +} \ No newline at end of file diff --git a/app/src/test/java/com/programmers/util/OperatorTest.java b/app/src/test/java/com/programmers/util/OperatorTest.java new file mode 100644 index 000000000..9610317f1 --- /dev/null +++ b/app/src/test/java/com/programmers/util/OperatorTest.java @@ -0,0 +1,58 @@ +package com.programmers.util; + +import com.programmers.exception.DivisionByZeroException; +import com.programmers.exception.NotFoundOperatorException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.assertj.core.api.Assertions.*; + +class OperatorTest { + + @ParameterizedTest + @DisplayName("연산자에 따라 제대로 계산") + @CsvSource(value={"+ 8", "- 4", "* 12", "/ 3"}, delimiter = ' ') + void exactlyCalc(String operator, Integer result) { + // given + Integer op1 = 6; Integer op2 = 2; + + // when + Operator calc = Operator.of(operator); + + // then + assertThat(calc.calculateOperation(op1, op2)) + .isEqualTo(result); + } + + @Test + @DisplayName("이상한 연산자가 들어갈 경우 NotFoundOperatorException 던짐") + void throwNullPointerExceptionWhenoddOperator() { + // given + String oddOperator = "!"; + + assertThatThrownBy(() -> { + // when + Operator.of(oddOperator); + // then + }).isExactlyInstanceOf(NotFoundOperatorException.class) + .hasMessage("찾을 수 없는 연산자입니다."); + } + + @Test + @DisplayName("0으로 나누는 경우 DivisionByZeroException 던짐") + void throwWhenDivideZero() { + // given + Integer op1 = 4; Integer op2 = 0; + Operator calc = Operator.of("/"); + + assertThatThrownBy(() -> { + // when + calc.calculateOperation(op1, op2); + // then + }).isExactlyInstanceOf(DivisionByZeroException.class) + .hasMessage("0으로 나눌 수 없습니다."); + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..ad51c31a5 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,17 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.1.1/userguide/multi_project_builds.html + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + // Apply the foojay-resolver plugin to allow automatic download of JDKs + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' +} + +rootProject.name = 'java-calculator' +include('app')