diff --git a/README.md b/README.md index 86699576c..4a87494ff 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,28 @@ # 미션 - 자판기 -## 🔍 진행방식 +## 🔍 진행 방식 -- 미션은 **기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항** 세 가지로 구성되어 있다. -- 세 개의 요구사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. -- 기능 요구사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. +- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. +- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. +- 기능 요구 사항에 기재되지 않은 내용은 **스스로 판단하여 구현한다.** -## ✉️ 미션 제출 방법 +## 📮 미션 제출 방법 - 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 를 참고해 제출한다. -- GitHub에 미션을 제출한 후 [우아한테크코스 지원 플랫폼](https://apply.techcourse.co.kr) 에 접속하여 프리코스 과제를 제출한다. - - 자세한 방법은 [링크](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 를 참고한다. - - **Pull Request만 보내고, 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.** + - GitHub을 활용한 제출 방법은 [우아한테크코스 프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 제출한다. +- GitHub에 미션을 제출한 후 슬랙 `#미션-제출` 채널에 PR 주소를 올린다. + - **Pull Request만 보내고 Slack에 메시지를 보내지 않으면 리뷰를 하지 않으므로 주의한다.** -## ✔️ 과제 제출 전 체크리스트 - 0점 방지 +## 🚨 과제 제출 전 체크 리스트 -- 터미널에서 `java -version`을 실행해 자바 8인지 확인한다. 또는 Eclipse, IntelliJ IDEA와 같은 IDE의 자바 8로 실행하는지 확인한다. -- 터미널에서 맥 또는 리눅스 사용자의 경우 `./gradlew clean test`, 윈도우 사용자의 경우 `gradlew.bat clean test` 명령을 실행했을 때 모든 테스트가 아래와 같이 통과하는지 확인한다. +- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지켜야한다.** +- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. + +### 테스트 실행 가이드 + +- 터미널에서 `java -version`을 실행하여 Java 버전이 11인지 확인한다. 또는 Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 11로 실행되는지 확인한다. +- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, + Windows 사용자의 경우 `gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다. ``` BUILD SUCCESSFUL in 0s @@ -25,6 +30,82 @@ BUILD SUCCESSFUL in 0s --- +## 🚀 기능 목록 +### 예외 +- 잘못된 금액 입력시 `[ERROR] 금액은 숫자여야 합니다.`예외 발생 +- 상품명, 가격, 수량입력 형식이 잘못된 경우 + - `[ERROR] {상품명, 가격, 수량} 입력 형식을 확인해주세요.` 예외 발생 + +### 금액 +- 금액은 숫자이다. +- 금액은 1원 단위가 될 수 없다.(ex 1001원) + +### 가격 +- 가격은 숫자이다. +- 가격은 100원 이상이고 10원으로 나누어 떨어져야 한다. + +### 수량 +- 수량은 0개보다 작을 수 없다. + +### 주문 +- 사용자가 구매하고자 하는 상품의 이름으로 주문한다. + +### 상품 +- 상품은 이름, 가격, 수량이 있다. +- 상품의 가격은 100원 이상이고, 10원으로 나누어 떨어져야 한다. + +### 잔돈 +- 잔돈은 동전으로 이루어져 있다. +- 제공된 Coin enum을 사용한다. + +### 자판기 +- 잔돈을 가지고 있다. +- 자판기에 상품을 채워넣을 수 있다. +- 사용자가 금액을 투입할 수 있다. +- 남은 금액이 상품의 최저 가격보다 적거나 모든 상품이 소진된 경우 바로 잔돈을 돌려준다. +- 잔돈을 반환할 수 없는 경우 잔돈으로 반환할 수 있는 금액만 반환한다. + +### 입력 +- 자판기가 보유하고 있는 금액을 입력한다. +```angular2html +자판기가 보유하고 있는 금액을 입력해 주세요. +450 +``` +- 상품명, 가격, 수량은 쉼표로, 개별 상품은 대괄호 []로 묶어서 세미콜론;으로 구분한다. +```angular2html +[콜라,1500,20];[사이다,1000,10] +``` +- 투입 금액을 입력한다. +```angular2html +투입 금액을 입력해 주세요. +3000 +``` +- 구매할 상품명을 입력한다. +```angular2html +구매할 상품명을 입력해 주세요. +사이다 +``` + +### 출력 +- 자판기가 보유한 동전을 출력한다. +```angular2html +자판기가 보유한 동전 +500원 - 0개 +100원 - 4개 +50원 - 1개 +10원 - 0개 +``` +- 투입 금액을 출력한다. +```angular2html +투입 금액: 3000원 +``` +- 잔돈을 출력한다. +```angular2html +잔돈 +100원 - 4개 +50원 - 1개 +``` + ## 🚀 기능 요구사항 반환되는 동전이 최소한이 되는 자판기를 구현한다. @@ -112,10 +193,10 @@ BUILD SUCCESSFUL in 0s ## 🎱 프로그래밍 요구사항 -- 프로그램을 실행하는 시작점은 `Application`의 `main()`이다. -- JDK 8 버전에서 실행 가능해야 한다. **JDK 8에서 정상 동작하지 않을 경우 0점 처리**한다. -- 자바 코드 컨벤션을 지키면서 프로그래밍한다. - - https://naver.github.io/hackday-conventions-java +- JDK 11 버전에서 실행 가능해야 한다. +- 프로그램 실행의 시작점은 `Application`의 `main()`이다. +- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. +- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다. - indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. @@ -154,14 +235,14 @@ public enum Coin { - JDK에서 기본 제공하는 Random, Scanner API 대신 `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms`, `Console` API를 활용해 구현해야 한다. - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInList()`를 활용한다. - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. -- 프로그램 구현을 완료했을 때 `src/test/java` 디렉터리의 `ApplicationTest`에 있는 모든 테스트 케이스가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** +- 프로그램 구현을 완료했을 때 `src/test/java` 디렉터리의 `ApplicationTest`에 있는 모든 테스트 케이스가 성공해야 한다. --- -## 📈 과제 진행 요구사항 +## ✏️ 과제 진행 요구 사항 -- 미션은 [java-vendingmachine-precourse](https://github.com/woowacourse/java-vendingmachine-precourse) 저장소를 Fork/Clone해 시작한다. -- **기능을 구현하기 전에 java-vendingmachine-precourse/docs/README.md 파일에 구현할 기능 목록을 정리**해 추가한다. -- **Git의 커밋 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위**로 추가한다. - - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 를 참고한다. +- 미션은 [java-vendingmachine](https://github.com/grow-up-study/java-vendingmachine) 저장소를 Fork & Clone해 시작한다. +- 과제 진행 및 제출 방법은 [우아한테크코스 프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다. +- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. +- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다. + - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다. diff --git a/src/main/java/vendingmachine/Application.java b/src/main/java/vendingmachine/Application.java index 9d3be447b..4fcbe1f13 100644 --- a/src/main/java/vendingmachine/Application.java +++ b/src/main/java/vendingmachine/Application.java @@ -1,7 +1,10 @@ package vendingmachine; +import vendingmachine.controller.VendingMachineController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + VendingMachineController vendingMachineController = new VendingMachineController(); + vendingMachineController.run(); } } diff --git a/src/main/java/vendingmachine/controller/Parser.java b/src/main/java/vendingmachine/controller/Parser.java new file mode 100644 index 000000000..f354e22de --- /dev/null +++ b/src/main/java/vendingmachine/controller/Parser.java @@ -0,0 +1,103 @@ +package vendingmachine.controller; + +import java.util.Arrays; +import java.util.stream.Collectors; +import vendingmachine.exception.AmountNumberFormatException; +import vendingmachine.exception.PriceNumberFormatException; +import vendingmachine.exception.ProductFormatException; +import vendingmachine.exception.QuantityNumberFormatException; +import vendingmachine.model.Amount; +import vendingmachine.model.MachineAmount; +import vendingmachine.model.Name; +import vendingmachine.model.Order; +import vendingmachine.model.Price; +import vendingmachine.model.Product; +import vendingmachine.model.Products; +import vendingmachine.model.Quantity; + +public class Parser { + + private static final String PRODUCT_SEPARATOR = ";"; + private static final char PRODUCT_START_WITH_CHAR = '['; + private static final char PRODUCT_END_WITH_CHAR = ']'; + private static final String PRODUCT_START_WITH_STRING = "["; + private static final String PRODUCT_END_WITH_STRING = "]"; + private static final String PRODUCT_INFO_SEPARATOR = ","; + private static final int MIN_PRODUCT_INFO_STRING_SIZE = 2; + private static final int PRODUCT_INFO_SIZE = 3; + private static final int PRODUCT_NAME_INDEX = 0; + private static final int PRODUCT_PRICE_INDEX = 1; + private static final int PRODUCT_QUANTITY_INDEX = 2; + + + private Parser() { + } + + public static MachineAmount parseMachineAmount(String amount) { + try { + return new MachineAmount(Integer.parseInt(amount)); + }catch (NumberFormatException exception) { + throw new AmountNumberFormatException(); + } + } + + public static Amount parseAmount(String amount) { + try { + return new Amount(Integer.parseInt(amount)); + }catch (NumberFormatException exception) { + throw new AmountNumberFormatException(); + } + } + + public static Products parseProducts(String products) { + String[] product = products.split(PRODUCT_SEPARATOR); + return new Products(Arrays.stream(product).map(Parser::parseProduct).collect(Collectors.toList())); + } + + private static Product parseProduct(String product) { + validateProductString(product); + String replaced = product.replace(PRODUCT_START_WITH_STRING, "").replace(PRODUCT_END_WITH_STRING, ""); + String[] productInfo = replaced.split(PRODUCT_INFO_SEPARATOR); + validateProductInfo(productInfo); + return new Product( + new Name(productInfo[PRODUCT_NAME_INDEX]), + parsePrice(productInfo[PRODUCT_PRICE_INDEX]), + parseQuantity(productInfo[PRODUCT_QUANTITY_INDEX]) + ); + } + + private static void validateProductInfo(String[] productInfo) { + if (productInfo.length != PRODUCT_INFO_SIZE) { + throw new ProductFormatException(); + } + } + + private static void validateProductString(String product) { + if (product.toCharArray().length <= MIN_PRODUCT_INFO_STRING_SIZE) { + throw new ProductFormatException(); + } + if (!(product.charAt(0) == PRODUCT_START_WITH_CHAR && product.charAt(product.length() - 1) == PRODUCT_END_WITH_CHAR)) { + throw new ProductFormatException(); + } + } + + private static Price parsePrice(String price) { + try { + return new Price(Integer.parseInt(price)); + }catch (NumberFormatException exception) { + throw new PriceNumberFormatException(); + } + } + + private static Quantity parseQuantity(String quantity) { + try { + return new Quantity(Integer.parseInt(quantity)); + }catch (NumberFormatException exception) { + throw new QuantityNumberFormatException(); + } + } + + public static Order parseOrder(String productName) { + return new Order(new Name(productName)); + } +} diff --git a/src/main/java/vendingmachine/controller/VendingMachineController.java b/src/main/java/vendingmachine/controller/VendingMachineController.java new file mode 100644 index 000000000..b68ef1097 --- /dev/null +++ b/src/main/java/vendingmachine/controller/VendingMachineController.java @@ -0,0 +1,85 @@ +package vendingmachine.controller; + +import java.util.function.Supplier; +import vendingmachine.dto.ChangeDto; +import vendingmachine.exception.IllegalArgumentBaseException; +import vendingmachine.model.Amount; +import vendingmachine.model.Change; +import vendingmachine.model.MachineAmount; +import vendingmachine.model.Order; +import vendingmachine.model.Products; +import vendingmachine.model.VendingMachine; +import vendingmachine.service.VendingMachineService; +import vendingmachine.view.InputView; +import vendingmachine.view.OutputView; + +public class VendingMachineController { + + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + private VendingMachineService vendingMachineService; + + public void run() { + Change change = handleInput(this::inputMachineAmount); + outputView.printMachineState(new ChangeDto(change)); + Products products = handleInput(this::inputProducts); + Amount amount = handleInput(this::inputAmount); + vendingMachineService = new VendingMachineService(new VendingMachine(change, products, amount)); + buy(); + } + + private void buy() { + while(vendingMachineService.isBuyable()) { + buyProduct(); + } + printResultChange(); + } + + private void printResultChange() { + outputView.printRemainAmount(vendingMachineService.getAmount()); + outputView.printChange(vendingMachineService.getChange()); + } + + private void buyProduct() { + outputView.printRemainAmount(vendingMachineService.getAmount()); + handleInput(() -> vendingMachineService.buy(inputOrder())); + } + + private Order inputOrder() { + return Parser.parseOrder(inputView.inputBuyProductName()); + } + + private Amount inputAmount() { + return Parser.parseAmount(inputView.inputBuyAmount()); + } + + private Change inputMachineAmount() { + MachineAmount amount = Parser.parseMachineAmount(inputView.inputMachineAmount()); + return new Change(amount); + } + + private Products inputProducts() { + return Parser.parseProducts(inputView.inputProducts()); + } + + private T handleInput(Supplier inputSupplier) { + while (true) { + try { + return inputSupplier.get(); + } catch (IllegalArgumentBaseException exception) { + outputView.printErrorMessage(exception.getMessage()); + } + } + } + + private void handleInput(Runnable runnable) { + while (true) { + try { + runnable.run(); + return; + } catch (IllegalArgumentBaseException exception) { + outputView.printErrorMessage(exception.getMessage()); + } + } + } +} diff --git a/src/main/java/vendingmachine/dto/ChangeDto.java b/src/main/java/vendingmachine/dto/ChangeDto.java new file mode 100644 index 000000000..46bd0d93a --- /dev/null +++ b/src/main/java/vendingmachine/dto/ChangeDto.java @@ -0,0 +1,17 @@ +package vendingmachine.dto; + +import java.util.LinkedHashMap; +import java.util.Map; +import vendingmachine.model.Change; + +public class ChangeDto { + private final Map change = new LinkedHashMap<>(); + + public ChangeDto(Change change) { + change.getChangeMoney().forEach(((coin, count) -> this.change.put(coin.getAmount(), count))); + } + + public Map getChange() { + return change; + } +} diff --git a/src/main/java/vendingmachine/exception/AmountFormatException.java b/src/main/java/vendingmachine/exception/AmountFormatException.java new file mode 100644 index 000000000..8f2d0551a --- /dev/null +++ b/src/main/java/vendingmachine/exception/AmountFormatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class AmountFormatException extends IllegalArgumentBaseException { + + public AmountFormatException() { + super(ExceptionMessage.AMOUNT_FORMAT); + } +} diff --git a/src/main/java/vendingmachine/exception/AmountNotEnoughException.java b/src/main/java/vendingmachine/exception/AmountNotEnoughException.java new file mode 100644 index 000000000..3f3b86d51 --- /dev/null +++ b/src/main/java/vendingmachine/exception/AmountNotEnoughException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class AmountNotEnoughException extends IllegalArgumentBaseException { + + public AmountNotEnoughException() { + super(ExceptionMessage.AMOUNT_NOT_ENOUGH); + } +} diff --git a/src/main/java/vendingmachine/exception/AmountNumberFormatException.java b/src/main/java/vendingmachine/exception/AmountNumberFormatException.java new file mode 100644 index 000000000..d29f17d89 --- /dev/null +++ b/src/main/java/vendingmachine/exception/AmountNumberFormatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class AmountNumberFormatException extends IllegalArgumentBaseException { + + public AmountNumberFormatException() { + super(ExceptionMessage.AMOUNT_NUMBER_FORMAT); + } +} diff --git a/src/main/java/vendingmachine/exception/ExceptionMessage.java b/src/main/java/vendingmachine/exception/ExceptionMessage.java new file mode 100644 index 000000000..ab5b224ee --- /dev/null +++ b/src/main/java/vendingmachine/exception/ExceptionMessage.java @@ -0,0 +1,27 @@ +package vendingmachine.exception; + +public enum ExceptionMessage { + + PRICE_NUMBER_FORMAT("가격은 숫자여야 합니다."), + AMOUNT_NUMBER_FORMAT("금액은 숫자여야 합니다."), + QUANTITY_NUMBER_FORMAT("수량은 숫자여야 합니다."), + AMOUNT_FORMAT("금액은 10으로 나누어 떨어지며 0 이상이여야 합니다."), + PRODUCT_PRICE_FORMAT("상품 가격은 100원 이상 10으로 나누어 떨어져야 합니다."), + PRODUCT_FORMAT("상품 입력 형식을 확인해주세요."), + PRODUCT_QUANTITY_FORMAT("상품의 개수는 0개 이상이여야 합니다."), + PRODUCT_NAME_NOT_BLANK("상품의 이름은 공백일 수 없습니다."), + PRODUCT_NAME_NOT_FOUND("존재하지 않는 상품입니다."), + PRODUCT_QUANTITY_NOT_ENOUGH("재고가 부족합니다."), + AMOUNT_NOT_ENOUGH("금액이 부족합니다."), + ; + + private final String message; + + ExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/vendingmachine/exception/IllegalArgumentBaseException.java b/src/main/java/vendingmachine/exception/IllegalArgumentBaseException.java new file mode 100644 index 000000000..50563462b --- /dev/null +++ b/src/main/java/vendingmachine/exception/IllegalArgumentBaseException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class IllegalArgumentBaseException extends IllegalArgumentException { + + protected IllegalArgumentBaseException(ExceptionMessage exceptionMessage) { + super(String.format("[ERROR] %s", exceptionMessage.getMessage())); + } +} diff --git a/src/main/java/vendingmachine/exception/PriceNumberFormatException.java b/src/main/java/vendingmachine/exception/PriceNumberFormatException.java new file mode 100644 index 000000000..53c10dab1 --- /dev/null +++ b/src/main/java/vendingmachine/exception/PriceNumberFormatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class PriceNumberFormatException extends IllegalArgumentBaseException { + + public PriceNumberFormatException() { + super(ExceptionMessage.PRICE_NUMBER_FORMAT); + } +} diff --git a/src/main/java/vendingmachine/exception/ProductFormatException.java b/src/main/java/vendingmachine/exception/ProductFormatException.java new file mode 100644 index 000000000..d31bc1279 --- /dev/null +++ b/src/main/java/vendingmachine/exception/ProductFormatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class ProductFormatException extends IllegalArgumentException { + + public ProductFormatException() { + super(ExceptionMessage.PRODUCT_FORMAT.getMessage()); + } +} diff --git a/src/main/java/vendingmachine/exception/ProductNameNotBlankException.java b/src/main/java/vendingmachine/exception/ProductNameNotBlankException.java new file mode 100644 index 000000000..90c9ccbe8 --- /dev/null +++ b/src/main/java/vendingmachine/exception/ProductNameNotBlankException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class ProductNameNotBlankException extends IllegalArgumentBaseException { + + public ProductNameNotBlankException() { + super(ExceptionMessage.PRODUCT_NAME_NOT_BLANK); + } +} diff --git a/src/main/java/vendingmachine/exception/ProductNameNotFoundException.java b/src/main/java/vendingmachine/exception/ProductNameNotFoundException.java new file mode 100644 index 000000000..411b0cfe1 --- /dev/null +++ b/src/main/java/vendingmachine/exception/ProductNameNotFoundException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class ProductNameNotFoundException extends IllegalArgumentBaseException { + + public ProductNameNotFoundException() { + super(ExceptionMessage.PRODUCT_NAME_NOT_FOUND); + } +} diff --git a/src/main/java/vendingmachine/exception/ProductPriceFromatException.java b/src/main/java/vendingmachine/exception/ProductPriceFromatException.java new file mode 100644 index 000000000..4994efebc --- /dev/null +++ b/src/main/java/vendingmachine/exception/ProductPriceFromatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class ProductPriceFromatException extends IllegalArgumentBaseException { + + public ProductPriceFromatException() { + super(ExceptionMessage.PRODUCT_PRICE_FORMAT); + } +} diff --git a/src/main/java/vendingmachine/exception/ProductQuantityFormatException.java b/src/main/java/vendingmachine/exception/ProductQuantityFormatException.java new file mode 100644 index 000000000..31b7b7dbf --- /dev/null +++ b/src/main/java/vendingmachine/exception/ProductQuantityFormatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class ProductQuantityFormatException extends IllegalArgumentBaseException { + + public ProductQuantityFormatException() { + super(ExceptionMessage.PRODUCT_QUANTITY_FORMAT); + } +} diff --git a/src/main/java/vendingmachine/exception/ProductQuantityNotEnoughException.java b/src/main/java/vendingmachine/exception/ProductQuantityNotEnoughException.java new file mode 100644 index 000000000..17c118c3d --- /dev/null +++ b/src/main/java/vendingmachine/exception/ProductQuantityNotEnoughException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class ProductQuantityNotEnoughException extends IllegalArgumentBaseException { + + public ProductQuantityNotEnoughException() { + super(ExceptionMessage.PRODUCT_QUANTITY_NOT_ENOUGH); + } +} diff --git a/src/main/java/vendingmachine/exception/QuantityNumberFormatException.java b/src/main/java/vendingmachine/exception/QuantityNumberFormatException.java new file mode 100644 index 000000000..12aa03ccb --- /dev/null +++ b/src/main/java/vendingmachine/exception/QuantityNumberFormatException.java @@ -0,0 +1,8 @@ +package vendingmachine.exception; + +public class QuantityNumberFormatException extends IllegalArgumentBaseException { + + public QuantityNumberFormatException() { + super(ExceptionMessage.QUANTITY_NUMBER_FORMAT); + } +} diff --git a/src/main/java/vendingmachine/model/Amount.java b/src/main/java/vendingmachine/model/Amount.java new file mode 100644 index 000000000..f18a43dec --- /dev/null +++ b/src/main/java/vendingmachine/model/Amount.java @@ -0,0 +1,29 @@ +package vendingmachine.model; + +import vendingmachine.exception.AmountFormatException; + +public class Amount { + private int value; + + public Amount(int amount) { + validateAmount(amount); + this.value = amount; + } + + private void validateAmount(int amount) { + if (amount % 10 != 0) { + throw new AmountFormatException(); + } + } + + public int getValue() { + return value; + } + + public void decrease(int amount) { + if (this.value - amount < 0) { + throw new AmountFormatException(); + } + this.value -= amount; + } +} diff --git a/src/main/java/vendingmachine/model/Change.java b/src/main/java/vendingmachine/model/Change.java new file mode 100644 index 000000000..4b08e1aa8 --- /dev/null +++ b/src/main/java/vendingmachine/model/Change.java @@ -0,0 +1,28 @@ +package vendingmachine.model; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class Change { + private final Map changeMoney = new LinkedHashMap<>(); + + public Change(MachineAmount amount) { + int remainMoney = amount.getAmount(); + for (Coin coin : Coin.values()) { + int coinCount = remainMoney / coin.getAmount(); + changeMoney.put(coin, coinCount); + remainMoney -= coin.getAmount() * coinCount; + } + } + + public int getTotalChange() { + return changeMoney.entrySet() + .stream() + .mapToInt(changeEntry -> changeEntry.getKey().getAmount() * changeEntry.getValue()) + .sum(); + } + + public Map getChangeMoney() { + return changeMoney; + } +} diff --git a/src/main/java/vendingmachine/Coin.java b/src/main/java/vendingmachine/model/Coin.java similarity index 68% rename from src/main/java/vendingmachine/Coin.java rename to src/main/java/vendingmachine/model/Coin.java index c76293fbc..6779e7d40 100644 --- a/src/main/java/vendingmachine/Coin.java +++ b/src/main/java/vendingmachine/model/Coin.java @@ -1,4 +1,4 @@ -package vendingmachine; +package vendingmachine.model; public enum Coin { COIN_500(500), @@ -12,5 +12,7 @@ public enum Coin { this.amount = amount; } - // 추가 기능 구현 + public int getAmount() { + return amount; + } } diff --git a/src/main/java/vendingmachine/model/MachineAmount.java b/src/main/java/vendingmachine/model/MachineAmount.java new file mode 100644 index 000000000..3cd55baa4 --- /dev/null +++ b/src/main/java/vendingmachine/model/MachineAmount.java @@ -0,0 +1,22 @@ +package vendingmachine.model; + +import vendingmachine.exception.AmountFormatException; + +public class MachineAmount { + private int amount; + + public MachineAmount(int amount) { + validateAmount(amount); + this.amount = amount; + } + + private void validateAmount(int amount) { + if (amount < 0 || amount % 10 != 0) { + throw new AmountFormatException(); + } + } + + public int getAmount() { + return amount; + } +} diff --git a/src/main/java/vendingmachine/model/Name.java b/src/main/java/vendingmachine/model/Name.java new file mode 100644 index 000000000..89f3f7ba0 --- /dev/null +++ b/src/main/java/vendingmachine/model/Name.java @@ -0,0 +1,40 @@ +package vendingmachine.model; + +import java.util.Objects; +import vendingmachine.exception.ProductNameNotBlankException; + +public class Name { + private final String value; + + public Name(String name) { + validateName(name); + this.value = name; + } + + private void validateName(String name) { + if (name.replace(" ","").isEmpty()) { + throw new ProductNameNotBlankException(); + } + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Name)) { + return false; + } + Name otherName = (Name) other; + return Objects.equals(getValue(), otherName.getValue()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getValue()); + } +} diff --git a/src/main/java/vendingmachine/model/Order.java b/src/main/java/vendingmachine/model/Order.java new file mode 100644 index 000000000..c5cc32faf --- /dev/null +++ b/src/main/java/vendingmachine/model/Order.java @@ -0,0 +1,13 @@ +package vendingmachine.model; + +public class Order { + private final Name productName; + + public Order(Name productName) { + this.productName = productName; + } + + public Name name() { + return new Name(productName.getValue()); + } +} diff --git a/src/main/java/vendingmachine/model/Price.java b/src/main/java/vendingmachine/model/Price.java new file mode 100644 index 000000000..8039372b2 --- /dev/null +++ b/src/main/java/vendingmachine/model/Price.java @@ -0,0 +1,22 @@ +package vendingmachine.model; + +import vendingmachine.exception.ProductPriceFromatException; + +public class Price { + private final int value; + + public Price(int price) { + validateAmount(price); + this.value = price; + } + + private void validateAmount(int price) { + if (price < 100 || price % 10 != 0) { + throw new ProductPriceFromatException(); + } + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/vendingmachine/model/Product.java b/src/main/java/vendingmachine/model/Product.java new file mode 100644 index 000000000..95cf550ba --- /dev/null +++ b/src/main/java/vendingmachine/model/Product.java @@ -0,0 +1,37 @@ +package vendingmachine.model; + +public class Product { + private final Name name; + private final Price price; + private final Quantity quantity; + + public Product(Name name, Price price, Quantity quantity) { + this.name = name; + this.price = price; + this.quantity = quantity; + } + + public Name getName() { + return name; + } + + public boolean isBuyable(Amount amount) { + return price.getValue() <= amount.getValue(); + } + + public int getPrice() { + return price.getValue(); + } + + public void decreaseQuantity() { + this.quantity.decrease(); + } + + public int getQuantity() { + return quantity.getQuantity(); + } + + public boolean isEnoughQuantity() { + return quantity.getQuantity() > 0; + } +} diff --git a/src/main/java/vendingmachine/model/Products.java b/src/main/java/vendingmachine/model/Products.java new file mode 100644 index 000000000..cec19147c --- /dev/null +++ b/src/main/java/vendingmachine/model/Products.java @@ -0,0 +1,42 @@ +package vendingmachine.model; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public class Products implements Iterable { + private final List products; + + public Products(List products) { + this.products = new ArrayList<>(products); + } + + public Optional find(Name name) { + return products.stream().filter(product -> product.getName().equals(name)).findFirst(); + } + + public long buyableProductCount(int amount) { + return products.stream() + .filter(product -> product.getPrice() <= amount) + .filter(product -> product.getQuantity() > 0) + .count(); + } + + @Override + public Iterator iterator() { + return products.iterator(); + } + + @Override + public void forEach(Consumer action) { + Iterable.super.forEach(action); + } + + public int findMinPrice() { + return products.stream() + .mapToInt(Product::getPrice) + .min().getAsInt(); + } +} diff --git a/src/main/java/vendingmachine/model/Quantity.java b/src/main/java/vendingmachine/model/Quantity.java new file mode 100644 index 000000000..9d8b4e188 --- /dev/null +++ b/src/main/java/vendingmachine/model/Quantity.java @@ -0,0 +1,26 @@ +package vendingmachine.model; + +import vendingmachine.exception.ProductQuantityFormatException; + +public class Quantity { + private int quantity; + + public Quantity(int quantity) { + validateQuantity(quantity); + this.quantity = quantity; + } + + private void validateQuantity(int quantity) { + if (quantity <= 0) { + throw new ProductQuantityFormatException(); + } + } + + public void decrease() { + this.quantity--; + } + + public int getQuantity() { + return quantity; + } +} diff --git a/src/main/java/vendingmachine/model/VendingMachine.java b/src/main/java/vendingmachine/model/VendingMachine.java new file mode 100644 index 000000000..a55cb551d --- /dev/null +++ b/src/main/java/vendingmachine/model/VendingMachine.java @@ -0,0 +1,49 @@ +package vendingmachine.model; + +import vendingmachine.exception.AmountNotEnoughException; +import vendingmachine.exception.ProductNameNotFoundException; +import vendingmachine.exception.ProductQuantityNotEnoughException; + +public class VendingMachine { + private final Change change; + private final Products products; + private final Amount amount; + + public VendingMachine(Change change, Products products, Amount amount) { + this.change = change; + this.products = products; + this.amount = amount; + } + + public Change getChange() { + if (change.getTotalChange() <= amount.getValue()) { + return change; + } + return new Change(new MachineAmount(amount.getValue())); + } + + public boolean isBuyableAnyProduct() { + return products.buyableProductCount(getAmount()) > 0; + } + + public void buy(Order order) { + Product product = validateOrder(order); + amount.decrease(product.getPrice()); + product.decreaseQuantity(); + } + + private Product validateOrder(Order order) { + Product product = products.find(order.name()).orElseThrow(ProductNameNotFoundException::new); + if (!product.isBuyable(amount)) { + throw new AmountNotEnoughException(); + } + if (!product.isEnoughQuantity()) { + throw new ProductQuantityNotEnoughException(); + } + return product; + } + + public int getAmount() { + return amount.getValue(); + } +} diff --git a/src/main/java/vendingmachine/service/VendingMachineService.java b/src/main/java/vendingmachine/service/VendingMachineService.java new file mode 100644 index 000000000..327e91ad9 --- /dev/null +++ b/src/main/java/vendingmachine/service/VendingMachineService.java @@ -0,0 +1,30 @@ +package vendingmachine.service; + +import vendingmachine.dto.ChangeDto; +import vendingmachine.model.Order; +import vendingmachine.model.VendingMachine; + +public class VendingMachineService { + + private final VendingMachine vendingMachine; + + public VendingMachineService(VendingMachine vendingMachine) { + this.vendingMachine = vendingMachine; + } + + public boolean isBuyable() { + return vendingMachine.isBuyableAnyProduct(); + } + + public void buy(Order order) { + vendingMachine.buy(order); + } + + public int getAmount() { + return vendingMachine.getAmount(); + } + + public ChangeDto getChange() { + return new ChangeDto(vendingMachine.getChange()); + } +} diff --git a/src/main/java/vendingmachine/view/InputMessage.java b/src/main/java/vendingmachine/view/InputMessage.java new file mode 100644 index 000000000..99759f40d --- /dev/null +++ b/src/main/java/vendingmachine/view/InputMessage.java @@ -0,0 +1,24 @@ +package vendingmachine.view; + +public enum InputMessage { + + INPUT_MACHINE_AMOUNT("자판기가 보유하고 있는 금액을 입력해 주세요."), + INPUT_PRODUCTS("상품명과 가격, 수량을 입력해 주세요."), + INPUT_BUY_AMOUNT("투입 금액을 입력해 주세요."), + INPUT_BUY_PRODUCT_NAME("구매할 상품명을 입력해 주세요."), + ; + + private final String message; + + InputMessage(String message) { + this.message = message; + } + + public void print(Object... values) { + System.out.println(format(values)); + } + + public String format(Object... values) { + return String.format(message, values); + } +} diff --git a/src/main/java/vendingmachine/view/InputView.java b/src/main/java/vendingmachine/view/InputView.java new file mode 100644 index 000000000..c11d09acd --- /dev/null +++ b/src/main/java/vendingmachine/view/InputView.java @@ -0,0 +1,27 @@ +package vendingmachine.view; + + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + public String inputBuyAmount() { + InputMessage.INPUT_BUY_AMOUNT.print(); + return Console.readLine(); + } + + public String inputBuyProductName() { + InputMessage.INPUT_BUY_PRODUCT_NAME.print(); + return Console.readLine(); + } + + public String inputMachineAmount() { + InputMessage.INPUT_MACHINE_AMOUNT.print(); + return Console.readLine(); + } + + public String inputProducts() { + InputMessage.INPUT_PRODUCTS.print(); + return Console.readLine(); + } +} diff --git a/src/main/java/vendingmachine/view/OutputMessage.java b/src/main/java/vendingmachine/view/OutputMessage.java new file mode 100644 index 000000000..cbec7c634 --- /dev/null +++ b/src/main/java/vendingmachine/view/OutputMessage.java @@ -0,0 +1,34 @@ +package vendingmachine.view; + +import java.util.List; + +public enum OutputMessage { + + OUTPUT_MACHINE_COIN_TITLE("자판기가 보유한 동전"), + OUTPUT_MACHINE_COIN_DETAIL("%d원 - %d개"), + OUTPUT_AMOUNT("투입 금액: %d원"), + OUTPUT_CHANGE_TITLE("잔돈"), + ; + + private final String message; + + OutputMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void print(Object... values) { + System.out.println(format(values)); + } + + public String format(Object... values) { + return String.format(message, values); + } + + public String format(List values) { + return String.format(message, values.toArray()); + } +} diff --git a/src/main/java/vendingmachine/view/OutputView.java b/src/main/java/vendingmachine/view/OutputView.java new file mode 100644 index 000000000..e2265e209 --- /dev/null +++ b/src/main/java/vendingmachine/view/OutputView.java @@ -0,0 +1,37 @@ +package vendingmachine.view; + +import static vendingmachine.view.OutputMessage.OUTPUT_AMOUNT; +import static vendingmachine.view.OutputMessage.OUTPUT_CHANGE_TITLE; +import static vendingmachine.view.OutputMessage.OUTPUT_MACHINE_COIN_DETAIL; +import static vendingmachine.view.OutputMessage.OUTPUT_MACHINE_COIN_TITLE; + +import vendingmachine.dto.ChangeDto; + +public class OutputView { + + public void printErrorMessage(String message) { + System.out.println(message); + } + + public void printMachineState(ChangeDto changeDto) { + StringBuilderPrinter printer = new StringBuilderPrinter(); + printer.appendLine(OUTPUT_MACHINE_COIN_TITLE.getMessage()); + changeDto.getChange().forEach((coin, count) -> + printer.appendLine(OUTPUT_MACHINE_COIN_DETAIL.format(coin, count)) + ); + printer.print(); + } + + public void printChange(ChangeDto changeDto) { + StringBuilderPrinter printer = new StringBuilderPrinter(); + printer.appendLine(OUTPUT_CHANGE_TITLE.getMessage()); + changeDto.getChange().forEach((coin, count) -> + printer.appendLine(OUTPUT_MACHINE_COIN_DETAIL.format(coin, count)) + ); + printer.print(); + } + + public void printRemainAmount(int amount) { + OUTPUT_AMOUNT.print(amount); + } +} diff --git a/src/main/java/vendingmachine/view/StringBuilderPrinter.java b/src/main/java/vendingmachine/view/StringBuilderPrinter.java new file mode 100644 index 000000000..f88619883 --- /dev/null +++ b/src/main/java/vendingmachine/view/StringBuilderPrinter.java @@ -0,0 +1,13 @@ +package vendingmachine.view; + +public class StringBuilderPrinter { + private final StringBuilder stringBuilder = new StringBuilder(); + + public void appendLine(String line) { + stringBuilder.append(line).append('\n'); + } + + public void print() { + System.out.println(stringBuilder); + } +} diff --git a/src/test/java/vendingmachine/model/AmountTest.java b/src/test/java/vendingmachine/model/AmountTest.java new file mode 100644 index 000000000..c72ca785e --- /dev/null +++ b/src/test/java/vendingmachine/model/AmountTest.java @@ -0,0 +1,34 @@ +package vendingmachine.model; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class AmountTest { + + @Test + void 자판기_투입_금액은_10으로_나누어_떨어지지_않으면_예외를_발생한다() { + assertThatThrownBy(() -> { + Amount amount = new Amount(1); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 자판기에_남은_금액보다_많이_사용하면_예외를_발생한다() { + assertThatThrownBy(() -> { + Amount amount = new Amount(10); + amount.decrease(15); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 자판기_투입_금액을_사용한다() { + assertSimpleTest(() -> { + Amount amount = new Amount(10); + amount.decrease(5); + assertThat(amount.getValue()).isEqualTo(5); + }); + } +} diff --git a/src/test/java/vendingmachine/model/ChangeTest.java b/src/test/java/vendingmachine/model/ChangeTest.java new file mode 100644 index 000000000..4d873ba81 --- /dev/null +++ b/src/test/java/vendingmachine/model/ChangeTest.java @@ -0,0 +1,32 @@ +package vendingmachine.model; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import camp.nextstep.edu.missionutils.test.Assertions; +import org.junit.jupiter.api.Test; + +class ChangeTest { + + @Test + void 자판기의_잔돈을_반환한다() { + Assertions.assertSimpleTest(() -> { + Change change = new Change(new MachineAmount(450)); + assertThat(change.getTotalChange()).isEqualTo(450); + }); + } + + @Test + void 자판기에_금액을_입력하면_동전으로_환산한다() { + Assertions.assertSimpleTest(() -> { + Change change = new Change(new MachineAmount(450)); + assertThat(change.getChangeMoney().get(Coin.COIN_500)).isZero(); + assertThat(change.getChangeMoney().get(Coin.COIN_100)).isEqualTo(4); + assertThat(change.getChangeMoney().get(Coin.COIN_50)).isEqualTo(1); + assertThat(change.getChangeMoney().get(Coin.COIN_10)).isZero(); + }); + assertThatThrownBy(() -> { + MachineAmount machineAmount = new MachineAmount(-2); + }).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/vendingmachine/model/MachineAmountTest.java b/src/test/java/vendingmachine/model/MachineAmountTest.java new file mode 100644 index 000000000..60801e015 --- /dev/null +++ b/src/test/java/vendingmachine/model/MachineAmountTest.java @@ -0,0 +1,25 @@ +package vendingmachine.model; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class MachineAmountTest { + + @Test + void 자판기의_잔돈은_0원보다_작으면_예외가_발생한다() { + assertThatThrownBy(() -> { + MachineAmount machineAmount = new MachineAmount(-2); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 자판기의_잔돈은_10으로_나누어_떨어지지_않으면_예외가_발생한다() { + assertThatThrownBy(() -> { + Amount amount = new Amount(10); + amount.decrease(15); + }).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/vendingmachine/model/NameTest.java b/src/test/java/vendingmachine/model/NameTest.java new file mode 100644 index 000000000..643cc278b --- /dev/null +++ b/src/test/java/vendingmachine/model/NameTest.java @@ -0,0 +1,15 @@ +package vendingmachine.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class NameTest { + + @Test + void 상품_이름은_공백일_수_없다() { + assertThatThrownBy(() -> { + Name name = new Name(""); + }).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/vendingmachine/model/PriceTest.java b/src/test/java/vendingmachine/model/PriceTest.java new file mode 100644 index 000000000..86313541f --- /dev/null +++ b/src/test/java/vendingmachine/model/PriceTest.java @@ -0,0 +1,23 @@ +package vendingmachine.model; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class PriceTest { + + @Test + void 제품의_가격은_100원_이상이_아니면_예외가_발생한다() { + assertThatThrownBy(() -> { + Price price = new Price(90); + }).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 제품의_가격은_10으로_나누어_떨어지지_않으면_예외를_발생한다() { + assertThatThrownBy(() -> { + Price price = new Price(91); + }).isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/src/test/java/vendingmachine/model/ProductTest.java b/src/test/java/vendingmachine/model/ProductTest.java new file mode 100644 index 000000000..5463a7bb9 --- /dev/null +++ b/src/test/java/vendingmachine/model/ProductTest.java @@ -0,0 +1,60 @@ +package vendingmachine.model; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import camp.nextstep.edu.missionutils.test.Assertions; +import org.junit.jupiter.api.Test; + +class ProductTest { + + @Test + void 가진_금액으로_제품을_구매할수_없으면_false를_반환한다() { + assertSimpleTest(() -> { + Product product = new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + ); + assertThat(product.isBuyable(new Amount(1400))).isFalse(); + }); + } + + @Test + void 가진_금액으로_제품을_구매할수_있으면_true를_반환한다() { + assertSimpleTest(() -> { + Product product = new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + ); + assertThat(product.isBuyable(new Amount(1600))).isTrue(); + }); + } + + @Test + void 재고가_충분하지_않으면_false를_반환한다() { + assertSimpleTest(() -> { + Product product = new Product( + new Name("콜라"), + new Price(1500), + new Quantity(1) + ); + product.decreaseQuantity(); + assertThat(product.isEnoughQuantity()).isFalse(); + }); + } + + @Test + void 재고가_충분하면_true를_반환한다() { + assertSimpleTest(() -> { + Product product = new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + ); + assertThat(product.isEnoughQuantity()).isTrue(); + }); + } +} diff --git a/src/test/java/vendingmachine/model/ProductsTest.java b/src/test/java/vendingmachine/model/ProductsTest.java new file mode 100644 index 000000000..d1e0c31de --- /dev/null +++ b/src/test/java/vendingmachine/model/ProductsTest.java @@ -0,0 +1,88 @@ +package vendingmachine.model; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ProductsTest { + + private Products products; + + @BeforeEach + void setup() { + List productList = new ArrayList<>(); + productList.add(new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + )); + productList.add(new Product( + new Name("사이다"), + new Price(1000), + new Quantity(10) + )); + products = new Products(productList); + } + + @Test + void 존재하는_상품을_조회한다() { + assertSimpleTest(() -> { + assertThat(products.find(new Name("콜라"))).isPresent(); + }); + } + + @Test + void 존재하지_않는_상품을_조회한다() { + assertSimpleTest(() -> { + assertThat(products.find(new Name("콜라1"))).isNotPresent(); + }); + } + + @Test + void 가장_저렴한_상품의_가격을_조회한다() { + assertSimpleTest(() -> assertThat(products.findMinPrice()).isEqualTo(1000)); + } + + @Test + void 구매_가능한_상품이_없을_때_개수를_계산한다() { + assertSimpleTest(() -> { + List productList = new ArrayList<>(); + productList.add(new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + )); + productList.add(new Product( + new Name("사이다"), + new Price(1000), + new Quantity(10) + )); + products = new Products(productList); + assertThat(products.buyableProductCount(500)).isZero(); + }); + } + + @Test + void 구매_가능한_상품이_있을_때_개수를_계산한다() { + assertSimpleTest(() -> { + List productList = new ArrayList<>(); + productList.add(new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + )); + productList.add(new Product( + new Name("사이다"), + new Price(1000), + new Quantity(10) + )); + products = new Products(productList); + assertThat(products.buyableProductCount(1000)).isEqualTo(1); + }); + } +} diff --git a/src/test/java/vendingmachine/model/QuantityTest.java b/src/test/java/vendingmachine/model/QuantityTest.java new file mode 100644 index 000000000..2dc8d8c4b --- /dev/null +++ b/src/test/java/vendingmachine/model/QuantityTest.java @@ -0,0 +1,19 @@ +package vendingmachine.model; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; + +class QuantityTest { + + @Test + void 재고를_감소시킨다() { + assertSimpleTest(() -> { + Quantity quantity = new Quantity(1); + quantity.decrease(); + assertThat(quantity.getQuantity()).isZero(); + }); + } +} diff --git a/src/test/java/vendingmachine/model/VendingMachineTest.java b/src/test/java/vendingmachine/model/VendingMachineTest.java new file mode 100644 index 000000000..42f60dfab --- /dev/null +++ b/src/test/java/vendingmachine/model/VendingMachineTest.java @@ -0,0 +1,76 @@ +package vendingmachine.model; + +import static camp.nextstep.edu.missionutils.test.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import camp.nextstep.edu.missionutils.test.Assertions; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class VendingMachineTest { + + private VendingMachine vendingMachine; + private Products products; + + @BeforeEach + void setup() { + List productList = new ArrayList<>(); + productList.add(new Product( + new Name("콜라"), + new Price(1500), + new Quantity(10) + )); + productList.add(new Product( + new Name("사이다"), + new Price(100), + new Quantity(30) + )); + products = new Products(productList); + Change change = new Change(new MachineAmount(450)); + Amount amount = new Amount(3000); + vendingMachine = new VendingMachine(change, products, amount); + } + + @Test + void 잔돈을_반환한다_잔돈이_부족한경우_모든_잔돈을_반환한다() { + assertSimpleTest(() -> { + assertThat(vendingMachine.getChange().getTotalChange()).isEqualTo(450); + }); + } + + @Test + void 잔돈이_부족하지_않은_경우_게산하여_반환한다() { + assertSimpleTest(() -> { + IntStream.range(0, 27).forEach(i -> vendingMachine.buy(new Order(new Name("사이다")))); + assertThat(vendingMachine.getChange().getTotalChange()).isEqualTo(300); + }); + } + + @Test + void 구매_가능한_상품이_있다면_true를_반환한다() { + assertSimpleTest(() -> { + assertThat(vendingMachine.isBuyableAnyProduct()).isTrue(); + }); + } + + @Test + void 구매_가능한_상품이_없다면_false를_반환한다() { + assertSimpleTest(() -> { + IntStream.range(0, 30).forEach(i -> vendingMachine.buy(new Order(new Name("사이다")))); + assertThat(vendingMachine.isBuyableAnyProduct()).isFalse(); + }); + } + + @Test + void 상품을_구매한다() { + assertSimpleTest(() -> { + vendingMachine.buy(new Order(new Name("사이다"))); + assertThat(vendingMachine.getAmount()).isEqualTo(2900); + assertThat(products.find(new Name("사이다")).get().getQuantity()).isEqualTo(29); + }); + } +}