From 23e33a7753ebb26ab4c6d20d70b42785ab921226 Mon Sep 17 00:00:00 2001 From: John King Date: Mon, 15 Jul 2024 21:33:24 +0200 Subject: [PATCH 01/11] WIP - assorted improvements to readability. Introduces ZonedDateTime --- README.md | 25 ++++++------- .../java/tw/joi/energy/config/TestData.java | 2 +- .../joi/energy/domain/ElectricityReading.java | 5 +-- .../java/tw/joi/energy/domain/PricePlan.java | 19 +++------- .../repository/PricePlanRepository.java | 6 ++-- .../tw/joi/energy/domain/PricePlanTest.java | 35 +++++++++++-------- .../tw/joi/energy/domain/SmartMeterTest.java | 7 ++-- .../repository/PricePlanRepositoryTest.java | 2 ++ .../repository/SmartMeterRepositoryTest.java | 3 ++ .../service/MeterReadingManagerTest.java | 34 +++++++++++------- .../service/PricePlanComparatorTest.java | 31 ++++++++-------- 11 files changed, 93 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index af5c7f6c..cccddf01 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ as well as the logic to recommend the cheapest price plan for a particular house >Unfortunately, as the codebase has evolved, it has gathered tech debt in the form of a number of code smells and some questionable design decisions. Our goal for the upcoming exercise would be to deliver value by implementing a new -feature using _Test Driven Development_ (TDD), while refactoring away the code smells we see. +feature using _[Test Driven Development](https://martinfowler.com/bliki/TestDrivenDevelopment.html)_ (TDD), while refactoring away the code smells we see. > >In preparation for this, please take some time to go through the code and identify any improvements, big or small, that would improve its maintainability, testability, and design. @@ -46,7 +46,9 @@ The project requires [Java 21](https://adoptium.net/) or higher. ## Useful commands -Compile the project, run the tests and creates an executable JAR file +### Build the project + +Compile the project, run the tests and creates an executable JAR file: ```console ./gradlew build @@ -67,11 +69,10 @@ You can run it with the following command: ## API Documentation -The codebase contains two service classes, _MeterReadingManager_ and _PricePlanComparator_, that serve as entry points to -the implemented features. +The codebase contains two service classes, `MeterReadingManager` and `PricePlanComparator` that serve as entry points to the implemented features. ### MeterReadingManager -Provides methods to store and fetch the energy consumption readings from a given Smart Meter +Provides methods to store and fetch the energy consumption readings from a given Smart Meter. > #### _public void_ storeReadings(_String smartMeterId, List electricityReadings_) Stores the provided _ElectricityReading_ collection in the indicated _SmartMeter_. If no @@ -91,13 +92,13 @@ An _ElectricityReading_ record consists of the following fields: Example readings -| Date (`GMT`) | Epoch timestamp | Reading (kWh) | -|-------------------|----------------:|--------------:| -| `2020-11-29 8:00` | 1606636800 | 600.05 | -| `2020-11-29 9:00` | 1606640400 | 602.06 | -| `2020-11-30 7:30` | 1606721400 | 610.09 | -| `2020-12-01 8:30` | 1606811400 | 627.12 | -| `2020-12-02 8:30` | 1606897800 | 635.14 | +| Date (`GMT`) | Epoch timestamp (seconds) | Reading (kWh) | +|-------------------|--------------------------:|--------------:| +| `2020-11-29 8:00` | 1606636800 | 600.05 | +| `2020-11-29 9:00` | 1606640400 | 602.06 | +| `2020-11-30 7:30` | 1606721400 | 610.09 | +| `2020-12-01 8:30` | 1606811400 | 627.12 | +| `2020-12-02 8:30` | 1606897800 | 635.14 | Thee above table shows some readings sampled by a smart meter over multiple days. Note that since the smart meter is reporting the total energy consumed up to that point in time, a reading's value will always be higher or the same as diff --git a/src/main/java/tw/joi/energy/config/TestData.java b/src/main/java/tw/joi/energy/config/TestData.java index fd70503b..51401c04 100644 --- a/src/main/java/tw/joi/energy/config/TestData.java +++ b/src/main/java/tw/joi/energy/config/TestData.java @@ -14,7 +14,7 @@ public final class TestData { private static final PricePlan MOST_EVIL_PRICE_PLAN = new PricePlan("price-plan-0", "Dr Evil's Dark Energy", BigDecimal.TEN, emptyList()); private static final PricePlan RENEWABLES_PRICE_PLAN = - new PricePlan("price-plan-1", "The Green Eco", BigDecimal.valueOf(2), null); + new PricePlan("price-plan-1", "The Green Eco", BigDecimal.valueOf(2), emptyList()); private static final PricePlan STANDARD_PRICE_PLAN = new PricePlan("price-plan-2", "Power for Everyone", BigDecimal.ONE, emptyList()); diff --git a/src/main/java/tw/joi/energy/domain/ElectricityReading.java b/src/main/java/tw/joi/energy/domain/ElectricityReading.java index 34ff26b5..1af38519 100644 --- a/src/main/java/tw/joi/energy/domain/ElectricityReading.java +++ b/src/main/java/tw/joi/energy/domain/ElectricityReading.java @@ -4,6 +4,7 @@ import java.time.Instant; /** - * @param reading kWh + * @param time point in time + * @param readingInKwH energy consumed in total to this point in time in kWh */ -public record ElectricityReading(Instant time, BigDecimal reading) {} +public record ElectricityReading(Instant time, BigDecimal readingInKwH) {} diff --git a/src/main/java/tw/joi/energy/domain/PricePlan.java b/src/main/java/tw/joi/energy/domain/PricePlan.java index e195cdfc..39c28b8f 100644 --- a/src/main/java/tw/joi/energy/domain/PricePlan.java +++ b/src/main/java/tw/joi/energy/domain/PricePlan.java @@ -1,21 +1,20 @@ package tw.joi.energy.domain; -import java.io.*; import java.math.BigDecimal; -import java.text.*; import java.time.DayOfWeek; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.List; public class PricePlan { - private String energySupplier; - private String planName; + private final String energySupplier; + private final String planName; private final BigDecimal unitRate; // unit price per kWh private final List peakTimeMultipliers; public PricePlan( - String planName, String energySupplier, BigDecimal unitRate, List peakTimeMultipliers) { + String planName, String energySupplier, BigDecimal unitRate, List peakTimeMultipliers) { this.planName = planName; this.energySupplier = energySupplier; this.unitRate = unitRate; @@ -26,23 +25,15 @@ public String getEnergySupplier() { return energySupplier; } - public void setEnergySupplier(String supplierName) { - this.energySupplier = supplierName; - } - public String getPlanName() { return planName; } - public void setPlanName(String name) { - this.planName = name; - } - public BigDecimal getUnitRate() { return unitRate; } - public BigDecimal getPrice(LocalDateTime dateTime) { + public BigDecimal getPrice(ZonedDateTime dateTime) { return unitRate; } diff --git a/src/main/java/tw/joi/energy/repository/PricePlanRepository.java b/src/main/java/tw/joi/energy/repository/PricePlanRepository.java index db256c53..5195c901 100644 --- a/src/main/java/tw/joi/energy/repository/PricePlanRepository.java +++ b/src/main/java/tw/joi/energy/repository/PricePlanRepository.java @@ -5,10 +5,10 @@ import java.math.BigDecimal; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.regex.*; import tw.joi.energy.domain.ElectricityReading; import tw.joi.energy.domain.PricePlan; import tw.joi.energy.domain.SmartMeter; @@ -34,8 +34,8 @@ private BigDecimal calculateCost(Collection electricityReadi .max(comparing(ElectricityReading::time)) .get(); - BigDecimal energyConsumed = latest.reading().subtract(oldest.reading()); - return energyConsumed.multiply(pricePlan.getPrice(LocalDateTime.now())); + BigDecimal energyConsumed = latest.readingInKwH().subtract(oldest.readingInKwH()); + return energyConsumed.multiply(pricePlan.getPrice(ZonedDateTime.now())); } public List getAllPricePlans() { diff --git a/src/test/java/tw/joi/energy/domain/PricePlanTest.java b/src/test/java/tw/joi/energy/domain/PricePlanTest.java index c49e5b3c..236ec32c 100644 --- a/src/test/java/tw/joi/energy/domain/PricePlanTest.java +++ b/src/test/java/tw/joi/energy/domain/PricePlanTest.java @@ -1,39 +1,46 @@ package tw.joi.energy.domain; -import org.assertj.core.data.Percentage; -import org.junit.jupiter.api.Test; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.Month; - -import static java.util.Collections.emptyList; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.TimeZone; +import org.assertj.core.data.Percentage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; public class PricePlanTest { @Test + @DisplayName("Get energy supplier should return supplier if not null") public void get_energy_supplier_should_return_the_energy_supplier_given_supplier_is_existent() { - PricePlan pricePlan = new PricePlan(null, "Energy Supplier Name", null, null); + PricePlan pricePlan = new PricePlan("Test Plan Name", "Energy Supplier Name", BigDecimal.ONE, emptyList()); assertThat(pricePlan.getEnergySupplier()).isEqualTo("Energy Supplier Name"); } @Test - public void get_price_should_return_the_base_price_given_an_ordinary_date_time() throws Exception { - LocalDateTime normalDateTime = LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0); - PricePlan pricePlan = new PricePlan(null, null, BigDecimal.ONE, emptyList()); + @DisplayName("Get price should return price given non-peak date and time") + public void get_price_should_return_the_base_price_given_an_ordinary_date_time() { + ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), + ZoneId.of("GMT")); + // the price plan has no peak days.... + PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE, emptyList()); - BigDecimal price = pricePlan.getPrice(normalDateTime); + BigDecimal price = pricePlan.getPrice(nonPeakDateTime); - assertThat(price).isCloseTo(BigDecimal.ONE, Percentage.withPercentage(1)); + assertThat(price).isEqualTo(BigDecimal.ONE); } @Test + @DisplayName("Get unit rate should return unit rate if no null") public void get_unit_rate_should_return_unit_rate_given_unit_rate_is_present() { - PricePlan pricePlan = new PricePlan(null, null, BigDecimal.TWO, null); - pricePlan.setPlanName("test-price-plan"); - pricePlan.setEnergySupplier("test-energy-supplier"); + PricePlan pricePlan = new PricePlan("test-price-plan", "test-energy-supplier", BigDecimal.TWO, emptyList()); BigDecimal rate = pricePlan.getUnitRate(); diff --git a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java index 925c2566..5c941a8a 100644 --- a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java +++ b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java @@ -1,14 +1,15 @@ package tw.joi.energy.domain; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; class SmartMeterTest { @Test + @DisplayName("Price plan should be null if none has been supplied") void price_plan_id_should_be_null_given_no_price_plan_has_been_provided() { var smartMeter = new SmartMeter(null, Collections.emptyList()); diff --git a/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java b/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java index 393deaa6..77edc42c 100644 --- a/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java +++ b/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java @@ -3,11 +3,13 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class PricePlanRepositoryTest { @Test + @DisplayName("Should return empty list of plans if none available") void should_return_empty_list_when_get_all_price_plans_given_no_price_plans_available() { var repository = new PricePlanRepository(emptyList()); diff --git a/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java b/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java index 5ee7991d..3f1fe369 100644 --- a/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java +++ b/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java @@ -3,12 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.List; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import tw.joi.energy.domain.SmartMeter; class SmartMeterRepositoryTest { @Test + @DisplayName("findById should return empty option when searching for non-existent id") void should_return_empty_smart_meter_when_find_by_id_given_a_non_existent_id() { var repository = new SmartMeterRepository(); @@ -16,6 +18,7 @@ void should_return_empty_smart_meter_when_find_by_id_given_a_non_existent_id() { } @Test + @DisplayName("findById should return appropriate smart meter if parameter exists in repository") void should_return_smart_meters_when_find_by_id_given_existent_smart_meter_ids() { var repository = new SmartMeterRepository(); SmartMeter smartMeter0 = new SmartMeter(null, List.of()); diff --git a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java index 84a98bb1..517b0225 100644 --- a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java +++ b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java @@ -1,18 +1,18 @@ package tw.joi.energy.service; -import org.junit.jupiter.api.Test; -import tw.joi.energy.domain.ElectricityReading; -import tw.joi.energy.repository.SmartMeterRepository; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static tw.joi.energy.fixture.ElectricityReadingFixture.createReading; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import tw.joi.energy.domain.ElectricityReading; +import tw.joi.energy.repository.SmartMeterRepository; + public class MeterReadingManagerTest { private static final String SMART_METER_ID = "10101010"; @@ -20,6 +20,7 @@ public class MeterReadingManagerTest { private final MeterReadingManager meterReadingManager = new MeterReadingManager(smartMeterRepository); @Test + @DisplayName("storeReadings should throw exception given a null meterId") public void store_readings_should_throw_exception_given_meter_id_is_null() { assertThatThrownBy(() -> meterReadingManager.storeReadings(null, emptyList())) .isInstanceOf(IllegalArgumentException.class) @@ -27,6 +28,7 @@ public void store_readings_should_throw_exception_given_meter_id_is_null() { } @Test + @DisplayName("storeReadings should throw exception given meterId is empty string") public void store_readings_should_throw_exception_given_meter_id_is_empty() { assertThatThrownBy(() -> meterReadingManager.storeReadings("", emptyList())) .isInstanceOf(IllegalArgumentException.class) @@ -34,6 +36,7 @@ public void store_readings_should_throw_exception_given_meter_id_is_empty() { } @Test + @DisplayName("storeReadings should throw exception given readings is null") public void store_readings_should_throw_exception_given_readings_is_null() { assertThatThrownBy(() -> meterReadingManager.storeReadings(SMART_METER_ID, null)) .isInstanceOf(IllegalArgumentException.class) @@ -41,6 +44,7 @@ public void store_readings_should_throw_exception_given_readings_is_null() { } @Test + @DisplayName("storeReadings should throw exception given readings is emtpy list") public void store_readings_should_throw_exception_given_readings_is_empty() { assertThatThrownBy(() -> meterReadingManager.storeReadings(SMART_METER_ID, emptyList())) .isInstanceOf(IllegalArgumentException.class) @@ -48,7 +52,8 @@ public void store_readings_should_throw_exception_given_readings_is_empty() { } @Test - public void store_readings_should_success_given_meter_readings() { + @DisplayName("storeReadings should succeed given non-empty list of readings") + public void store_readings_should_succeed_given_meter_readings() { var readingsToStore = List.of(createReading(LocalDate.now(), 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, readingsToStore); @@ -58,7 +63,8 @@ public void store_readings_should_success_given_meter_readings() { } @Test - public void store_readings_should_success_given_multiple_batches_of_meter_readings() { + @DisplayName("storeReadings should succeed when called multiple times") + public void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { var meterReadings = List.of(createReading(LocalDate.now(), 1.0)); var otherMeterReadings = List.of(createReading(LocalDate.now(), 2.0)); @@ -74,8 +80,8 @@ public void store_readings_should_success_given_multiple_batches_of_meter_readin } @Test - public void - readings_should_store_to_associate_meter_given_multiple_meters_are_existent() { + @DisplayName("storeReadings should write supplied readings to correct meter") + public void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { var meterReadings = List.of(createReading(LocalDate.now(), 1.0)); var otherMeterReadings = List.of(createReading(LocalDate.now(), 2.0)); @@ -87,13 +93,15 @@ public void store_readings_should_success_given_multiple_batches_of_meter_readin } @Test - public void read_readings_should_throw_exception_given_meter_id_is_not_existent() { + @DisplayName("readReadings should throw exception if supplied meterId is not persisted") + public void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { assertThatThrownBy(() -> meterReadingManager.readReadings(SMART_METER_ID)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("smartMeterId"); } @Test + @DisplayName("readReadings should return previously supplied readings for a known meterId") public void read_readings_should_return_readings_given_readings_are_existent() { // given var meterReadings = List.of(createReading(LocalDate.now(), 1.0)); diff --git a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java index 5c62e6bc..c9b5d073 100644 --- a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java +++ b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java @@ -1,17 +1,5 @@ package tw.joi.energy.service; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import tw.joi.energy.domain.PricePlan; -import tw.joi.energy.domain.SmartMeter; -import tw.joi.energy.repository.PricePlanRepository; -import tw.joi.energy.repository.SmartMeterRepository; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static tw.joi.energy.fixture.ElectricityReadingFixture.createReading; @@ -22,6 +10,18 @@ import static tw.joi.energy.fixture.PricePlanFixture.WORST_PLAN_ID; import static tw.joi.energy.fixture.PricePlanFixture.WORST_PRICE_PLAN; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import tw.joi.energy.domain.PricePlan; +import tw.joi.energy.domain.SmartMeter; +import tw.joi.energy.repository.PricePlanRepository; +import tw.joi.energy.repository.SmartMeterRepository; + public class PricePlanComparatorTest { private static final String SMART_METER_ID = "smart-meter-id"; private PricePlanComparator comparator; @@ -39,6 +39,7 @@ public void setUp() { } @Test + @DisplayName("recommend should return costs for all plans when no limit specified") public void recommend_should_return_all_costs_given_no_limit() { var readings = List.of(createReading(tenDaysAgo, 3.0), createReading(today, 35.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); @@ -54,6 +55,7 @@ public void recommend_should_return_all_costs_given_no_limit() { } @Test + @DisplayName("recommend should return top two cheapest costings if limit of 2 supplied ") public void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { var readings = List.of(createReading(tenDaysAgo, 5.0), createReading(today, 20.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); @@ -68,8 +70,8 @@ public void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { } @Test - public void - recommend_should_return_all_costs_given_limit_is_bigger_than_count_of_price_plans() { + @DisplayName("recommend should return all costs if limit is larger than sum of known price plans") + public void recommend_should_return_all_costs_given_limit_is_bigger_than_count_of_price_plans() { var readings = List.of(createReading(tenDaysAgo, 3.0), createReading(today, 25.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -84,6 +86,7 @@ public void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { } @Test + @DisplayName("recommend should throw exception given a missing smartId") public void recommend_should_throw_exception_given_smart_meter_is_not_existent() { assertThatThrownBy(() -> comparator.recommendCheapestPricePlans("not_existent_id", null)) .isInstanceOf(RuntimeException.class) From 89e4c02d9bfe2cd3d4e65f53cb44467060828a1c Mon Sep 17 00:00:00 2001 From: John King Date: Mon, 15 Jul 2024 22:35:19 +0200 Subject: [PATCH 02/11] WIP - changes readings generator to use a stream, more use of zoned date time --- src/main/java/tw/joi/energy/App.java | 2 +- .../config/ElectricityReadingsGenerator.java | 39 ++++++++++--------- .../java/tw/joi/energy/config/TestData.java | 15 +++---- .../java/tw/joi/energy/domain/PricePlan.java | 6 +-- .../tw/joi/energy/domain/PricePlanTest.java | 11 ++---- .../tw/joi/energy/domain/SmartMeterTest.java | 5 ++- .../fixture/ElectricityReadingFixture.java | 9 +++-- .../joi/energy/fixture/PricePlanFixture.java | 9 +++-- .../service/MeterReadingManagerTest.java | 15 ++++--- .../service/PricePlanComparatorTest.java | 5 ++- 10 files changed, 62 insertions(+), 54 deletions(-) diff --git a/src/main/java/tw/joi/energy/App.java b/src/main/java/tw/joi/energy/App.java index cc89b947..7cdc2156 100644 --- a/src/main/java/tw/joi/energy/App.java +++ b/src/main/java/tw/joi/energy/App.java @@ -25,7 +25,7 @@ public static void main(String[] args) { printAllAvailablePricePlans(pricePlanRepository); printSmartMeterInformation(smartMeterRepository, "Before storing readings..."); - var readingsToSave = ElectricityReadingsGenerator.generate(3); + var readingsToSave = ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList(); meterReadingManager.storeReadings(TEST_SMART_METER, readingsToSave); printSmartMeterInformation(smartMeterRepository, "After storing readings..."); diff --git a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java index d1ffae84..a3552cb1 100644 --- a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java +++ b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java @@ -2,34 +2,37 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.Clock; import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalField; +import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Random; +import java.util.stream.Stream; import tw.joi.energy.domain.ElectricityReading; public class ElectricityReadingsGenerator { - public static List generate(int number) { - List readings = new ArrayList<>(); - Instant now = Instant.now(); - BigDecimal previousReading = BigDecimal.ONE; - Instant previousReadingTime = now.minusSeconds(2 * number * 60L); + private ElectricityReadingsGenerator() {} - Random readingRandomiser = new Random(); - - for (int i = 0; i < number; i++) { - double positiveIncrement = Math.abs(readingRandomiser.nextGaussian()); - BigDecimal currentReading = - previousReading.add(BigDecimal.valueOf(positiveIncrement)).setScale(4, RoundingMode.CEILING); - ElectricityReading electricityReading = - new ElectricityReading(previousReadingTime.plusSeconds(i * 60L), currentReading); - readings.add(electricityReading); - previousReading = currentReading; - } + public static Stream generateElectricityReadingStream(int days) { + return generateElectricityReadingStream(Clock.systemDefaultZone(), BigDecimal.ZERO, days); + } - readings.sort(Comparator.comparing(ElectricityReading::time)); - return readings; + // we'll provide hourly readings for the specified number of days assuming 24 hours a day + // we'll assume that a house consumes ca 2700 kWh a year, so about 0.3 kWh per hour + public static Stream generateElectricityReadingStream(Clock clock, BigDecimal initialReading, int days) { + var now = clock.instant(); + var readingRandomiser = new Random(); + var seed = new ElectricityReading(now, initialReading); + var lastTimeToBeSupplied = now.plus(days * 24, ChronoUnit.HOURS); + return Stream.iterate(seed, er -> er.time().equals(lastTimeToBeSupplied) || er.time().isAfter(lastTimeToBeSupplied), + er -> { + var hoursWorthOfEnergy = BigDecimal.valueOf(readingRandomiser.nextDouble(0.3 - 0.2, 0.3 + 0.2)); + return new ElectricityReading(er.time().plus(1, ChronoUnit.HOURS), er.readingInKwH().add(hoursWorthOfEnergy)); + }); } } diff --git a/src/main/java/tw/joi/energy/config/TestData.java b/src/main/java/tw/joi/energy/config/TestData.java index 51401c04..cc2ada47 100644 --- a/src/main/java/tw/joi/energy/config/TestData.java +++ b/src/main/java/tw/joi/energy/config/TestData.java @@ -1,6 +1,7 @@ package tw.joi.energy.config; import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import java.math.BigDecimal; import java.util.List; @@ -12,23 +13,23 @@ public final class TestData { private static final PricePlan MOST_EVIL_PRICE_PLAN = - new PricePlan("price-plan-0", "Dr Evil's Dark Energy", BigDecimal.TEN, emptyList()); + new PricePlan("price-plan-0", "Dr Evil's Dark Energy", BigDecimal.TEN, emptySet()); private static final PricePlan RENEWABLES_PRICE_PLAN = - new PricePlan("price-plan-1", "The Green Eco", BigDecimal.valueOf(2), emptyList()); + new PricePlan("price-plan-1", "The Green Eco", BigDecimal.valueOf(2), emptySet()); private static final PricePlan STANDARD_PRICE_PLAN = - new PricePlan("price-plan-2", "Power for Everyone", BigDecimal.ONE, emptyList()); + new PricePlan("price-plan-2", "Power for Everyone", BigDecimal.ONE, emptySet()); public static SmartMeterRepository smartMeterRepository() { var smartMeterRepository = new SmartMeterRepository(); smartMeterRepository.save("smart-meter-0", new SmartMeter(MOST_EVIL_PRICE_PLAN, emptyList())); smartMeterRepository.save( - "smart-meter-1", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generate(7))); + "smart-meter-1", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(7).toList())); smartMeterRepository.save( - "smart-meter-2", new SmartMeter(MOST_EVIL_PRICE_PLAN, ElectricityReadingsGenerator.generate(20))); + "smart-meter-2", new SmartMeter(MOST_EVIL_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(20).toList())); smartMeterRepository.save( - "smart-meter-3", new SmartMeter(STANDARD_PRICE_PLAN, ElectricityReadingsGenerator.generate(12))); + "smart-meter-3", new SmartMeter(STANDARD_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(12).toList())); smartMeterRepository.save( - "smart-meter-4", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generate(3))); + "smart-meter-4", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList())); return smartMeterRepository; } diff --git a/src/main/java/tw/joi/energy/domain/PricePlan.java b/src/main/java/tw/joi/energy/domain/PricePlan.java index 39c28b8f..b784de41 100644 --- a/src/main/java/tw/joi/energy/domain/PricePlan.java +++ b/src/main/java/tw/joi/energy/domain/PricePlan.java @@ -2,19 +2,19 @@ import java.math.BigDecimal; import java.time.DayOfWeek; -import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.List; +import java.util.Set; public class PricePlan { private final String energySupplier; private final String planName; private final BigDecimal unitRate; // unit price per kWh - private final List peakTimeMultipliers; + private final Set peakTimeMultipliers; public PricePlan( - String planName, String energySupplier, BigDecimal unitRate, List peakTimeMultipliers) { + String planName, String energySupplier, BigDecimal unitRate, Set peakTimeMultipliers) { this.planName = planName; this.energySupplier = energySupplier; this.unitRate = unitRate; diff --git a/src/test/java/tw/joi/energy/domain/PricePlanTest.java b/src/test/java/tw/joi/energy/domain/PricePlanTest.java index 236ec32c..7d283781 100644 --- a/src/test/java/tw/joi/energy/domain/PricePlanTest.java +++ b/src/test/java/tw/joi/energy/domain/PricePlanTest.java @@ -1,16 +1,13 @@ package tw.joi.energy.domain; -import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.TimeZone; -import org.assertj.core.data.Percentage; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +16,7 @@ public class PricePlanTest { @Test @DisplayName("Get energy supplier should return supplier if not null") public void get_energy_supplier_should_return_the_energy_supplier_given_supplier_is_existent() { - PricePlan pricePlan = new PricePlan("Test Plan Name", "Energy Supplier Name", BigDecimal.ONE, emptyList()); + PricePlan pricePlan = new PricePlan("Test Plan Name", "Energy Supplier Name", BigDecimal.ONE, emptySet()); assertThat(pricePlan.getEnergySupplier()).isEqualTo("Energy Supplier Name"); } @@ -30,7 +27,7 @@ public void get_price_should_return_the_base_price_given_an_ordinary_date_time() ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), ZoneId.of("GMT")); // the price plan has no peak days.... - PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE, emptyList()); + PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE, emptySet()); BigDecimal price = pricePlan.getPrice(nonPeakDateTime); @@ -40,7 +37,7 @@ public void get_price_should_return_the_base_price_given_an_ordinary_date_time() @Test @DisplayName("Get unit rate should return unit rate if no null") public void get_unit_rate_should_return_unit_rate_given_unit_rate_is_present() { - PricePlan pricePlan = new PricePlan("test-price-plan", "test-energy-supplier", BigDecimal.TWO, emptyList()); + PricePlan pricePlan = new PricePlan("test-price-plan", "test-energy-supplier", BigDecimal.TWO, emptySet()); BigDecimal rate = pricePlan.getUnitRate(); diff --git a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java index 5c941a8a..56c27a62 100644 --- a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java +++ b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java @@ -2,16 +2,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.util.Collections; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import static java.util.Collections.emptyList; + class SmartMeterTest { @Test @DisplayName("Price plan should be null if none has been supplied") void price_plan_id_should_be_null_given_no_price_plan_has_been_provided() { - var smartMeter = new SmartMeter(null, Collections.emptyList()); + var smartMeter = new SmartMeter(null, emptyList()); var pricePlanId = smartMeter.getPricePlanId(); diff --git a/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java b/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java index 254502e2..264e953e 100644 --- a/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java +++ b/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java @@ -4,15 +4,16 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import tw.joi.energy.domain.ElectricityReading; public class ElectricityReadingFixture { - public static ElectricityReading createReading(LocalDateTime timeToRead, Double reading) { + public static ElectricityReading createReading(ZonedDateTime timeToRead, Double reading) { return new ElectricityReading( - timeToRead.atZone(ZoneId.systemDefault()).toInstant(), BigDecimal.valueOf(reading)); + timeToRead.toInstant(), BigDecimal.valueOf(reading)); } - public static ElectricityReading createReading(LocalDate dateToRead, Double reading) { - return createReading(dateToRead.atStartOfDay(), reading); + public static ElectricityReading createReading(LocalDate dateToRead, ZoneId zoneId, Double reading) { + return createReading(ZonedDateTime.of(dateToRead.atStartOfDay(), zoneId), reading); } } diff --git a/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java b/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java index a312b002..e2bfc9a0 100644 --- a/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java +++ b/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java @@ -1,9 +1,10 @@ package tw.joi.energy.fixture; import java.math.BigDecimal; -import java.util.Collections; import tw.joi.energy.domain.PricePlan; +import static java.util.Collections.emptySet; + public class PricePlanFixture { public static final String WORST_PLAN_ID = "worst-supplier"; @@ -11,11 +12,11 @@ public class PricePlanFixture { public static final String SECOND_BEST_PLAN_ID = "second-best-supplier"; public static final PricePlan DEFAULT_PRICE_PLAN = - new PricePlan(SECOND_BEST_PLAN_ID, "energy-supplier", BigDecimal.TWO, Collections.emptyList()); + new PricePlan(SECOND_BEST_PLAN_ID, "energy-supplier", BigDecimal.TWO, emptySet()); public static final PricePlan WORST_PRICE_PLAN = - new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, Collections.emptyList()); + new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, emptySet()); public static final PricePlan BEST_PRICE_PLAN = - new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, Collections.emptyList()); + new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, emptySet()); } diff --git a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java index 517b0225..7fd59474 100644 --- a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java +++ b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java @@ -6,6 +6,8 @@ import static tw.joi.energy.fixture.ElectricityReadingFixture.createReading; import java.time.LocalDate; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -15,6 +17,7 @@ public class MeterReadingManagerTest { + private static final ZoneId GMT = ZoneId.of("GMT"); private static final String SMART_METER_ID = "10101010"; private final SmartMeterRepository smartMeterRepository = new SmartMeterRepository(); private final MeterReadingManager meterReadingManager = new MeterReadingManager(smartMeterRepository); @@ -54,7 +57,7 @@ public void store_readings_should_throw_exception_given_readings_is_empty() { @Test @DisplayName("storeReadings should succeed given non-empty list of readings") public void store_readings_should_succeed_given_meter_readings() { - var readingsToStore = List.of(createReading(LocalDate.now(), 1.0)); + var readingsToStore = List.of(createReading(LocalDate.now(), GMT, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, readingsToStore); @@ -65,8 +68,8 @@ public void store_readings_should_succeed_given_meter_readings() { @Test @DisplayName("storeReadings should succeed when called multiple times") public void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { - var meterReadings = List.of(createReading(LocalDate.now(), 1.0)); - var otherMeterReadings = List.of(createReading(LocalDate.now(), 2.0)); + var meterReadings = List.of(createReading(LocalDate.now(), GMT, 1.0)); + var otherMeterReadings = List.of(createReading(LocalDate.now(), GMT, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings(SMART_METER_ID, otherMeterReadings); @@ -82,8 +85,8 @@ public void store_readings_should_succeed_given_multiple_batches_of_meter_readin @Test @DisplayName("storeReadings should write supplied readings to correct meter") public void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { - var meterReadings = List.of(createReading(LocalDate.now(), 1.0)); - var otherMeterReadings = List.of(createReading(LocalDate.now(), 2.0)); + var meterReadings = List.of(createReading(ZonedDateTime.now(), 1.0)); + var otherMeterReadings = List.of(createReading(ZonedDateTime.now(), 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings("00001", otherMeterReadings); @@ -104,7 +107,7 @@ public void read_readings_should_throw_exception_given_meter_id_is_not_persisted @DisplayName("readReadings should return previously supplied readings for a known meterId") public void read_readings_should_return_readings_given_readings_are_existent() { // given - var meterReadings = List.of(createReading(LocalDate.now(), 1.0)); + var meterReadings = List.of(createReading(ZonedDateTime.now(), 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); // expect assertThat(meterReadingManager.readReadings(SMART_METER_ID)).isEqualTo(meterReadings); diff --git a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java index c9b5d073..53bff3ac 100644 --- a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java +++ b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java @@ -12,6 +12,7 @@ import java.math.BigDecimal; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -26,8 +27,8 @@ public class PricePlanComparatorTest { private static final String SMART_METER_ID = "smart-meter-id"; private PricePlanComparator comparator; private SmartMeterRepository smartMeterRepository; - private final LocalDateTime today = LocalDateTime.now(); - private final LocalDateTime tenDaysAgo = today.minusDays(10); + private final ZonedDateTime today = ZonedDateTime.now(); + private final ZonedDateTime tenDaysAgo = today.minusDays(10); @BeforeEach public void setUp() { From 4c379c106bd2300fb8a95338ab809f379cb20897 Mon Sep 17 00:00:00 2001 From: John King Date: Tue, 16 Jul 2024 14:39:00 +0200 Subject: [PATCH 03/11] Completes chunk of proposed refactoring. Largely simplifies things down a little more and reduces reliance on global settings such as timezones or clocks. Some other notable points: - simplifes ElecriticityReadingsGenerator with a stream. Supplies tests for this stream. - factors out PeakTimeMultiplier into a simple record - provides ElectricityReading with a secondary constructor to avoid the fixture factory - PricePLan is now constructed with simple Map rather than a list for more determinism - Tidies up imports and formatting using the code conventions defined in the build --- build.gradle.kts | 2 +- src/main/java/tw/joi/energy/App.java | 3 +- .../config/ElectricityReadingsGenerator.java | 33 ++++---- .../java/tw/joi/energy/config/TestData.java | 24 +++++- .../joi/energy/domain/ElectricityReading.java | 8 +- .../joi/energy/domain/PeakTimeMultiplier.java | 6 ++ .../java/tw/joi/energy/domain/PricePlan.java | 21 ++--- .../java/tw/joi/energy/domain/SmartMeter.java | 1 + .../repository/PricePlanRepository.java | 5 +- .../ElectricityReadingsGeneratorTest.java | 77 +++++++++++++++++++ .../tw/joi/energy/domain/PricePlanTest.java | 6 +- .../tw/joi/energy/domain/SmartMeterTest.java | 3 +- .../fixture/ElectricityReadingFixture.java | 19 ----- .../joi/energy/fixture/PricePlanFixture.java | 10 +-- .../service/MeterReadingManagerTest.java | 36 ++++----- .../service/PricePlanComparatorTest.java | 33 ++++---- 16 files changed, 186 insertions(+), 101 deletions(-) create mode 100644 src/main/java/tw/joi/energy/domain/PeakTimeMultiplier.java create mode 100644 src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java delete mode 100644 src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java diff --git a/build.gradle.kts b/build.gradle.kts index 7c73ae47..15b89e8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,7 @@ application { mainClass = "tw.joi.energy.App" } -tasks.withType() { +tasks.withType { this.options.isDeprecation = true } diff --git a/src/main/java/tw/joi/energy/App.java b/src/main/java/tw/joi/energy/App.java index 7cdc2156..237ca9d2 100644 --- a/src/main/java/tw/joi/energy/App.java +++ b/src/main/java/tw/joi/energy/App.java @@ -25,7 +25,8 @@ public static void main(String[] args) { printAllAvailablePricePlans(pricePlanRepository); printSmartMeterInformation(smartMeterRepository, "Before storing readings..."); - var readingsToSave = ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList(); + var readingsToSave = + ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList(); meterReadingManager.storeReadings(TEST_SMART_METER, readingsToSave); printSmartMeterInformation(smartMeterRepository, "After storing readings..."); diff --git a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java index a3552cb1..37c14848 100644 --- a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java +++ b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java @@ -1,21 +1,19 @@ package tw.joi.energy.config; import java.math.BigDecimal; -import java.math.RoundingMode; import java.time.Clock; -import java.time.Instant; import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalField; -import java.time.temporal.TemporalUnit; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; import java.util.Random; import java.util.stream.Stream; import tw.joi.energy.domain.ElectricityReading; public class ElectricityReadingsGenerator { + public static final double AVG_HOURLY_USAGE = 0.3; + public static final double VARIANCE = 0.2; + public static final double MIN_HOURLY_USAGE = AVG_HOURLY_USAGE - VARIANCE; + public static final double MAX_HOURLY_USAGE = AVG_HOURLY_USAGE + VARIANCE; + private ElectricityReadingsGenerator() {} public static Stream generateElectricityReadingStream(int days) { @@ -24,15 +22,22 @@ public static Stream generateElectricityReadingStream(int da // we'll provide hourly readings for the specified number of days assuming 24 hours a day // we'll assume that a house consumes ca 2700 kWh a year, so about 0.3 kWh per hour - public static Stream generateElectricityReadingStream(Clock clock, BigDecimal initialReading, int days) { + + // the assumed starting point is the time on the clock, the ending point 24 hours later - so for 1 day, we'll get 25 + // readings + public static Stream generateElectricityReadingStream( + Clock clock, BigDecimal initialReading, int days) { var now = clock.instant(); var readingRandomiser = new Random(); var seed = new ElectricityReading(now, initialReading); - var lastTimeToBeSupplied = now.plus(days * 24, ChronoUnit.HOURS); - return Stream.iterate(seed, er -> er.time().equals(lastTimeToBeSupplied) || er.time().isAfter(lastTimeToBeSupplied), - er -> { - var hoursWorthOfEnergy = BigDecimal.valueOf(readingRandomiser.nextDouble(0.3 - 0.2, 0.3 + 0.2)); - return new ElectricityReading(er.time().plus(1, ChronoUnit.HOURS), er.readingInKwH().add(hoursWorthOfEnergy)); - }); + var lastTimeToBeSupplied = now.plus(days * 24L, ChronoUnit.HOURS); + return Stream.iterate( + seed, er -> er.time().equals(lastTimeToBeSupplied) || er.time().isBefore(lastTimeToBeSupplied), er -> { + var hoursWorthOfEnergy = + BigDecimal.valueOf(readingRandomiser.nextDouble(MIN_HOURLY_USAGE, MAX_HOURLY_USAGE)); + return new ElectricityReading( + er.time().plus(1, ChronoUnit.HOURS), + er.readingInKwH().add(hoursWorthOfEnergy)); + }); } } diff --git a/src/main/java/tw/joi/energy/config/TestData.java b/src/main/java/tw/joi/energy/config/TestData.java index cc2ada47..17979ce8 100644 --- a/src/main/java/tw/joi/energy/config/TestData.java +++ b/src/main/java/tw/joi/energy/config/TestData.java @@ -23,13 +23,29 @@ public static SmartMeterRepository smartMeterRepository() { var smartMeterRepository = new SmartMeterRepository(); smartMeterRepository.save("smart-meter-0", new SmartMeter(MOST_EVIL_PRICE_PLAN, emptyList())); smartMeterRepository.save( - "smart-meter-1", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(7).toList())); + "smart-meter-1", + new SmartMeter( + RENEWABLES_PRICE_PLAN, + ElectricityReadingsGenerator.generateElectricityReadingStream(7) + .toList())); smartMeterRepository.save( - "smart-meter-2", new SmartMeter(MOST_EVIL_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(20).toList())); + "smart-meter-2", + new SmartMeter( + MOST_EVIL_PRICE_PLAN, + ElectricityReadingsGenerator.generateElectricityReadingStream(20) + .toList())); smartMeterRepository.save( - "smart-meter-3", new SmartMeter(STANDARD_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(12).toList())); + "smart-meter-3", + new SmartMeter( + STANDARD_PRICE_PLAN, + ElectricityReadingsGenerator.generateElectricityReadingStream(12) + .toList())); smartMeterRepository.save( - "smart-meter-4", new SmartMeter(RENEWABLES_PRICE_PLAN, ElectricityReadingsGenerator.generateElectricityReadingStream(3).toList())); + "smart-meter-4", + new SmartMeter( + RENEWABLES_PRICE_PLAN, + ElectricityReadingsGenerator.generateElectricityReadingStream(3) + .toList())); return smartMeterRepository; } diff --git a/src/main/java/tw/joi/energy/domain/ElectricityReading.java b/src/main/java/tw/joi/energy/domain/ElectricityReading.java index 1af38519..79aa05ec 100644 --- a/src/main/java/tw/joi/energy/domain/ElectricityReading.java +++ b/src/main/java/tw/joi/energy/domain/ElectricityReading.java @@ -1,10 +1,16 @@ package tw.joi.energy.domain; import java.math.BigDecimal; +import java.time.Clock; import java.time.Instant; /** * @param time point in time * @param readingInKwH energy consumed in total to this point in time in kWh */ -public record ElectricityReading(Instant time, BigDecimal readingInKwH) {} +public record ElectricityReading(Instant time, BigDecimal readingInKwH) { + + public ElectricityReading(Clock clock, double readingInKwH) { + this(clock.instant(), BigDecimal.valueOf(readingInKwH)); + } +} diff --git a/src/main/java/tw/joi/energy/domain/PeakTimeMultiplier.java b/src/main/java/tw/joi/energy/domain/PeakTimeMultiplier.java new file mode 100644 index 00000000..00476492 --- /dev/null +++ b/src/main/java/tw/joi/energy/domain/PeakTimeMultiplier.java @@ -0,0 +1,6 @@ +package tw.joi.energy.domain; + +import java.math.BigDecimal; +import java.time.DayOfWeek; + +public record PeakTimeMultiplier(DayOfWeek dayOfWeek, BigDecimal multiplier) {} diff --git a/src/main/java/tw/joi/energy/domain/PricePlan.java b/src/main/java/tw/joi/energy/domain/PricePlan.java index b784de41..9b2ac43b 100644 --- a/src/main/java/tw/joi/energy/domain/PricePlan.java +++ b/src/main/java/tw/joi/energy/domain/PricePlan.java @@ -3,22 +3,24 @@ import java.math.BigDecimal; import java.time.DayOfWeek; import java.time.ZonedDateTime; -import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class PricePlan { private final String energySupplier; private final String planName; private final BigDecimal unitRate; // unit price per kWh - private final Set peakTimeMultipliers; + private final Map peakTimeMultipliers; public PricePlan( - String planName, String energySupplier, BigDecimal unitRate, Set peakTimeMultipliers) { + String planName, String energySupplier, BigDecimal unitRate, Set peakTimeMultipliers) { this.planName = planName; this.energySupplier = energySupplier; this.unitRate = unitRate; - this.peakTimeMultipliers = peakTimeMultipliers; + this.peakTimeMultipliers = peakTimeMultipliers.stream() + .collect(Collectors.toUnmodifiableMap(PeakTimeMultiplier::dayOfWeek, PeakTimeMultiplier::multiplier)); } public String getEnergySupplier() { @@ -41,15 +43,4 @@ public BigDecimal getPrice(ZonedDateTime dateTime) { public String toString() { return "Name: '" + planName + "', Unit Rate: " + unitRate + ", Supplier: '" + energySupplier + "'"; } - - static class PeakTimeMultiplier { - - DayOfWeek dayOfWeek; - BigDecimal multiplier; - - public PeakTimeMultiplier(DayOfWeek dayOfWeek, BigDecimal multiplier) { - this.dayOfWeek = dayOfWeek; - this.multiplier = multiplier; - } - } } diff --git a/src/main/java/tw/joi/energy/domain/SmartMeter.java b/src/main/java/tw/joi/energy/domain/SmartMeter.java index 4a7966a5..ddad3373 100644 --- a/src/main/java/tw/joi/energy/domain/SmartMeter.java +++ b/src/main/java/tw/joi/energy/domain/SmartMeter.java @@ -8,6 +8,7 @@ import java.util.stream.Collectors; public class SmartMeter { + private final PricePlan pricePlan; private final List electricityReadings; diff --git a/src/main/java/tw/joi/energy/repository/PricePlanRepository.java b/src/main/java/tw/joi/energy/repository/PricePlanRepository.java index 5195c901..23ccac3b 100644 --- a/src/main/java/tw/joi/energy/repository/PricePlanRepository.java +++ b/src/main/java/tw/joi/energy/repository/PricePlanRepository.java @@ -1,10 +1,9 @@ package tw.joi.energy.repository; -import static java.util.Comparator.*; -import static java.util.stream.Collectors.*; +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toMap; import java.math.BigDecimal; -import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.Collection; import java.util.List; diff --git a/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java b/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java new file mode 100644 index 00000000..d2bd8e31 --- /dev/null +++ b/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java @@ -0,0 +1,77 @@ +package tw.joi.energy.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static tw.joi.energy.config.ElectricityReadingsGenerator.MAX_HOURLY_USAGE; +import static tw.joi.energy.config.ElectricityReadingsGenerator.MIN_HOURLY_USAGE; +import static tw.joi.energy.config.ElectricityReadingsGenerator.generateElectricityReadingStream; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.function.BiConsumer; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import tw.joi.energy.domain.ElectricityReading; + +class ElectricityReadingsGeneratorTest { + + @Test + @DisplayName("Stream for one day should have 25 entries") + void streamShouldHave25EntriesForOneDay() { + assertThat(generateElectricityReadingStream(1).count()).isEqualTo(25); + } + + @Test + @DisplayName("Stream for two days should have 49 entries") + void streamShouldHave49EntriesForTwoDays() { + assertThat(generateElectricityReadingStream(2).count()).isEqualTo(49); + } + + @Test + @DisplayName("Stream for one day should end 24 hours after initial entry") + void streamShouldHave24HoursAfterInitialEntry() { + var streamAsList = generateElectricityReadingStream(1).toList(); + var firstEntry = streamAsList.getFirst(); + var lastEntry = streamAsList.getLast(); + assertThat(Duration.between(firstEntry.time(), lastEntry.time())).hasHours(24); + } + + @Test + @DisplayName("Stream entries should be one hour apart") + void streamEntriesShouldBeOneHourApart() { + validateOrderedPairsOfEntries(generateElectricityReadingStream(1), (earlierReading, laterReading) -> assertThat( + Duration.between(earlierReading.time(), laterReading.time())) + .hasHours(1)); + } + + @Test + @DisplayName("Stream entries should have an increasing energy consumption over time") + void streamEntriesShouldHaveAnIncreasingEnergyConsumptionOverTime() { + validateOrderedPairsOfEntries(generateElectricityReadingStream(1), (earlierReading, laterReading) -> assertThat( + laterReading.readingInKwH().compareTo(earlierReading.readingInKwH())) + .isEqualTo(1)); + } + + @Test + @DisplayName("Stream entries should have an energy consumption in the expected reange") + void streamEntriesShouldHaveAnIncreasingEnergyConsumption() { + var min = BigDecimal.valueOf(MIN_HOURLY_USAGE); + var max = BigDecimal.valueOf(MAX_HOURLY_USAGE); + + validateOrderedPairsOfEntries(generateElectricityReadingStream(1), (earlierReading, laterReading) -> { + var energyBetweenReadings = laterReading.readingInKwH().subtract(earlierReading.readingInKwH()); + assertThat(energyBetweenReadings.compareTo(min)).isEqualTo(1); + assertThat(energyBetweenReadings.compareTo(max)).isEqualTo(-1); + }); + } + + private void validateOrderedPairsOfEntries( + Stream stream, BiConsumer validator) { + var streamAsList = stream.toList(); + for (int i = 1; i <= streamAsList.size() - 1; i++) { + var laterElectricityReading = streamAsList.get(i); + var earlierElectricityReading = streamAsList.get(i - 1); + validator.accept(earlierElectricityReading, laterElectricityReading); + } + } +} diff --git a/src/test/java/tw/joi/energy/domain/PricePlanTest.java b/src/test/java/tw/joi/energy/domain/PricePlanTest.java index 7d283781..6734869c 100644 --- a/src/test/java/tw/joi/energy/domain/PricePlanTest.java +++ b/src/test/java/tw/joi/energy/domain/PricePlanTest.java @@ -24,9 +24,9 @@ public void get_energy_supplier_should_return_the_energy_supplier_given_supplier @Test @DisplayName("Get price should return price given non-peak date and time") public void get_price_should_return_the_base_price_given_an_ordinary_date_time() { - ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), - ZoneId.of("GMT")); - // the price plan has no peak days.... + ZonedDateTime nonPeakDateTime = + ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), ZoneId.of("GMT")); + // the price plan has no peak days, so all times are non-peak PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE, emptySet()); BigDecimal price = pricePlan.getPrice(nonPeakDateTime); diff --git a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java index 56c27a62..3674acc2 100644 --- a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java +++ b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java @@ -1,12 +1,11 @@ package tw.joi.energy.domain; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static java.util.Collections.emptyList; - class SmartMeterTest { @Test diff --git a/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java b/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java deleted file mode 100644 index 264e953e..00000000 --- a/src/test/java/tw/joi/energy/fixture/ElectricityReadingFixture.java +++ /dev/null @@ -1,19 +0,0 @@ -package tw.joi.energy.fixture; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import tw.joi.energy.domain.ElectricityReading; - -public class ElectricityReadingFixture { - public static ElectricityReading createReading(ZonedDateTime timeToRead, Double reading) { - return new ElectricityReading( - timeToRead.toInstant(), BigDecimal.valueOf(reading)); - } - - public static ElectricityReading createReading(LocalDate dateToRead, ZoneId zoneId, Double reading) { - return createReading(ZonedDateTime.of(dateToRead.atStartOfDay(), zoneId), reading); - } -} diff --git a/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java b/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java index e2bfc9a0..7a05c23c 100644 --- a/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java +++ b/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java @@ -1,10 +1,10 @@ package tw.joi.energy.fixture; +import static java.util.Collections.emptySet; + import java.math.BigDecimal; import tw.joi.energy.domain.PricePlan; -import static java.util.Collections.emptySet; - public class PricePlanFixture { public static final String WORST_PLAN_ID = "worst-supplier"; @@ -14,9 +14,7 @@ public class PricePlanFixture { public static final PricePlan DEFAULT_PRICE_PLAN = new PricePlan(SECOND_BEST_PLAN_ID, "energy-supplier", BigDecimal.TWO, emptySet()); - public static final PricePlan WORST_PRICE_PLAN = - new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, emptySet()); + public static final PricePlan WORST_PRICE_PLAN = new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, emptySet()); - public static final PricePlan BEST_PRICE_PLAN = - new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, emptySet()); + public static final PricePlan BEST_PRICE_PLAN = new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, emptySet()); } diff --git a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java index 7fd59474..5736733c 100644 --- a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java +++ b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java @@ -3,11 +3,10 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static tw.joi.energy.fixture.ElectricityReadingFixture.createReading; -import java.time.LocalDate; +import java.time.Clock; +import java.time.Instant; import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -15,10 +14,11 @@ import tw.joi.energy.domain.ElectricityReading; import tw.joi.energy.repository.SmartMeterRepository; -public class MeterReadingManagerTest { +class MeterReadingManagerTest { private static final ZoneId GMT = ZoneId.of("GMT"); private static final String SMART_METER_ID = "10101010"; + private static final Clock FIXED_CLOCK = Clock.fixed(Instant.ofEpochSecond(1721124813L), GMT); private final SmartMeterRepository smartMeterRepository = new SmartMeterRepository(); private final MeterReadingManager meterReadingManager = new MeterReadingManager(smartMeterRepository); @@ -32,7 +32,7 @@ public void store_readings_should_throw_exception_given_meter_id_is_null() { @Test @DisplayName("storeReadings should throw exception given meterId is empty string") - public void store_readings_should_throw_exception_given_meter_id_is_empty() { + void store_readings_should_throw_exception_given_meter_id_is_empty() { assertThatThrownBy(() -> meterReadingManager.storeReadings("", emptyList())) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("smartMeterId"); @@ -40,7 +40,7 @@ public void store_readings_should_throw_exception_given_meter_id_is_empty() { @Test @DisplayName("storeReadings should throw exception given readings is null") - public void store_readings_should_throw_exception_given_readings_is_null() { + void store_readings_should_throw_exception_given_readings_is_null() { assertThatThrownBy(() -> meterReadingManager.storeReadings(SMART_METER_ID, null)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("readings"); @@ -48,7 +48,7 @@ public void store_readings_should_throw_exception_given_readings_is_null() { @Test @DisplayName("storeReadings should throw exception given readings is emtpy list") - public void store_readings_should_throw_exception_given_readings_is_empty() { + void store_readings_should_throw_exception_given_readings_is_empty() { assertThatThrownBy(() -> meterReadingManager.storeReadings(SMART_METER_ID, emptyList())) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("readings"); @@ -56,8 +56,8 @@ public void store_readings_should_throw_exception_given_readings_is_empty() { @Test @DisplayName("storeReadings should succeed given non-empty list of readings") - public void store_readings_should_succeed_given_meter_readings() { - var readingsToStore = List.of(createReading(LocalDate.now(), GMT, 1.0)); + void store_readings_should_succeed_given_meter_readings() { + var readingsToStore = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, readingsToStore); @@ -67,9 +67,9 @@ public void store_readings_should_succeed_given_meter_readings() { @Test @DisplayName("storeReadings should succeed when called multiple times") - public void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { - var meterReadings = List.of(createReading(LocalDate.now(), GMT, 1.0)); - var otherMeterReadings = List.of(createReading(LocalDate.now(), GMT, 2.0)); + void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { + var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); + var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings(SMART_METER_ID, otherMeterReadings); @@ -84,9 +84,9 @@ public void store_readings_should_succeed_given_multiple_batches_of_meter_readin @Test @DisplayName("storeReadings should write supplied readings to correct meter") - public void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { - var meterReadings = List.of(createReading(ZonedDateTime.now(), 1.0)); - var otherMeterReadings = List.of(createReading(ZonedDateTime.now(), 2.0)); + void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { + var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); + var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings("00001", otherMeterReadings); @@ -97,7 +97,7 @@ public void store_readings_should_store_to_correct_meter_given_multiple_meters_e @Test @DisplayName("readReadings should throw exception if supplied meterId is not persisted") - public void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { + void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { assertThatThrownBy(() -> meterReadingManager.readReadings(SMART_METER_ID)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("smartMeterId"); @@ -105,9 +105,9 @@ public void read_readings_should_throw_exception_given_meter_id_is_not_persisted @Test @DisplayName("readReadings should return previously supplied readings for a known meterId") - public void read_readings_should_return_readings_given_readings_are_existent() { + void read_readings_should_return_readings_given_readings_are_existent() { // given - var meterReadings = List.of(createReading(ZonedDateTime.now(), 1.0)); + var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); // expect assertThat(meterReadingManager.readReadings(SMART_METER_ID)).isEqualTo(meterReadings); diff --git a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java index 53bff3ac..e8b6a140 100644 --- a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java +++ b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static tw.joi.energy.fixture.ElectricityReadingFixture.createReading; import static tw.joi.energy.fixture.PricePlanFixture.BEST_PLAN_ID; import static tw.joi.energy.fixture.PricePlanFixture.BEST_PRICE_PLAN; import static tw.joi.energy.fixture.PricePlanFixture.DEFAULT_PRICE_PLAN; @@ -11,27 +10,33 @@ import static tw.joi.energy.fixture.PricePlanFixture.WORST_PRICE_PLAN; import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.time.ZonedDateTime; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import tw.joi.energy.domain.ElectricityReading; import tw.joi.energy.domain.PricePlan; import tw.joi.energy.domain.SmartMeter; import tw.joi.energy.repository.PricePlanRepository; import tw.joi.energy.repository.SmartMeterRepository; -public class PricePlanComparatorTest { +class PricePlanComparatorTest { private static final String SMART_METER_ID = "smart-meter-id"; private PricePlanComparator comparator; private SmartMeterRepository smartMeterRepository; - private final ZonedDateTime today = ZonedDateTime.now(); - private final ZonedDateTime tenDaysAgo = today.minusDays(10); + + private static final ZoneId GMT = ZoneId.of("GMT"); + private static final Instant testInstant = Instant.ofEpochSecond(1721124813L); + private static final Clock today = Clock.fixed(testInstant, GMT); + private static final Clock tenDaysAgo = Clock.fixed(testInstant.minus(10, ChronoUnit.DAYS), GMT); @BeforeEach - public void setUp() { + void setUp() { List pricePlans = List.of(WORST_PRICE_PLAN, BEST_PRICE_PLAN, DEFAULT_PRICE_PLAN); PricePlanRepository pricePlanRepository = new PricePlanRepository(pricePlans); @@ -41,8 +46,8 @@ public void setUp() { @Test @DisplayName("recommend should return costs for all plans when no limit specified") - public void recommend_should_return_all_costs_given_no_limit() { - var readings = List.of(createReading(tenDaysAgo, 3.0), createReading(today, 35.0)); + void recommend_should_return_all_costs_given_no_limit() { + var readings = List.of(new ElectricityReading(tenDaysAgo, 3.0), new ElectricityReading(today, 35.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -57,8 +62,8 @@ public void recommend_should_return_all_costs_given_no_limit() { @Test @DisplayName("recommend should return top two cheapest costings if limit of 2 supplied ") - public void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { - var readings = List.of(createReading(tenDaysAgo, 5.0), createReading(today, 20.0)); + void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { + var readings = List.of(new ElectricityReading(tenDaysAgo, 5.0), new ElectricityReading(today, 20.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -72,8 +77,8 @@ public void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { @Test @DisplayName("recommend should return all costs if limit is larger than sum of known price plans") - public void recommend_should_return_all_costs_given_limit_is_bigger_than_count_of_price_plans() { - var readings = List.of(createReading(tenDaysAgo, 3.0), createReading(today, 25.0)); + void recommend_should_return_all_costs_given_limit_is_bigger_than_count_of_price_plans() { + var readings = List.of(new ElectricityReading(tenDaysAgo, 3.0), new ElectricityReading(today, 25.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -88,7 +93,7 @@ public void recommend_should_return_all_costs_given_limit_is_bigger_than_count_o @Test @DisplayName("recommend should throw exception given a missing smartId") - public void recommend_should_throw_exception_given_smart_meter_is_not_existent() { + void recommend_should_throw_exception_given_smart_meter_is_not_existent() { assertThatThrownBy(() -> comparator.recommendCheapestPricePlans("not_existent_id", null)) .isInstanceOf(RuntimeException.class) .hasMessageContaining("missing args"); From d2eb50d2c955193765e4fd6129d938f7f0b0b6da Mon Sep 17 00:00:00 2001 From: John King <53827445+jejking-tw@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:37:52 +0530 Subject: [PATCH 04/11] Update src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java Co-authored-by: Jeremy Huiskamp --- .../java/tw/joi/energy/config/ElectricityReadingsGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java index 37c14848..5e4ffbc9 100644 --- a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java +++ b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java @@ -32,7 +32,7 @@ public static Stream generateElectricityReadingStream( var seed = new ElectricityReading(now, initialReading); var lastTimeToBeSupplied = now.plus(days * 24L, ChronoUnit.HOURS); return Stream.iterate( - seed, er -> er.time().equals(lastTimeToBeSupplied) || er.time().isBefore(lastTimeToBeSupplied), er -> { + seed, er -> !er.time().isAfter(lastTimeToBeSupplied), er -> { var hoursWorthOfEnergy = BigDecimal.valueOf(readingRandomiser.nextDouble(MIN_HOURLY_USAGE, MAX_HOURLY_USAGE)); return new ElectricityReading( From 19e10a6466d1edbed33731bdf9020ee79199841e Mon Sep 17 00:00:00 2001 From: John King <53827445+jejking-tw@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:46:30 +0530 Subject: [PATCH 05/11] Update src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java Co-authored-by: Jeremy Huiskamp --- .../tw/joi/energy/config/ElectricityReadingsGeneratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java b/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java index d2bd8e31..e890b1fa 100644 --- a/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java +++ b/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java @@ -18,7 +18,7 @@ class ElectricityReadingsGeneratorTest { @Test @DisplayName("Stream for one day should have 25 entries") void streamShouldHave25EntriesForOneDay() { - assertThat(generateElectricityReadingStream(1).count()).isEqualTo(25); + assertThat(generateElectricityReadingStream(1)).hasSize(25); } @Test From c9c22681c7f003da0ec25485d193bc31fb2d37bd Mon Sep 17 00:00:00 2001 From: John King Date: Sun, 21 Jul 2024 17:46:11 +0530 Subject: [PATCH 06/11] Changes PricePlanTest to use isEqualByComparingTo on BigDecimal --- src/test/java/tw/joi/energy/domain/PricePlanTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/tw/joi/energy/domain/PricePlanTest.java b/src/test/java/tw/joi/energy/domain/PricePlanTest.java index 6734869c..9466028a 100644 --- a/src/test/java/tw/joi/energy/domain/PricePlanTest.java +++ b/src/test/java/tw/joi/energy/domain/PricePlanTest.java @@ -31,7 +31,7 @@ public void get_price_should_return_the_base_price_given_an_ordinary_date_time() BigDecimal price = pricePlan.getPrice(nonPeakDateTime); - assertThat(price).isEqualTo(BigDecimal.ONE); + assertThat(price).isEqualByComparingTo(BigDecimal.ONE); } @Test @@ -41,6 +41,6 @@ public void get_unit_rate_should_return_unit_rate_given_unit_rate_is_present() { BigDecimal rate = pricePlan.getUnitRate(); - assertThat(rate).isEqualTo(BigDecimal.TWO); + assertThat(rate).isEqualByComparingTo(BigDecimal.TWO); } } From 54a628c52d2ec9c5fa4d8f2f88ff06a674c408cd Mon Sep 17 00:00:00 2001 From: John King Date: Sun, 21 Jul 2024 18:06:05 +0530 Subject: [PATCH 07/11] Makes it clearer that the fixed time stamp is just arbitary --- .../energy/service/MeterReadingManagerTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java index 5736733c..8c86c4ef 100644 --- a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java +++ b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java @@ -18,7 +18,8 @@ class MeterReadingManagerTest { private static final ZoneId GMT = ZoneId.of("GMT"); private static final String SMART_METER_ID = "10101010"; - private static final Clock FIXED_CLOCK = Clock.fixed(Instant.ofEpochSecond(1721124813L), GMT); + private static final long ARBITRARY_TIME_STAMP = 1721124813L; + private static final Clock ARBITRARY_FIXED_CLOCK = Clock.fixed(Instant.ofEpochSecond(ARBITRARY_TIME_STAMP), GMT); private final SmartMeterRepository smartMeterRepository = new SmartMeterRepository(); private final MeterReadingManager meterReadingManager = new MeterReadingManager(smartMeterRepository); @@ -57,7 +58,7 @@ void store_readings_should_throw_exception_given_readings_is_empty() { @Test @DisplayName("storeReadings should succeed given non-empty list of readings") void store_readings_should_succeed_given_meter_readings() { - var readingsToStore = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); + var readingsToStore = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, readingsToStore); @@ -68,8 +69,8 @@ void store_readings_should_succeed_given_meter_readings() { @Test @DisplayName("storeReadings should succeed when called multiple times") void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { - var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); - var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); + var meterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); + var otherMeterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings(SMART_METER_ID, otherMeterReadings); @@ -85,8 +86,8 @@ void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { @Test @DisplayName("storeReadings should write supplied readings to correct meter") void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { - var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); - var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); + var meterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); + var otherMeterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings("00001", otherMeterReadings); @@ -107,7 +108,7 @@ void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { @DisplayName("readReadings should return previously supplied readings for a known meterId") void read_readings_should_return_readings_given_readings_are_existent() { // given - var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); + var meterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); // expect assertThat(meterReadingManager.readReadings(SMART_METER_ID)).isEqualTo(meterReadings); From 20ce194b89f6293fcaaac798d7cbc41b84aaa3e5 Mon Sep 17 00:00:00 2001 From: John King Date: Sun, 21 Jul 2024 19:02:20 +0530 Subject: [PATCH 08/11] Makes it clear that the fixed clock is arbitary. --- .../energy/service/MeterReadingManagerTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java index 8c86c4ef..0dbfca86 100644 --- a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java +++ b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java @@ -19,7 +19,7 @@ class MeterReadingManagerTest { private static final ZoneId GMT = ZoneId.of("GMT"); private static final String SMART_METER_ID = "10101010"; private static final long ARBITRARY_TIME_STAMP = 1721124813L; - private static final Clock ARBITRARY_FIXED_CLOCK = Clock.fixed(Instant.ofEpochSecond(ARBITRARY_TIME_STAMP), GMT); + private static final Clock FIXED_CLOCK = Clock.fixed(Instant.ofEpochSecond(ARBITRARY_TIME_STAMP), GMT); private final SmartMeterRepository smartMeterRepository = new SmartMeterRepository(); private final MeterReadingManager meterReadingManager = new MeterReadingManager(smartMeterRepository); @@ -58,7 +58,7 @@ void store_readings_should_throw_exception_given_readings_is_empty() { @Test @DisplayName("storeReadings should succeed given non-empty list of readings") void store_readings_should_succeed_given_meter_readings() { - var readingsToStore = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); + var readingsToStore = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, readingsToStore); @@ -69,8 +69,8 @@ void store_readings_should_succeed_given_meter_readings() { @Test @DisplayName("storeReadings should succeed when called multiple times") void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { - var meterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); - var otherMeterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 2.0)); + var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); + var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings(SMART_METER_ID, otherMeterReadings); @@ -86,8 +86,8 @@ void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { @Test @DisplayName("storeReadings should write supplied readings to correct meter") void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { - var meterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); - var otherMeterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 2.0)); + var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); + var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); meterReadingManager.storeReadings("00001", otherMeterReadings); @@ -108,7 +108,7 @@ void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { @DisplayName("readReadings should return previously supplied readings for a known meterId") void read_readings_should_return_readings_given_readings_are_existent() { // given - var meterReadings = List.of(new ElectricityReading(ARBITRARY_FIXED_CLOCK, 1.0)); + var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); // expect assertThat(meterReadingManager.readReadings(SMART_METER_ID)).isEqualTo(meterReadings); From 6baa03c462e6f15584cf35795bdb9a9dd0b50136 Mon Sep 17 00:00:00 2001 From: John King Date: Sun, 21 Jul 2024 19:04:27 +0530 Subject: [PATCH 09/11] Provides an additional constructor for `PricePlan` that initiates an immutable empty map of peak time modifiers. Modifies the existing constructor to remove unnecessary (and broken) conversion from set to map. --- src/main/java/tw/joi/energy/config/TestData.java | 6 +++--- .../java/tw/joi/energy/domain/PricePlan.java | 16 +++++++++++----- .../java/tw/joi/energy/domain/PricePlanTest.java | 7 +++---- .../tw/joi/energy/fixture/PricePlanFixture.java | 8 +++----- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/main/java/tw/joi/energy/config/TestData.java b/src/main/java/tw/joi/energy/config/TestData.java index 17979ce8..008a4ec5 100644 --- a/src/main/java/tw/joi/energy/config/TestData.java +++ b/src/main/java/tw/joi/energy/config/TestData.java @@ -13,11 +13,11 @@ public final class TestData { private static final PricePlan MOST_EVIL_PRICE_PLAN = - new PricePlan("price-plan-0", "Dr Evil's Dark Energy", BigDecimal.TEN, emptySet()); + new PricePlan("price-plan-0", "Dr Evil's Dark Energy", BigDecimal.TEN); private static final PricePlan RENEWABLES_PRICE_PLAN = - new PricePlan("price-plan-1", "The Green Eco", BigDecimal.valueOf(2), emptySet()); + new PricePlan("price-plan-1", "The Green Eco", BigDecimal.valueOf(2)); private static final PricePlan STANDARD_PRICE_PLAN = - new PricePlan("price-plan-2", "Power for Everyone", BigDecimal.ONE, emptySet()); + new PricePlan("price-plan-2", "Power for Everyone", BigDecimal.ONE); public static SmartMeterRepository smartMeterRepository() { var smartMeterRepository = new SmartMeterRepository(); diff --git a/src/main/java/tw/joi/energy/domain/PricePlan.java b/src/main/java/tw/joi/energy/domain/PricePlan.java index 9b2ac43b..8ac5f79a 100644 --- a/src/main/java/tw/joi/energy/domain/PricePlan.java +++ b/src/main/java/tw/joi/energy/domain/PricePlan.java @@ -3,9 +3,9 @@ import java.math.BigDecimal; import java.time.DayOfWeek; import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; public class PricePlan { @@ -14,13 +14,19 @@ public class PricePlan { private final BigDecimal unitRate; // unit price per kWh private final Map peakTimeMultipliers; + public PricePlan(String planName, String energySupplier, BigDecimal unitRate) { + this.planName = planName; + this.energySupplier = energySupplier; + this.unitRate = unitRate; + this.peakTimeMultipliers = Collections.emptyMap(); + } + public PricePlan( - String planName, String energySupplier, BigDecimal unitRate, Set peakTimeMultipliers) { + String planName, String energySupplier, BigDecimal unitRate, Map peakTimeMultipliers) { this.planName = planName; this.energySupplier = energySupplier; this.unitRate = unitRate; - this.peakTimeMultipliers = peakTimeMultipliers.stream() - .collect(Collectors.toUnmodifiableMap(PeakTimeMultiplier::dayOfWeek, PeakTimeMultiplier::multiplier)); + this.peakTimeMultipliers = Collections.unmodifiableMap(new HashMap<>(peakTimeMultipliers)); } public String getEnergySupplier() { diff --git a/src/test/java/tw/joi/energy/domain/PricePlanTest.java b/src/test/java/tw/joi/energy/domain/PricePlanTest.java index 9466028a..3f5558eb 100644 --- a/src/test/java/tw/joi/energy/domain/PricePlanTest.java +++ b/src/test/java/tw/joi/energy/domain/PricePlanTest.java @@ -1,6 +1,5 @@ package tw.joi.energy.domain; -import static java.util.Collections.emptySet; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import java.math.BigDecimal; @@ -16,7 +15,7 @@ public class PricePlanTest { @Test @DisplayName("Get energy supplier should return supplier if not null") public void get_energy_supplier_should_return_the_energy_supplier_given_supplier_is_existent() { - PricePlan pricePlan = new PricePlan("Test Plan Name", "Energy Supplier Name", BigDecimal.ONE, emptySet()); + PricePlan pricePlan = new PricePlan("Test Plan Name", "Energy Supplier Name", BigDecimal.ONE); assertThat(pricePlan.getEnergySupplier()).isEqualTo("Energy Supplier Name"); } @@ -27,7 +26,7 @@ public void get_price_should_return_the_base_price_given_an_ordinary_date_time() ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), ZoneId.of("GMT")); // the price plan has no peak days, so all times are non-peak - PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE, emptySet()); + PricePlan pricePlan = new PricePlan("test plan", "test supplier", BigDecimal.ONE); BigDecimal price = pricePlan.getPrice(nonPeakDateTime); @@ -37,7 +36,7 @@ public void get_price_should_return_the_base_price_given_an_ordinary_date_time() @Test @DisplayName("Get unit rate should return unit rate if no null") public void get_unit_rate_should_return_unit_rate_given_unit_rate_is_present() { - PricePlan pricePlan = new PricePlan("test-price-plan", "test-energy-supplier", BigDecimal.TWO, emptySet()); + PricePlan pricePlan = new PricePlan("test-price-plan", "test-energy-supplier", BigDecimal.TWO); BigDecimal rate = pricePlan.getUnitRate(); diff --git a/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java b/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java index 7a05c23c..3b40e7e4 100644 --- a/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java +++ b/src/test/java/tw/joi/energy/fixture/PricePlanFixture.java @@ -1,7 +1,5 @@ package tw.joi.energy.fixture; -import static java.util.Collections.emptySet; - import java.math.BigDecimal; import tw.joi.energy.domain.PricePlan; @@ -12,9 +10,9 @@ public class PricePlanFixture { public static final String SECOND_BEST_PLAN_ID = "second-best-supplier"; public static final PricePlan DEFAULT_PRICE_PLAN = - new PricePlan(SECOND_BEST_PLAN_ID, "energy-supplier", BigDecimal.TWO, emptySet()); + new PricePlan(SECOND_BEST_PLAN_ID, "energy-supplier", BigDecimal.TWO); - public static final PricePlan WORST_PRICE_PLAN = new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN, emptySet()); + public static final PricePlan WORST_PRICE_PLAN = new PricePlan(WORST_PLAN_ID, null, BigDecimal.TEN); - public static final PricePlan BEST_PRICE_PLAN = new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE, emptySet()); + public static final PricePlan BEST_PRICE_PLAN = new PricePlan(BEST_PLAN_ID, null, BigDecimal.ONE); } From 50af408afe52df6d823ca8cae4d3116457f9f620 Mon Sep 17 00:00:00 2001 From: John King Date: Sun, 21 Jul 2024 19:05:30 +0530 Subject: [PATCH 10/11] applies code formatting rules --- .../config/ElectricityReadingsGenerator.java | 14 ++++++-------- src/main/java/tw/joi/energy/config/TestData.java | 1 - src/main/java/tw/joi/energy/domain/PricePlan.java | 5 ++++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java index 5e4ffbc9..2bc0d421 100644 --- a/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java +++ b/src/main/java/tw/joi/energy/config/ElectricityReadingsGenerator.java @@ -31,13 +31,11 @@ public static Stream generateElectricityReadingStream( var readingRandomiser = new Random(); var seed = new ElectricityReading(now, initialReading); var lastTimeToBeSupplied = now.plus(days * 24L, ChronoUnit.HOURS); - return Stream.iterate( - seed, er -> !er.time().isAfter(lastTimeToBeSupplied), er -> { - var hoursWorthOfEnergy = - BigDecimal.valueOf(readingRandomiser.nextDouble(MIN_HOURLY_USAGE, MAX_HOURLY_USAGE)); - return new ElectricityReading( - er.time().plus(1, ChronoUnit.HOURS), - er.readingInKwH().add(hoursWorthOfEnergy)); - }); + return Stream.iterate(seed, er -> !er.time().isAfter(lastTimeToBeSupplied), er -> { + var hoursWorthOfEnergy = + BigDecimal.valueOf(readingRandomiser.nextDouble(MIN_HOURLY_USAGE, MAX_HOURLY_USAGE)); + return new ElectricityReading( + er.time().plus(1, ChronoUnit.HOURS), er.readingInKwH().add(hoursWorthOfEnergy)); + }); } } diff --git a/src/main/java/tw/joi/energy/config/TestData.java b/src/main/java/tw/joi/energy/config/TestData.java index 008a4ec5..2add51da 100644 --- a/src/main/java/tw/joi/energy/config/TestData.java +++ b/src/main/java/tw/joi/energy/config/TestData.java @@ -1,7 +1,6 @@ package tw.joi.energy.config; import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; import java.math.BigDecimal; import java.util.List; diff --git a/src/main/java/tw/joi/energy/domain/PricePlan.java b/src/main/java/tw/joi/energy/domain/PricePlan.java index 8ac5f79a..c2a22275 100644 --- a/src/main/java/tw/joi/energy/domain/PricePlan.java +++ b/src/main/java/tw/joi/energy/domain/PricePlan.java @@ -22,7 +22,10 @@ public PricePlan(String planName, String energySupplier, BigDecimal unitRate) { } public PricePlan( - String planName, String energySupplier, BigDecimal unitRate, Map peakTimeMultipliers) { + String planName, + String energySupplier, + BigDecimal unitRate, + Map peakTimeMultipliers) { this.planName = planName; this.energySupplier = energySupplier; this.unitRate = unitRate; From aa0836c1e1f6366afb339a660b1d8c53f1d944e9 Mon Sep 17 00:00:00 2001 From: John King Date: Sun, 21 Jul 2024 19:16:28 +0530 Subject: [PATCH 11/11] rationalises test method names to align with @DisplayName annotation --- .../ElectricityReadingsGeneratorTest.java | 10 +++++----- .../tw/joi/energy/domain/PricePlanTest.java | 8 ++++---- .../tw/joi/energy/domain/SmartMeterTest.java | 2 +- .../repository/PricePlanRepositoryTest.java | 2 +- .../repository/SmartMeterRepositoryTest.java | 4 ++-- .../service/MeterReadingManagerTest.java | 18 +++++++++--------- .../service/PricePlanComparatorTest.java | 8 ++++---- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java b/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java index e890b1fa..82243523 100644 --- a/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java +++ b/src/test/java/tw/joi/energy/config/ElectricityReadingsGeneratorTest.java @@ -17,19 +17,19 @@ class ElectricityReadingsGeneratorTest { @Test @DisplayName("Stream for one day should have 25 entries") - void streamShouldHave25EntriesForOneDay() { + void streamForOneDayShouldHave25Entries() { assertThat(generateElectricityReadingStream(1)).hasSize(25); } @Test @DisplayName("Stream for two days should have 49 entries") - void streamShouldHave49EntriesForTwoDays() { + void streamForTwoDaysShouldHave49Entries() { assertThat(generateElectricityReadingStream(2).count()).isEqualTo(49); } @Test @DisplayName("Stream for one day should end 24 hours after initial entry") - void streamShouldHave24HoursAfterInitialEntry() { + void streamForOneDayShouldHave24HoursAfterInitialEntry() { var streamAsList = generateElectricityReadingStream(1).toList(); var firstEntry = streamAsList.getFirst(); var lastEntry = streamAsList.getLast(); @@ -53,8 +53,8 @@ void streamEntriesShouldHaveAnIncreasingEnergyConsumptionOverTime() { } @Test - @DisplayName("Stream entries should have an energy consumption in the expected reange") - void streamEntriesShouldHaveAnIncreasingEnergyConsumption() { + @DisplayName("Stream entries should have an energy consumption in the expected range") + void streamEntriesShouldHaveAnIncreasingEnergyConsumptionInExpectedRange() { var min = BigDecimal.valueOf(MIN_HOURLY_USAGE); var max = BigDecimal.valueOf(MAX_HOURLY_USAGE); diff --git a/src/test/java/tw/joi/energy/domain/PricePlanTest.java b/src/test/java/tw/joi/energy/domain/PricePlanTest.java index 3f5558eb..6ab7b995 100644 --- a/src/test/java/tw/joi/energy/domain/PricePlanTest.java +++ b/src/test/java/tw/joi/energy/domain/PricePlanTest.java @@ -14,7 +14,7 @@ public class PricePlanTest { @Test @DisplayName("Get energy supplier should return supplier if not null") - public void get_energy_supplier_should_return_the_energy_supplier_given_supplier_is_existent() { + public void getSupplierShouldReturnSupplierIfNotNull() { PricePlan pricePlan = new PricePlan("Test Plan Name", "Energy Supplier Name", BigDecimal.ONE); assertThat(pricePlan.getEnergySupplier()).isEqualTo("Energy Supplier Name"); @@ -22,7 +22,7 @@ public void get_energy_supplier_should_return_the_energy_supplier_given_supplier @Test @DisplayName("Get price should return price given non-peak date and time") - public void get_price_should_return_the_base_price_given_an_ordinary_date_time() { + public void getPriceShouldReturnPriceGivenNonPeakDateAndTime() { ZonedDateTime nonPeakDateTime = ZonedDateTime.of(LocalDateTime.of(2017, Month.AUGUST, 31, 12, 0, 0), ZoneId.of("GMT")); // the price plan has no peak days, so all times are non-peak @@ -34,8 +34,8 @@ public void get_price_should_return_the_base_price_given_an_ordinary_date_time() } @Test - @DisplayName("Get unit rate should return unit rate if no null") - public void get_unit_rate_should_return_unit_rate_given_unit_rate_is_present() { + @DisplayName("Get unit rate should return unit rate if not null") + public void getUnitRateShouldReturnUnitRateIfNotNull() { PricePlan pricePlan = new PricePlan("test-price-plan", "test-energy-supplier", BigDecimal.TWO); BigDecimal rate = pricePlan.getUnitRate(); diff --git a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java index 3674acc2..b98c127f 100644 --- a/src/test/java/tw/joi/energy/domain/SmartMeterTest.java +++ b/src/test/java/tw/joi/energy/domain/SmartMeterTest.java @@ -10,7 +10,7 @@ class SmartMeterTest { @Test @DisplayName("Price plan should be null if none has been supplied") - void price_plan_id_should_be_null_given_no_price_plan_has_been_provided() { + void pricePlanShouldBeNullIfNoneHasBeenSupplied() { var smartMeter = new SmartMeter(null, emptyList()); var pricePlanId = smartMeter.getPricePlanId(); diff --git a/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java b/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java index 77edc42c..77af1753 100644 --- a/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java +++ b/src/test/java/tw/joi/energy/repository/PricePlanRepositoryTest.java @@ -10,7 +10,7 @@ class PricePlanRepositoryTest { @Test @DisplayName("Should return empty list of plans if none available") - void should_return_empty_list_when_get_all_price_plans_given_no_price_plans_available() { + void shouldReturnEmptyListOfPlansIfNoneAvailable() { var repository = new PricePlanRepository(emptyList()); var allPlans = repository.getAllPricePlans(); diff --git a/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java b/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java index 3f1fe369..214d2175 100644 --- a/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java +++ b/src/test/java/tw/joi/energy/repository/SmartMeterRepositoryTest.java @@ -11,7 +11,7 @@ class SmartMeterRepositoryTest { @Test @DisplayName("findById should return empty option when searching for non-existent id") - void should_return_empty_smart_meter_when_find_by_id_given_a_non_existent_id() { + void findById_shouldReturnEmptyOptionWhenSearchingForNonExistentId() { var repository = new SmartMeterRepository(); assertThat(repository.findById("non-existent")).isEmpty(); @@ -19,7 +19,7 @@ void should_return_empty_smart_meter_when_find_by_id_given_a_non_existent_id() { @Test @DisplayName("findById should return appropriate smart meter if parameter exists in repository") - void should_return_smart_meters_when_find_by_id_given_existent_smart_meter_ids() { + void findById_shouldReturnSmartMeterIfParameterExistsInRepository() { var repository = new SmartMeterRepository(); SmartMeter smartMeter0 = new SmartMeter(null, List.of()); SmartMeter smartMeter1 = new SmartMeter(null, List.of()); diff --git a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java index 0dbfca86..5260e5b9 100644 --- a/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java +++ b/src/test/java/tw/joi/energy/service/MeterReadingManagerTest.java @@ -25,7 +25,7 @@ class MeterReadingManagerTest { @Test @DisplayName("storeReadings should throw exception given a null meterId") - public void store_readings_should_throw_exception_given_meter_id_is_null() { + public void storeReadingsShouldThrowExceptionGivenNullMeterId() { assertThatThrownBy(() -> meterReadingManager.storeReadings(null, emptyList())) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("smartMeterId"); @@ -33,7 +33,7 @@ public void store_readings_should_throw_exception_given_meter_id_is_null() { @Test @DisplayName("storeReadings should throw exception given meterId is empty string") - void store_readings_should_throw_exception_given_meter_id_is_empty() { + void storeReadingsShouldThrowExceptionGivenEmptyMeterId() { assertThatThrownBy(() -> meterReadingManager.storeReadings("", emptyList())) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("smartMeterId"); @@ -41,7 +41,7 @@ void store_readings_should_throw_exception_given_meter_id_is_empty() { @Test @DisplayName("storeReadings should throw exception given readings is null") - void store_readings_should_throw_exception_given_readings_is_null() { + void storeReadingsShouldThrowExceptionGivenNullReadings() { assertThatThrownBy(() -> meterReadingManager.storeReadings(SMART_METER_ID, null)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("readings"); @@ -49,7 +49,7 @@ void store_readings_should_throw_exception_given_readings_is_null() { @Test @DisplayName("storeReadings should throw exception given readings is emtpy list") - void store_readings_should_throw_exception_given_readings_is_empty() { + void storeReadingsShouldThrowExceptionGivenEmptyReadings() { assertThatThrownBy(() -> meterReadingManager.storeReadings(SMART_METER_ID, emptyList())) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("readings"); @@ -57,7 +57,7 @@ void store_readings_should_throw_exception_given_readings_is_empty() { @Test @DisplayName("storeReadings should succeed given non-empty list of readings") - void store_readings_should_succeed_given_meter_readings() { + void storeReadingsShouldSucceedGivenNonEmptyReadings() { var readingsToStore = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, readingsToStore); @@ -68,7 +68,7 @@ void store_readings_should_succeed_given_meter_readings() { @Test @DisplayName("storeReadings should succeed when called multiple times") - void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { + void storeReadingsShouldSucceedWhenCalledMultipleTimes() { var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); @@ -85,7 +85,7 @@ void store_readings_should_succeed_given_multiple_batches_of_meter_readings() { @Test @DisplayName("storeReadings should write supplied readings to correct meter") - void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() { + void storeReadingsShouldWriteSuppliedReadingsToCorrectMeter() { var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); var otherMeterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 2.0)); @@ -98,7 +98,7 @@ void store_readings_should_store_to_correct_meter_given_multiple_meters_exist() @Test @DisplayName("readReadings should throw exception if supplied meterId is not persisted") - void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { + void readReadingsShouldThrowExceptionIfSupplierNotPersisted() { assertThatThrownBy(() -> meterReadingManager.readReadings(SMART_METER_ID)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("smartMeterId"); @@ -106,7 +106,7 @@ void read_readings_should_throw_exception_given_meter_id_is_not_persisted() { @Test @DisplayName("readReadings should return previously supplied readings for a known meterId") - void read_readings_should_return_readings_given_readings_are_existent() { + void readReadingsShouldReturnPreviouslySuppliedReadings() { // given var meterReadings = List.of(new ElectricityReading(FIXED_CLOCK, 1.0)); meterReadingManager.storeReadings(SMART_METER_ID, meterReadings); diff --git a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java index e8b6a140..03d7a01c 100644 --- a/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java +++ b/src/test/java/tw/joi/energy/service/PricePlanComparatorTest.java @@ -46,7 +46,7 @@ void setUp() { @Test @DisplayName("recommend should return costs for all plans when no limit specified") - void recommend_should_return_all_costs_given_no_limit() { + void recommendShouldReturnCostsForAllPlansWhenNoLimitSpecified() { var readings = List.of(new ElectricityReading(tenDaysAgo, 3.0), new ElectricityReading(today, 35.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -62,7 +62,7 @@ void recommend_should_return_all_costs_given_no_limit() { @Test @DisplayName("recommend should return top two cheapest costings if limit of 2 supplied ") - void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { + void recommendShouldReturnTopTwoCheapestCostingsIfLimitOf2Supplied() { var readings = List.of(new ElectricityReading(tenDaysAgo, 5.0), new ElectricityReading(today, 20.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -77,7 +77,7 @@ void recommend_should_return_top_2_cheapest_costs_given_limit_is_2() { @Test @DisplayName("recommend should return all costs if limit is larger than sum of known price plans") - void recommend_should_return_all_costs_given_limit_is_bigger_than_count_of_price_plans() { + void recommendShouldReturnAllCostsIfLimitIsLargerThanSumOfKnownPricePlans() { var readings = List.of(new ElectricityReading(tenDaysAgo, 3.0), new ElectricityReading(today, 25.0)); var smartMeter = new SmartMeter(WORST_PRICE_PLAN, readings); smartMeterRepository.save(SMART_METER_ID, smartMeter); @@ -93,7 +93,7 @@ void recommend_should_return_all_costs_given_limit_is_bigger_than_count_of_price @Test @DisplayName("recommend should throw exception given a missing smartId") - void recommend_should_throw_exception_given_smart_meter_is_not_existent() { + void recommendShouldThrowExceptionGivenMissingSmartId() { assertThatThrownBy(() -> comparator.recommendCheapestPricePlans("not_existent_id", null)) .isInstanceOf(RuntimeException.class) .hasMessageContaining("missing args");