diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8acb57b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# build output directory +target/ + +# IntelliJ generated files +.idea/ diff --git a/pom.xml b/pom.xml index 2ca15ec..5344dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,10 +3,47 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - ch.engenius accounts 1.0-SNAPSHOT - - + + 11 + 11 + + + + + maven-surefire-plugin + 2.19.1 + + + org.junit.platform + junit-platform-surefire-provider + 1.0.3 + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + + + + + net.revelc.code.formatter + formatter-maven-plugin + 2.18.0 + + UTF-8 + + + + + + + org.junit.jupiter + junit-jupiter-api + 5.8.1 + test + + \ No newline at end of file diff --git a/src/main/java/ch/engenius/bank/Account.java b/src/main/java/ch/engenius/bank/Account.java index b9979cb..74549f6 100644 --- a/src/main/java/ch/engenius/bank/Account.java +++ b/src/main/java/ch/engenius/bank/Account.java @@ -3,29 +3,69 @@ import java.math.BigDecimal; public class Account { - private double money; + private BigDecimal money; - public void withdraw(double amount) { - if ((money - amount) < 0) { - throw new IllegalStateException("not enough credits on account"); + /* + * Creates the account with zero credit. + */ + public Account() { + this(BigDecimal.ZERO); + } + + /* + * Creates the account with given amount of credit. + * + * @param money Initial amount of credit for the account + */ + public Account(BigDecimal money) { + this.money = money; + } + + /* + * Withdraws given amount of credit from the account. + * + * @param amount Amount to withdraw + * + * @throws IllegalArgumentException If given amount is bellow or equal zero + * + * @throws IllegalStateException If given amount is larger than credits on the account + */ + public void withdraw(BigDecimal amount) { + if (amount.compareTo(BigDecimal.ZERO) < 1) { + throw new IllegalArgumentException("cannot withdraw zero or negative amount"); } - setMoney(money - amount); + synchronized (this) { + if (money.subtract(amount).compareTo(BigDecimal.ZERO) == -1) { + throw new IllegalStateException("not enough credit"); + } + + setMoney(money.subtract(amount)); + } } - public void deposit(double amount) { - setMoney(money + amount); + /* + * Deposits given amount of credit to the account. + * + * @param amount Amount to deposit + * + * @throws IllegalArgumentException If given amount is bellow or equal zero + */ + public void deposit(BigDecimal amount) { + if (amount.compareTo(BigDecimal.ZERO) < 1) { + throw new IllegalArgumentException("cannot deposit zero or negative amount"); + } + + synchronized (this) { + setMoney(money.add(amount)); + } } - public double getMoney() { + public BigDecimal getMoney() { return money; } - public void setMoney(double money) { + private void setMoney(BigDecimal money) { this.money = money; } - - public BigDecimal getMoneyAsBigDecimal() { - return BigDecimal.valueOf(money); - } } diff --git a/src/main/java/ch/engenius/bank/Bank.java b/src/main/java/ch/engenius/bank/Bank.java index 571ebc7..2eccf04 100644 --- a/src/main/java/ch/engenius/bank/Bank.java +++ b/src/main/java/ch/engenius/bank/Bank.java @@ -1,18 +1,42 @@ package ch.engenius.bank; +import java.math.BigDecimal; import java.util.HashMap; public class Bank { private HashMap accounts = new HashMap<>(); - public Account registerAccount(int accountNumber, int amount) { - Account account = new Account(); - account.setMoney(amount); + /* + * Creates an account with specified account number and initial amount of credits. + * + * @param accountNumber Number of the account + * + * @param amount Initial credit for the account + * + * @thorws IllegalStateException If account with given number has already been registered + */ + public Account registerAccount(int accountNumber, BigDecimal amount) { + if (accounts.containsKey(accountNumber)) { + throw new IllegalStateException("account already exists"); + } + + Account account = new Account(amount); accounts.put(accountNumber, account); return account; } - public Account getAccount( int number) { - return accounts.get(number); + /* + * Retrieve account by its number + * + * @param accountNumber Number of the account to retrieve + * + * @throws IllegalStateException If account with given number is not registered + */ + public Account getAccount(int accountNumber) { + if (!accounts.containsKey(accountNumber)) { + throw new IllegalStateException("account does not exist"); + } + + return accounts.get(accountNumber); } } diff --git a/src/main/java/ch/engenius/bank/BankRunner.java b/src/main/java/ch/engenius/bank/BankRunner.java index 10b30fd..093e857 100644 --- a/src/main/java/ch/engenius/bank/BankRunner.java +++ b/src/main/java/ch/engenius/bank/BankRunner.java @@ -1,6 +1,7 @@ package ch.engenius.bank; import java.math.BigDecimal; +import java.util.List; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -14,58 +15,57 @@ public class BankRunner { private final Random random = new Random(43); private final Bank bank = new Bank(); - public static void main(String[] args) { BankRunner runner = new BankRunner(); int accounts = 100; - int defaultDeposit = 1000; - int iterations = 10000; + int defaultDeposit = 1000; + int iterations = 10000; runner.registerAccounts(accounts, defaultDeposit); - runner.sanityCheck(accounts, accounts*defaultDeposit); + runner.sanityCheck(accounts, accounts * defaultDeposit); runner.runBank(iterations, accounts); - runner.sanityCheck(accounts, accounts*defaultDeposit); - + runner.sanityCheck(accounts, accounts * defaultDeposit); } private void runBank(int iterations, int maxAccount) { - for (int i =0; i< iterations; i++ ) { - executor.submit( ()-> runRandomOperation(maxAccount)); + for (int i = 0; i < iterations; i++) { + executor.submit(() -> runRandomOperation(maxAccount)); } try { executor.shutdown(); - executor.awaitTermination(100,TimeUnit.SECONDS); + executor.awaitTermination(1, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); + } finally { + if (!executor.isTerminated()) { + List operations = executor.shutdownNow(); + System.err.println("# of operations that failed to complete: " + operations.size()); + } } } private void runRandomOperation(int maxAccount) { - double transfer = random.nextDouble()*100.0; + BigDecimal transfer = BigDecimal.valueOf(random.nextDouble()).multiply(BigDecimal.valueOf(100)); int accountInNumber = random.nextInt(maxAccount); int accountOutNumber = random.nextInt(maxAccount); - Account accIn =bank.getAccount(accountInNumber); - Account accOut =bank.getAccount(accountOutNumber); - accIn.deposit(transfer); + Account accIn = bank.getAccount(accountInNumber); + Account accOut = bank.getAccount(accountOutNumber); accOut.withdraw(transfer); + accIn.deposit(transfer); } - private void registerAccounts(int number, int defaultMoney) { - for ( int i = 0; i < number; i++) { - bank.registerAccount(i, defaultMoney); + private void registerAccounts(int number, int defaultMoney) { + for (int i = 0; i < number; i++) { + bank.registerAccount(i, BigDecimal.valueOf(defaultMoney)); } } - private void sanityCheck( int accountMaxNumber, int totalExpectedMoney) { - BigDecimal sum = IntStream.range(0, accountMaxNumber) - .mapToObj( bank::getAccount) - .map ( Account::getMoneyAsBigDecimal) - .reduce( BigDecimal.ZERO, BigDecimal::add); + private void sanityCheck(int accountMaxNumber, int totalExpectedMoney) { + BigDecimal sum = IntStream.range(0, accountMaxNumber).mapToObj(bank::getAccount).map(Account::getMoney) + .reduce(BigDecimal.ZERO, BigDecimal::add); - if ( sum.intValue() != totalExpectedMoney) { - throw new IllegalStateException("we got "+ sum + " != " + totalExpectedMoney +" (expected)"); + if (sum.intValue() != totalExpectedMoney) { + throw new IllegalStateException("we got " + sum + " != " + totalExpectedMoney + " (expected)"); } System.out.println("sanity check OK"); } - - } diff --git a/src/test/java/ch/engenius/bank/AccountTest.java b/src/test/java/ch/engenius/bank/AccountTest.java new file mode 100644 index 0000000..86d0c80 --- /dev/null +++ b/src/test/java/ch/engenius/bank/AccountTest.java @@ -0,0 +1,80 @@ +package ch.engenius.bank; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import java.math.BigDecimal; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class AccountTest { + + private Account account; + private final BigDecimal initialMoney = BigDecimal.valueOf(123.456); + + @BeforeEach + public void setUp() { + account = new Account(initialMoney); + } + + @Test + public void shouldDepositMoney() { + final BigDecimal depositAmount = BigDecimal.valueOf(11); + + account.deposit(depositAmount); + + assertEquals(initialMoney.add(depositAmount), account.getMoney()); + } + + @Test + public void shouldWithdrawMoney() { + final BigDecimal withdrawalAmount = BigDecimal.valueOf(22); + + account.withdraw(withdrawalAmount); + + assertEquals(initialMoney.subtract(withdrawalAmount), account.getMoney()); + } + + @Test + public void shouldFailToDepositNothing() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> account.deposit(BigDecimal.ZERO)); + assertEquals("cannot deposit zero or negative amount", exception.getMessage()); + } + + @Test + public void shouldFailToDepositNegativeAmount() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> account.deposit(BigDecimal.valueOf(-100))); + assertEquals("cannot deposit zero or negative amount", exception.getMessage()); + } + + @Test + public void shouldWithdrawAllMoney() { + account.withdraw(initialMoney); + + assertEquals(BigDecimal.ZERO, account.getMoney().setScale(0)); + } + + @Test + public void shouldFailToWithdrawNothing() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> account.withdraw(BigDecimal.ZERO)); + assertEquals("cannot withdraw zero or negative amount", exception.getMessage()); + } + + @Test + public void shouldFailToWithdrawNegativeAmount() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> account.withdraw(BigDecimal.valueOf(-100))); + assertEquals("cannot withdraw zero or negative amount", exception.getMessage()); + } + + @Test + public void shouldFailToWithdrawWhenMissingMoney() { + final BigDecimal withdrawalAmount = BigDecimal.valueOf(321); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> account.withdraw(BigDecimal.valueOf(321))); + assertEquals("not enough credit", exception.getMessage()); + } +} diff --git a/src/test/java/ch/engenius/bank/BankTest.java b/src/test/java/ch/engenius/bank/BankTest.java new file mode 100644 index 0000000..d2d8c7d --- /dev/null +++ b/src/test/java/ch/engenius/bank/BankTest.java @@ -0,0 +1,48 @@ +package ch.engenius.bank; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import java.math.BigDecimal; +import static org.junit.jupiter.api.Assertions.*; + +public class BankTest { + + private static Bank bank; + private final int accountNumber = 3; + private final BigDecimal amount = BigDecimal.valueOf(123.456); + + @BeforeAll + public static void setUp() { + bank = new Bank(); + } + + @Test + @Order(1) + public void shouldRegisterAccount() { + bank.registerAccount(accountNumber, amount); + + Account account = bank.getAccount(accountNumber); + assertNotNull(account); + } + + @Test + public void shouldFailToRegisterAccountIfAlreadyExists() { + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> bank.registerAccount(accountNumber, BigDecimal.ZERO)); + assertEquals("account already exists", exception.getMessage()); + } + + @Test + public void shouldRegisterAccountWithEnoughMoney() { + Account account = bank.getAccount(accountNumber); + + assertEquals(amount, account.getMoney()); + } + + @Test + public void shouldFailToGetNonExistingAccount() { + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> bank.getAccount(550)); + assertEquals("account does not exist", exception.getMessage()); + } +}