diff --git a/src/paystation/domain/PayStation.java b/src/paystation/domain/PayStation.java index bc74e4d..ad074ae 100644 --- a/src/paystation/domain/PayStation.java +++ b/src/paystation/domain/PayStation.java @@ -3,10 +3,10 @@ * * Responsibilities: * - * 1) Accept payment; - * 2) Calculate parking time based on payment; - * 3) Know earning, parking time bought; - * 4) Issue receipts; + * 1) Accept payment; + * 2) Calculate parking time based on payment; + * 3) Know earning, parking time bought; + * 4) Issue receipts; * 5) Handle buy and cancel events. * * This source code is from the book "Flexible, Reliable Software: Using @@ -19,6 +19,8 @@ */ package paystation.domain; +import java.util.Map; + public interface PayStation { /** @@ -28,7 +30,7 @@ public interface PayStation { * is, a quarter is coinValue=25, etc. * @throws IllegalCoinException in case coinValue is not a valid coin value */ - public void addPayment(int coinValue) throws IllegalCoinException; + void addPayment(int coinValue) throws IllegalCoinException; /** * Read the machine's display. The display shows a numerical description of @@ -36,7 +38,7 @@ public interface PayStation { * * @return the number to display on the pay station display */ - public int readDisplay(); + int readDisplay(); /** * Buy parking time. Terminate the ongoing transaction and return a parking @@ -44,10 +46,30 @@ public interface PayStation { * * @return a valid parking receipt object. */ - public Receipt buy(); + Receipt buy(); + + /** + * Cancel the present transaction. Resets the paystation for a new transaction. + * @return A Map defining the coins returned to the user. + * The key is the coin type and the associated value is the number of these coins that are + * returned. + * The Map object is never null even if no coins are returned. + * The Map will only contain only keys for coins to be returned. + * The Map will be cleared after a cancel or buy. + */ + Map cancel(); /** - * Cancel the present transaction. Resets the machine for a new transaction. + * Return the total amount of money collected since the last call and empties + * it, setting the total to zero. Note: Money only collected after call to Buy */ - public void cancel(); + int empty(); + + /** + * Dummy header for setting current rate strategy of paystation instance. + * Resolve clashes by overrulling this change. Header is to make tests more coherent. + * @param newStrategy enum indicating selected strategy. + */ + void setRateStrategy(Strategy newStrategy); + } diff --git a/src/paystation/domain/PayStationImpl.java b/src/paystation/domain/PayStationImpl.java index 979f379..cf0a31e 100644 --- a/src/paystation/domain/PayStationImpl.java +++ b/src/paystation/domain/PayStationImpl.java @@ -1,14 +1,17 @@ package paystation.domain; +import java.util.HashMap; +import java.util.Map; + /** * Implementation of the pay station. * * Responsibilities: * - * 1) Accept payment; - * 2) Calculate parking time based on payment; - * 3) Know earning, parking time bought; - * 4) Issue receipts; + * 1) Accept payment; + * 2) Calculate parking time based on payment; + * 3) Know earning, parking time bought; + * 4) Issue receipts; * 5) Handle buy and cancel events. * * This source code is from the book "Flexible, Reliable Software: Using @@ -20,9 +23,18 @@ * purposes. For any commercial use, see http://www.baerbak.com/ */ public class PayStationImpl implements PayStation { - + private int insertedSoFar; + private HashMap insertedMap; /* map recording coin types and quantities */ private int timeBought; + private int totalCollected; /* Stores the total amount (in cents) collected by paystation */ + + public PayStationImpl() { + insertedSoFar = 0; + insertedMap = new HashMap<>(); + timeBought = 0; + totalCollected = 0; + } @Override public void addPayment(int coinValue) @@ -35,6 +47,10 @@ public void addPayment(int coinValue) throw new IllegalCoinException("Invalid coin: " + coinValue); } insertedSoFar += coinValue; + if (insertedMap.containsKey(coinValue)) + insertedMap.put(coinValue, insertedMap.get(coinValue) + 1); + else + insertedMap.put(coinValue, 1); timeBought = insertedSoFar / 5 * 2; } @@ -46,16 +62,33 @@ public int readDisplay() { @Override public Receipt buy() { Receipt r = new ReceiptImpl(timeBought); + totalCollected += insertedSoFar; /* Each buy action accrues more total money collected */ reset(); return r; } @Override - public void cancel() { + public Map cancel() { + HashMap returnedMap = (HashMap) insertedMap.clone(); reset(); + return returnedMap; } - + private void reset() { timeBought = insertedSoFar = 0; + insertedMap.clear(); + } + + @Override + /* Returns total money collected in the paystation since the last call to empty, and resets total */ + public int empty() { + int retval = totalCollected; + totalCollected = 0; + return retval; + } + + @Override + public void setRateStrategy(Strategy newStrategy) { + System.err.println("set rate strategy unimplemented"); } } diff --git a/src/paystation/domain/RateStrategy.java b/src/paystation/domain/RateStrategy.java new file mode 100644 index 0000000..eb081e1 --- /dev/null +++ b/src/paystation/domain/RateStrategy.java @@ -0,0 +1,6 @@ +package paystation.domain; + +public abstract class RateStrategy { + + public abstract int calculateTime(int payment); +} diff --git a/src/paystation/domain/Receipt.java b/src/paystation/domain/Receipt.java index 8925d20..7ee3518 100644 --- a/src/paystation/domain/Receipt.java +++ b/src/paystation/domain/Receipt.java @@ -20,5 +20,5 @@ public interface Receipt { * * @return number of minutes parking time */ - public int value(); + int value(); } diff --git a/src/paystation/domain/Strategy.java b/src/paystation/domain/Strategy.java new file mode 100644 index 0000000..8e395c1 --- /dev/null +++ b/src/paystation/domain/Strategy.java @@ -0,0 +1,7 @@ +package paystation.domain; + +/** + * Convenience enum for setting rate strategy in dummy setRateStrategy method. + * Resolve for merge by overrulling this commit and accepting the work of whoever took on this problem. + */ +public enum Strategy {LINEAR, PROGRESSIVE} \ No newline at end of file diff --git a/test/paystation/domain/LinearRateStrategyTests.java b/test/paystation/domain/LinearRateStrategyTests.java new file mode 100644 index 0000000..ba0b7e2 --- /dev/null +++ b/test/paystation/domain/LinearRateStrategyTests.java @@ -0,0 +1,85 @@ + +package paystation.domain; + +import org.junit.Test; +import static org.junit.Assert.*; +import static paystation.domain.Strategy.LINEAR; + +import org.junit.Before; + +public class LinearRateStrategyTests { + /** + * Test Cases for behavior of PayStation Using LinearRateStrategyTests + */ + PayStationImpl ps; + + /** + * Tests existence of linear rate strategy in setup + */ + @Before + public void setup() { + ps = new PayStationImpl(); + ps.setRateStrategy(LINEAR); + } + +// @Test +// public void existsClassLinearRateStrategy() { +// rs = new LinearRateStrategy(); +// } + // Don't use @Test -- test in setup + + /** + * Assert that meter starts with no time on it + */ + @Test + public void shouldHaveNoTimeAfterNoMoney(){ + assertEquals("Time should be zero until payment", 0, ps.readDisplay()); + } + + /** + * Entering 5 cents should make the display report 2 minutes parking time. + */ + @Test + public void shouldDisplay2MinFor5Cents() + throws IllegalCoinException { + ps.addPayment(5); + assertEquals("Should display 2 min for 5 cents", + 2, ps.readDisplay()); + } + + /** + * Entering 25 cents should make the display report 10 minutes parking time. + */ + @Test + public void shouldDisplay10MinFor25Cents() throws IllegalCoinException { + ps.addPayment(25); + assertEquals("Should display 10 min for 25 cents", + 10, ps.readDisplay()); + } + + /** + * Check that linear price model continued into second hour. + * + * Checks divergence between Alpha and Beta. + * Uses whole number of minutes to avoid complication with testing whether time is double or int. + */ + @Test + public void shouldDisplay70Minfor175Cents() throws IllegalCoinException { + for (int i = 0; i < 7; i++) { + ps.addPayment(25); + } + assertEquals("Should display 70 minutes for $1.75.", 70, ps.readDisplay()); + } + + /** + * Check that linear price model continues into third hour. + */ + @Test + public void shouldDisplay130MinFor310Cents() throws IllegalCoinException { + for(int i = 0; i < 13; i++) { + ps.addPayment(25); + } + assertEquals("Should display 130 minutes for $3.25", 130, ps.readDisplay()); + } + +} diff --git a/test/paystation/domain/PayStationImplTest.java b/test/paystation/domain/PayStationImplTest.java index 747dec5..b10cd64 100644 --- a/test/paystation/domain/PayStationImplTest.java +++ b/test/paystation/domain/PayStationImplTest.java @@ -15,6 +15,8 @@ import static org.junit.Assert.*; import org.junit.Before; +import java.util.HashMap; + public class PayStationImplTest { PayStation ps; @@ -128,8 +130,7 @@ public void shouldClearAfterBuy() * Verify that cancel clears the pay station */ @Test - public void shouldClearAfterCancel() - throws IllegalCoinException { + public void shouldClearAfterCancel() throws IllegalCoinException { ps.addPayment(10); ps.cancel(); assertEquals("Cancel should clear display", @@ -138,4 +139,111 @@ public void shouldClearAfterCancel() assertEquals("Insert after cancel should work", 10, ps.readDisplay()); } + + /** + * Call to cancel should clear the map. + */ + @Test + public void shouldClearMapAfterCancel() throws IllegalCoinException { + ps.addPayment(10); + ps.cancel(); + HashMap emptyMap = (HashMap) ps.cancel(); + assertTrue("Cancel should clear map", emptyMap.isEmpty()); + } + + /** + * Call to cancel should return a map containing one coin entered. + */ + @Test + public void cancelShouldReturnMapContainingOneCoin() throws IllegalCoinException { + ps.addPayment(25); + HashMap returnedMap = (HashMap) ps.cancel(); + assertEquals("Cancel should return a map containing one coin entered", + 1, returnedMap.size()); + assertEquals("Cancel should return a map containing one coin entered", + 1, (int) returnedMap.get(25)); + } + + /** + * Call to cancel should return a map containing a mixture of coins entered. + */ + @Test + public void cancelShouldReturnMapContainingMixtureOfCoins() throws IllegalCoinException { + ps.addPayment(25); + ps.addPayment(5); + ps.addPayment(10); + ps.addPayment(5); + HashMap returnedMap = (HashMap) ps.cancel(); + assertEquals("Cancel should return a map containing a mixture of coins entered", + 3, returnedMap.size()); + assertEquals("Cancel should return a map containing a mixture of coins entered", + 1, (int) returnedMap.get(25)); + assertEquals("Cancel should return a map containing a mixture of coins entered", + 1, (int) returnedMap.get(10)); + assertEquals("Cancel should return a map containing a mixture of coins entered", + 2, (int) returnedMap.get(5)); + } + + /** + * Call to cancel should return a map that does not contain a key for a coin not entered. + */ + @Test + public void cancelShouldReturnMapNotContainingKeyNotEntered() throws IllegalCoinException { + assertFalse("Returned map should not create key for a coin not entered", + ps.cancel().containsKey(25)); + ps.addPayment(25); + assertFalse("Returned map should not create key for a coin not entered", + ps.cancel().containsKey(5)); + } + + /** + * Call to empty returns the total amount entered. + */ + @Test + public void emptyReturnsTotalEntered() throws IllegalCoinException { + ps.addPayment(10); + ps.buy(); + assertEquals("Empty should show total amount entered", + 10, ps.empty()); + } + + /** + * Call to empty returns the total amount entered. + */ + @Test + public void cancelDoesNotAddFromEmpty() throws IllegalCoinException { + ps.addPayment(25); + ps.buy(); + ps.addPayment(25); + ps.cancel(); + assertEquals("Empty should not include cancelled amount entered", + 25, ps.empty()); + } + + /** + * Call to empty resets totalCollected to 0 + */ + @Test + public void emptyResetsTotalCollected() throws IllegalCoinException { + ps.addPayment(25); + ps.buy(); + ps.addPayment(25); + ps.buy(); + assertEquals("Empty should return total collected.", + 50, ps.empty()); + assertEquals("Previous empty should have reset total to zero.", + 0, ps.empty()); + } + + /** + * Call to buy should clear the map. + */ + @Test + public void shouldClearMapAfterBuy() throws IllegalCoinException { + ps.addPayment(25); + ps.buy(); + HashMap emptyMap = (HashMap) ps.cancel(); + assertTrue("Buy should clear map.", emptyMap.isEmpty()); + } + } diff --git a/test/paystation/domain/ProgressiveRateStrategyTests.java b/test/paystation/domain/ProgressiveRateStrategyTests.java new file mode 100644 index 0000000..9bfeb97 --- /dev/null +++ b/test/paystation/domain/ProgressiveRateStrategyTests.java @@ -0,0 +1,83 @@ + +package paystation.domain; + +import org.junit.Test; +import static org.junit.Assert.*; +import static paystation.domain.Strategy.PROGRESSIVE; + +import org.junit.Before; + +public class ProgressiveRateStrategyTests { + /** + * Test Cases for behavior of PayStation Using LinearRateStrategyTests + */ + PayStationImpl ps; + + /** + * Tests existence of linear rate strategy in setup + */ + @Before + public void setup() { + ps = new PayStationImpl(); + ps.setRateStrategy(PROGRESSIVE); + } + + /** + * Assert that meter starts with no time on it + */ + @Test + public void shouldHaveNoTimeAfterNoMoney(){ + assertEquals("Time should be zero until payment", 0, ps.readDisplay()); + } + + /** + * Entering 5 cents should make the display report 2 minutes parking time. + */ + @Test + public void shouldDisplay2MinFor5Cents() + throws IllegalCoinException { + ps.addPayment(5); + assertEquals("Should display 2 min for 5 cents", + 2, ps.readDisplay()); + } + + /** + * Entering 25 cents should make the display report 10 minutes parking time. + */ + @Test + public void shouldDisplay10MinFor25Cents() throws IllegalCoinException { + ps.addPayment(25); + assertEquals("Should display 10 min for 25 cents", + 10, ps.readDisplay()); + } + + /** + * Check that progressive price model begins in second hour. + * + * Checks divergence between Alpha and Beta. + * Uses whole number of minutes to avoid complication with testing whether time is double or int. + */ + @Test + public void shouldDisplay63Minfor160Cents() throws IllegalCoinException { + for (int i = 0; i < 6; i++) { + ps.addPayment(25); + } + ps.addPayment(10); + assertEquals("Should display 63 minutes for $1.60.", 63, ps.readDisplay()); + } + + /** + * Check that progressive price model implements third hour pricing. + * + * Uses whole numbers. + */ + @Test + public void shouldDisplay121MinFor355Cents() throws IllegalCoinException { + for(int i = 0; i < 14; i++) { + ps.addPayment(25); + } // Pay $3.50 for first two hours. + ps.addPayment(5); + assertEquals("Should display 121 minutes for $3.55", 121, ps.readDisplay()); + } + +}