From c6f6e3a835df0c28cd78dd1cb6408c6cba6c221f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 2 Jan 2023 18:06:08 +0100 Subject: [PATCH 1/7] Add WeatherDataSource --- pom.xml | 13 ++++- src/main/java/de/bcxp/challenge/App.java | 33 +++++++++-- .../de/bcxp/challenge/data/DataSource.java | 6 ++ .../data/weather/WeatherDataSourceCSV.java | 33 +++++++++++ .../challenge/data/weather/WeatherRecord.java | 36 ++++++++++++ src/test/java/de/bcxp/challenge/AppTest.java | 25 -------- .../weather/WeatherDataSourceCsvTest.java | 27 +++++++++ .../data/weather/WeatherDataSourceEnum.java | 6 ++ .../weather/WeatherDataSourceFactory.java | 38 +++++++++++++ .../data/weather/WeatherDataSourceMock.java | 19 +++++++ .../data/weather/WeatherDataSourceTest.java | 57 +++++++++++++++++++ src/test/resources/challenge/weather.csv | 4 ++ 12 files changed, 265 insertions(+), 32 deletions(-) create mode 100644 src/main/java/de/bcxp/challenge/data/DataSource.java create mode 100644 src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java create mode 100644 src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java delete mode 100644 src/test/java/de/bcxp/challenge/AppTest.java create mode 100644 src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java create mode 100644 src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java create mode 100644 src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java create mode 100644 src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java create mode 100644 src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java create mode 100644 src/test/resources/challenge/weather.csv diff --git a/pom.xml b/pom.xml index 6187768..9f06a86 100644 --- a/pom.xml +++ b/pom.xml @@ -35,10 +35,21 @@ + + com.opencsv + opencsv + 5.7.1 + org.junit.jupiter junit-jupiter-engine - 5.8.0-M1 + 5.9.0 + test + + + org.junit.jupiter + junit-jupiter-params + 5.9.0 test diff --git a/src/main/java/de/bcxp/challenge/App.java b/src/main/java/de/bcxp/challenge/App.java index a6c1150..b21f9a4 100644 --- a/src/main/java/de/bcxp/challenge/App.java +++ b/src/main/java/de/bcxp/challenge/App.java @@ -1,23 +1,44 @@ package de.bcxp.challenge; -/** - * The entry class for your solution. This class is only aimed as starting point and not intended as baseline for your software - * design. Read: create your own classes and packages as appropriate. - */ +import de.bcxp.challenge.data.DataSource; +import de.bcxp.challenge.data.weather.WeatherDataSourceCSV; +import de.bcxp.challenge.data.weather.WeatherRecord; + +import java.io.FileNotFoundException; +import java.nio.file.Path; + + public final class App { + private static final int EXIT_ERROR_DATA_WEATHER = 1; + /** * This is the main entry method of your program. * @param args The CLI arguments passed */ public static void main(String... args) { - // Your preparation code … + DataSource weatherDataSource = tryInitWeatherDataSource( + Path.of("./src/main/resources/de/bcxp/challenge/weather.csv") + ); - String dayWithSmallestTempSpread = "Someday"; // Your day analysis function call … + int dayWithSmallestTempSpread = -1; System.out.printf("Day with smallest temperature spread: %s%n", dayWithSmallestTempSpread); String countryWithHighestPopulationDensity = "Some country"; // Your population density analysis function call … System.out.printf("Country with highest population density: %s%n", countryWithHighestPopulationDensity); } + + private static DataSource tryInitWeatherDataSource(Path csvPath) { + + try { + return new WeatherDataSourceCSV(csvPath); + } catch (FileNotFoundException e) { + System.out.printf("Error: CSV file for weather data not found: %s%n", csvPath); + System.exit(EXIT_ERROR_DATA_WEATHER); + } + + return null; + } + } diff --git a/src/main/java/de/bcxp/challenge/data/DataSource.java b/src/main/java/de/bcxp/challenge/data/DataSource.java new file mode 100644 index 0000000..8d44722 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/data/DataSource.java @@ -0,0 +1,6 @@ +package de.bcxp.challenge.data; + + +public interface DataSource { + Iterable getData(); +} diff --git a/src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java new file mode 100644 index 0000000..d8595fe --- /dev/null +++ b/src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java @@ -0,0 +1,33 @@ +package de.bcxp.challenge.data.weather; + +import com.opencsv.bean.CsvToBeanBuilder; +import de.bcxp.challenge.data.DataSource; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.nio.file.Path; + +public class WeatherDataSourceCSV implements DataSource { + + private final FileReader csvFileReader; + + public WeatherDataSourceCSV(Path csvPath) throws FileNotFoundException { + // FileNotFoundException is an exception type that is clearly specific to a file-based implementation of the + // DataSource interface. Usually we should avoid throwing implementation-specific exceptions that are not + // defined by the interface. But for the constructor it's okay under the assumption that the data source will + // be initialized in a "dirty main" function and then injected into the application. + this.csvFileReader = new FileReader(csvPath.toString()); + } + + @Override + public Iterable getData() { + + CsvToBeanBuilder beanBuilder = new CsvToBeanBuilder<>(this.csvFileReader); + + return beanBuilder + .withType(WeatherRecord.class) + .build() + .parse(); + } + +} diff --git a/src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java b/src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java new file mode 100644 index 0000000..1997604 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java @@ -0,0 +1,36 @@ +package de.bcxp.challenge.data.weather; + +import com.opencsv.bean.CsvBindByName; + +public class WeatherRecord { + + @CsvBindByName(column = "Day", required = true) + private int day; + + @CsvBindByName(column = "MxT", required = true) + private int mxt; + + @CsvBindByName(column = "MnT", required = true) + private int mnt; + + public WeatherRecord() {} // openCSV needs this constructor when reading weather records from file + + public WeatherRecord(int day, int mxt, int mnt) { + this.day = day; + this.mxt = mxt; + this.mnt = mnt; + } + + public int getDay() { + return day; + } + + public int getMxT() { + return mxt; + } + + public int getMnT() { + return mnt; + } + +} diff --git a/src/test/java/de/bcxp/challenge/AppTest.java b/src/test/java/de/bcxp/challenge/AppTest.java deleted file mode 100644 index e7b400c..0000000 --- a/src/test/java/de/bcxp/challenge/AppTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.bcxp.challenge; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Example JUnit 5 test case. - */ -class AppTest { - - private String successLabel = "not successful"; - - @BeforeEach - void setUp() { - successLabel = "successful"; - } - - @Test - void aPointlessTest() { - assertEquals("successful", successLabel, "My expectations were not met"); - } - -} \ No newline at end of file diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java new file mode 100644 index 0000000..1c46e40 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java @@ -0,0 +1,27 @@ +package de.bcxp.challenge.data.weather; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; +import java.nio.file.Path; + +public class WeatherDataSourceCsvTest { + + @Test + public void testInvalidPathThrowsException() { + + // GIVEN an invalid path to CSV file + Path invalidPath = Path.of("./INVALID/PATH.csv"); + + // WHEN a CsvDataSource is instantiated with the wrong path + Exception exception = Assertions.assertThrows(FileNotFoundException.class, () -> + new WeatherDataSourceCSV(invalidPath) + ); + + // THEN an exception is thrown + Assertions.assertEquals(FileNotFoundException.class, exception.getClass()); + + } + +} diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java new file mode 100644 index 0000000..f630a86 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java @@ -0,0 +1,6 @@ +package de.bcxp.challenge.data.weather; + +public enum WeatherDataSourceEnum { + MOCK, + CSV, +} diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java new file mode 100644 index 0000000..97e4e5f --- /dev/null +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java @@ -0,0 +1,38 @@ +package de.bcxp.challenge.data.weather; + +import de.bcxp.challenge.data.DataSource; + +import java.io.FileNotFoundException; +import java.nio.file.Path; +import java.util.ArrayList; + +public class WeatherDataSourceFactory { + + public static DataSource newInstance(WeatherDataSourceEnum dataSourceEnum) throws FileNotFoundException { + + if (dataSourceEnum == WeatherDataSourceEnum.MOCK) { + return newMock(); + } else if (dataSourceEnum == WeatherDataSourceEnum.CSV) { + return newCsv(); + } + + return null; + } + + private static DataSource newMock() { + + ArrayList records = new ArrayList<>(); + + records.add(new WeatherRecord(1, 88, 59)); + records.add(new WeatherRecord(2, 79, 63)); + records.add(new WeatherRecord(3, 77, 55)); + + return new WeatherDataSourceMock(records); + } + + private static DataSource newCsv() throws FileNotFoundException { + Path csvPath = Path.of("./src/test/resources/challenge/weather.csv"); + return new WeatherDataSourceCSV(csvPath); + } + +} diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java new file mode 100644 index 0000000..f059051 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java @@ -0,0 +1,19 @@ +package de.bcxp.challenge.data.weather; + + +import de.bcxp.challenge.data.DataSource; + +public class WeatherDataSourceMock implements DataSource { + + private final Iterable records; + + public WeatherDataSourceMock(Iterable mockRecords) { + this.records = mockRecords; + } + + @Override + public Iterable getData() { + return this.records; + } + +} diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java new file mode 100644 index 0000000..a2306ba --- /dev/null +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java @@ -0,0 +1,57 @@ +package de.bcxp.challenge.data.weather; + +import de.bcxp.challenge.data.DataSource; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +public class WeatherDataSourceTest { + + @ParameterizedTest + @EnumSource(WeatherDataSourceEnum.class) // Run test for all DataSource implementations (as listed in the enum) + void testAllWeatherDataSourcesReturnPlausibleWeatherRecords(WeatherDataSourceEnum dataSourceEnum) throws FileNotFoundException { + + // GIVEN a data source for weather records + DataSource dataSource = WeatherDataSourceFactory.newInstance(dataSourceEnum); + assertNotNull(dataSource); + + // WHEN data is read into a list + List records = new ArrayList<>(); + dataSource.getData().forEach(records::add); + + // THEN there are three plausible weather records + for (int i = 0; i < 3; i++) { + WeatherRecord record = records.get(i); + assertEquals(i+1, record.getDay()); + assert(record.getMxT() >= record.getMnT()); + } + + } + + @ParameterizedTest + @EnumSource(WeatherDataSourceEnum.class) // Run test for all DataSource implementations (as listed in the enum) + void testDataSourcesReturnKnownMockData(WeatherDataSourceEnum dataSourceEnum) throws FileNotFoundException { + + // GIVEN a data source for weather records + DataSource dataSource = WeatherDataSourceFactory.newInstance(dataSourceEnum); + assertNotNull(dataSource); + + // WHEN data is read into a list + List records = new ArrayList<>(); + dataSource.getData().forEach(records::add); + + // THEN the weather records contain expected values (regression test) + assertEquals(88, records.get(0).getMxT()); + assertEquals(79, records.get(1).getMxT()); + assertEquals(63, records.get(1).getMnT()); + assertEquals(55, records.get(2).getMnT()); + } + +} diff --git a/src/test/resources/challenge/weather.csv b/src/test/resources/challenge/weather.csv new file mode 100644 index 0000000..2f6c771 --- /dev/null +++ b/src/test/resources/challenge/weather.csv @@ -0,0 +1,4 @@ +Day,MxT,MnT,AvT,AvDP,1HrP TPcpn,PDir,AvSp,Dir,MxS,SkyC,MxR,Mn,R AvSLP +1,88,59,74,53.8,0,280,9.6,270,17,1.6,93,23,1004.5 +2,79,63,71,46.5,0,330,8.7,340,23,3.3,70,28,1004.5 +3,77,55,66,39.6,0,350,5,350,9,2.8,59,24,1016.8 \ No newline at end of file From 26a1ee522985dd4c866f213def2fa459642eb5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 2 Jan 2023 19:59:46 +0100 Subject: [PATCH 2/7] Add weather stats --- src/main/java/de/bcxp/challenge/App.java | 9 ++-- .../de/bcxp/challenge/stats/WeatherStats.java | 25 +++++++++ .../weather/WeatherDataSourceFactory.java | 9 +--- .../data/weather/WeatherDataSourceMock.java | 9 ++++ .../challenge/stats/WeatherStatsTest.java | 52 +++++++++++++++++++ 5 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 src/main/java/de/bcxp/challenge/stats/WeatherStats.java create mode 100644 src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java diff --git a/src/main/java/de/bcxp/challenge/App.java b/src/main/java/de/bcxp/challenge/App.java index b21f9a4..2902f72 100644 --- a/src/main/java/de/bcxp/challenge/App.java +++ b/src/main/java/de/bcxp/challenge/App.java @@ -3,6 +3,7 @@ import de.bcxp.challenge.data.DataSource; import de.bcxp.challenge.data.weather.WeatherDataSourceCSV; import de.bcxp.challenge.data.weather.WeatherRecord; +import de.bcxp.challenge.stats.WeatherStats; import java.io.FileNotFoundException; import java.nio.file.Path; @@ -18,12 +19,12 @@ public final class App { */ public static void main(String... args) { - DataSource weatherDataSource = tryInitWeatherDataSource( + Iterable weatherData = tryInitWeatherDataSource( Path.of("./src/main/resources/de/bcxp/challenge/weather.csv") - ); + ).getData(); - int dayWithSmallestTempSpread = -1; - System.out.printf("Day with smallest temperature spread: %s%n", dayWithSmallestTempSpread); + WeatherRecord recordWithSmallestTempSpread = WeatherStats.findMinTemperatureSpread(weatherData); + System.out.printf("Day with smallest temperature spread: %s%n", recordWithSmallestTempSpread.getDay()); String countryWithHighestPopulationDensity = "Some country"; // Your population density analysis function call … System.out.printf("Country with highest population density: %s%n", countryWithHighestPopulationDensity); diff --git a/src/main/java/de/bcxp/challenge/stats/WeatherStats.java b/src/main/java/de/bcxp/challenge/stats/WeatherStats.java new file mode 100644 index 0000000..a6b3d4d --- /dev/null +++ b/src/main/java/de/bcxp/challenge/stats/WeatherStats.java @@ -0,0 +1,25 @@ +package de.bcxp.challenge.stats; + + +import de.bcxp.challenge.data.weather.WeatherRecord; + +import java.util.Comparator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class WeatherStats { + + public static WeatherRecord findMinTemperatureSpread(Iterable records) { + + Stream recordStream = StreamSupport.stream(records.spliterator(), false); + + return recordStream + .min(Comparator.comparing(WeatherStats::getMinMaxSpread)) + .orElse(null); + } + + private static int getMinMaxSpread(WeatherRecord indexedRecord) { + return indexedRecord.getMxT() - indexedRecord.getMnT(); + } + +} diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java index 97e4e5f..d5ff729 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java @@ -4,7 +4,6 @@ import java.io.FileNotFoundException; import java.nio.file.Path; -import java.util.ArrayList; public class WeatherDataSourceFactory { @@ -20,13 +19,7 @@ public static DataSource newInstance(WeatherDataSourceEnum dataSo } private static DataSource newMock() { - - ArrayList records = new ArrayList<>(); - - records.add(new WeatherRecord(1, 88, 59)); - records.add(new WeatherRecord(2, 79, 63)); - records.add(new WeatherRecord(3, 77, 55)); - + Iterable records = WeatherDataSourceMock.MOCK_RECORDS; return new WeatherDataSourceMock(records); } diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java index f059051..8da6bf1 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java +++ b/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java @@ -3,8 +3,17 @@ import de.bcxp.challenge.data.DataSource; +import java.util.Arrays; +import java.util.List; + public class WeatherDataSourceMock implements DataSource { + public static final List MOCK_RECORDS = Arrays.asList( + new WeatherRecord(1, 88, 59), + new WeatherRecord(2, 79, 63), + new WeatherRecord(3, 77, 55) + ); + private final Iterable records; public WeatherDataSourceMock(Iterable mockRecords) { diff --git a/src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java b/src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java new file mode 100644 index 0000000..d4ad5f6 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java @@ -0,0 +1,52 @@ +package de.bcxp.challenge.stats; + + +import de.bcxp.challenge.data.weather.WeatherDataSourceMock; +import de.bcxp.challenge.data.weather.WeatherRecord; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + + +public class WeatherStatsTest { + + @Test + void testMinSpreadForEmptyListIsNull() { + + // GIVEN an empty list of weather records + Iterable emptyRecords = new ArrayList<>(); + + // WHEN the record with minimum temperature spread is calculated + WeatherRecord minSpreadRecord = WeatherStats.findMinTemperatureSpread(emptyRecords); + + // THEN the result is null + assertNull(minSpreadRecord); + } + + @Test + void testMinimumTemperatureSpreadIsFound() { + + // GIVEN weather records and their minimum temperature spread + List records = WeatherDataSourceMock.MOCK_RECORDS; + WeatherRecord referenceMinSpreadRecord = records + .stream() + .min( + Comparator.comparing(WeatherStatsTest::calcTemperatureSpread) + ) + .orElse(null); + + // WHEN the record with minimum temperature spread is calculated + WeatherRecord calculatedMinSpreadRecord = WeatherStats.findMinTemperatureSpread(records); + + // THEN it corresponds to the reference minimum + assertNotNull(referenceMinSpreadRecord); + assertEquals(referenceMinSpreadRecord.getDay(), calculatedMinSpreadRecord.getDay()); + } + + private static int calcTemperatureSpread(WeatherRecord record) { + return record.getMxT() - record.getMnT(); + } + +} From 1fb3f7d45ab07e3c08e7119a26cdb147d503a952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 2 Jan 2023 20:27:50 +0100 Subject: [PATCH 3/7] Move files --- src/main/java/de/bcxp/challenge/App.java | 8 ++++---- .../de/bcxp/challenge/{data => weather}/DataSource.java | 2 +- .../{data => }/weather/WeatherDataSourceCSV.java | 3 +-- .../bcxp/challenge/{data => }/weather/WeatherRecord.java | 2 +- .../bcxp/challenge/{stats => weather}/WeatherStats.java | 4 +--- .../{data => }/weather/WeatherDataSourceCsvTest.java | 2 +- .../{data => }/weather/WeatherDataSourceTest.java | 5 +++-- .../challenge/{stats => weather}/WeatherStatsTest.java | 5 ++--- .../weather => weather/utils}/WeatherDataSourceEnum.java | 2 +- .../utils}/WeatherDataSourceFactory.java | 6 ++++-- .../weather => weather/utils}/WeatherDataSourceMock.java | 5 +++-- 11 files changed, 22 insertions(+), 22 deletions(-) rename src/main/java/de/bcxp/challenge/{data => weather}/DataSource.java (64%) rename src/main/java/de/bcxp/challenge/{data => }/weather/WeatherDataSourceCSV.java (93%) rename src/main/java/de/bcxp/challenge/{data => }/weather/WeatherRecord.java (95%) rename src/main/java/de/bcxp/challenge/{stats => weather}/WeatherStats.java (87%) rename src/test/java/de/bcxp/challenge/{data => }/weather/WeatherDataSourceCsvTest.java (94%) rename src/test/java/de/bcxp/challenge/{data => }/weather/WeatherDataSourceTest.java (92%) rename src/test/java/de/bcxp/challenge/{stats => weather}/WeatherStatsTest.java (91%) rename src/test/java/de/bcxp/challenge/{data/weather => weather/utils}/WeatherDataSourceEnum.java (58%) rename src/test/java/de/bcxp/challenge/{data/weather => weather/utils}/WeatherDataSourceFactory.java (82%) rename src/test/java/de/bcxp/challenge/{data/weather => weather/utils}/WeatherDataSourceMock.java (81%) diff --git a/src/main/java/de/bcxp/challenge/App.java b/src/main/java/de/bcxp/challenge/App.java index 2902f72..f5224f2 100644 --- a/src/main/java/de/bcxp/challenge/App.java +++ b/src/main/java/de/bcxp/challenge/App.java @@ -1,9 +1,9 @@ package de.bcxp.challenge; -import de.bcxp.challenge.data.DataSource; -import de.bcxp.challenge.data.weather.WeatherDataSourceCSV; -import de.bcxp.challenge.data.weather.WeatherRecord; -import de.bcxp.challenge.stats.WeatherStats; +import de.bcxp.challenge.weather.DataSource; +import de.bcxp.challenge.weather.WeatherDataSourceCSV; +import de.bcxp.challenge.weather.WeatherRecord; +import de.bcxp.challenge.weather.WeatherStats; import java.io.FileNotFoundException; import java.nio.file.Path; diff --git a/src/main/java/de/bcxp/challenge/data/DataSource.java b/src/main/java/de/bcxp/challenge/weather/DataSource.java similarity index 64% rename from src/main/java/de/bcxp/challenge/data/DataSource.java rename to src/main/java/de/bcxp/challenge/weather/DataSource.java index 8d44722..9ab7789 100644 --- a/src/main/java/de/bcxp/challenge/data/DataSource.java +++ b/src/main/java/de/bcxp/challenge/weather/DataSource.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.data; +package de.bcxp.challenge.weather; public interface DataSource { diff --git a/src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java similarity index 93% rename from src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java rename to src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java index d8595fe..6536cfd 100644 --- a/src/main/java/de/bcxp/challenge/data/weather/WeatherDataSourceCSV.java +++ b/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java @@ -1,7 +1,6 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather; import com.opencsv.bean.CsvToBeanBuilder; -import de.bcxp.challenge.data.DataSource; import java.io.FileNotFoundException; import java.io.FileReader; diff --git a/src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java b/src/main/java/de/bcxp/challenge/weather/WeatherRecord.java similarity index 95% rename from src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java rename to src/main/java/de/bcxp/challenge/weather/WeatherRecord.java index 1997604..85a0e2f 100644 --- a/src/main/java/de/bcxp/challenge/data/weather/WeatherRecord.java +++ b/src/main/java/de/bcxp/challenge/weather/WeatherRecord.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather; import com.opencsv.bean.CsvBindByName; diff --git a/src/main/java/de/bcxp/challenge/stats/WeatherStats.java b/src/main/java/de/bcxp/challenge/weather/WeatherStats.java similarity index 87% rename from src/main/java/de/bcxp/challenge/stats/WeatherStats.java rename to src/main/java/de/bcxp/challenge/weather/WeatherStats.java index a6b3d4d..1a92a98 100644 --- a/src/main/java/de/bcxp/challenge/stats/WeatherStats.java +++ b/src/main/java/de/bcxp/challenge/weather/WeatherStats.java @@ -1,8 +1,6 @@ -package de.bcxp.challenge.stats; +package de.bcxp.challenge.weather; -import de.bcxp.challenge.data.weather.WeatherRecord; - import java.util.Comparator; import java.util.stream.Stream; import java.util.stream.StreamSupport; diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java similarity index 94% rename from src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java rename to src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java index 1c46e40..aec87cd 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceCsvTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java similarity index 92% rename from src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java rename to src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java index a2306ba..6518f90 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java @@ -1,6 +1,7 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather; -import de.bcxp.challenge.data.DataSource; +import de.bcxp.challenge.weather.utils.WeatherDataSourceEnum; +import de.bcxp.challenge.weather.utils.WeatherDataSourceFactory; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; diff --git a/src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java similarity index 91% rename from src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java rename to src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java index d4ad5f6..fa52b4a 100644 --- a/src/test/java/de/bcxp/challenge/stats/WeatherStatsTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java @@ -1,8 +1,7 @@ -package de.bcxp.challenge.stats; +package de.bcxp.challenge.weather; -import de.bcxp.challenge.data.weather.WeatherDataSourceMock; -import de.bcxp.challenge.data.weather.WeatherRecord; +import de.bcxp.challenge.weather.utils.WeatherDataSourceMock; import org.junit.jupiter.api.Test; import java.util.*; diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceEnum.java similarity index 58% rename from src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java rename to src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceEnum.java index f630a86..e488516 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceEnum.java +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceEnum.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather.utils; public enum WeatherDataSourceEnum { MOCK, diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java similarity index 82% rename from src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java rename to src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java index d5ff729..77ef6a9 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceFactory.java +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java @@ -1,6 +1,8 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather.utils; -import de.bcxp.challenge.data.DataSource; +import de.bcxp.challenge.weather.DataSource; +import de.bcxp.challenge.weather.WeatherDataSourceCSV; +import de.bcxp.challenge.weather.WeatherRecord; import java.io.FileNotFoundException; import java.nio.file.Path; diff --git a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java similarity index 81% rename from src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java rename to src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java index 8da6bf1..0b811f3 100644 --- a/src/test/java/de/bcxp/challenge/data/weather/WeatherDataSourceMock.java +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java @@ -1,7 +1,8 @@ -package de.bcxp.challenge.data.weather; +package de.bcxp.challenge.weather.utils; -import de.bcxp.challenge.data.DataSource; +import de.bcxp.challenge.weather.DataSource; +import de.bcxp.challenge.weather.WeatherRecord; import java.util.Arrays; import java.util.List; From f81bf3e7c71fc7a49f0c5e6757d582cb1a88e073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 2 Jan 2023 20:42:43 +0100 Subject: [PATCH 4/7] Update doc --- .../java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java index 6536cfd..5fff830 100644 --- a/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java +++ b/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java @@ -13,8 +13,9 @@ public class WeatherDataSourceCSV implements DataSource { public WeatherDataSourceCSV(Path csvPath) throws FileNotFoundException { // FileNotFoundException is an exception type that is clearly specific to a file-based implementation of the // DataSource interface. Usually we should avoid throwing implementation-specific exceptions that are not - // defined by the interface. But for the constructor it's okay under the assumption that the data source will - // be initialized in a "dirty main" function and then injected into the application. + // defined by the interface. But for the constructor it's okay under the assumption that the only place that + // will use the implementation-specific constructor is the main function in which the different dependencies + // are plugged together. this.csvFileReader = new FileReader(csvPath.toString()); } From 9e684119750175c4ae99ca78404e49eabfccb607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Mon, 2 Jan 2023 21:00:55 +0100 Subject: [PATCH 5/7] Add tests for invalid inputs --- .../weather/WeatherDataSourceCSV.java | 1 + .../weather/WeatherDataSourceCsvTest.java | 39 +++++++++++++++++-- .../resources/challenge/weather_empty.csv | 0 .../challenge/weather_with_float.csv | 4 ++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/challenge/weather_empty.csv create mode 100644 src/test/resources/challenge/weather_with_float.csv diff --git a/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java index 5fff830..3c442e9 100644 --- a/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java +++ b/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java @@ -25,6 +25,7 @@ public Iterable getData() { CsvToBeanBuilder beanBuilder = new CsvToBeanBuilder<>(this.csvFileReader); return beanBuilder + .withOrderedResults(true) .withType(WeatherRecord.class) .build() .parse(); diff --git a/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java index aec87cd..a81a20f 100644 --- a/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java @@ -14,14 +14,45 @@ public void testInvalidPathThrowsException() { // GIVEN an invalid path to CSV file Path invalidPath = Path.of("./INVALID/PATH.csv"); - // WHEN a CsvDataSource is instantiated with the wrong path - Exception exception = Assertions.assertThrows(FileNotFoundException.class, () -> + // WHEN a CSV data source is instantiated with that path + // THEN a FileNotFoundException is thrown + Assertions.assertThrows(FileNotFoundException.class, () -> new WeatherDataSourceCSV(invalidPath) ); - // THEN an exception is thrown - Assertions.assertEquals(FileNotFoundException.class, exception.getClass()); + } + + + @Test + public void testEmptyCsvResultsInRuntimeException() throws FileNotFoundException { + + // GIVEN a data source for an empty CSV file + Path csvPath = Path.of("./src/test/resources/challenge/weather_empty.csv"); + DataSource dataSource = new WeatherDataSourceCSV(csvPath); + + // WHEN data is requested + // THEN an RuntimeException is thrown + Exception exception = Assertions.assertThrows(RuntimeException.class, () -> + dataSource.getData() + ); + // AND the error message is descriptive + assert(exception.getMessage().contains("Error capturing CSV header")); + } + + + @Test + public void testUnexpectedFloatValuesLeadToRuntimeException() throws FileNotFoundException { + + // GIVEN a data source pointing to a CSV with float values instead of the expected integers + Path csvPath = Path.of("./src/test/resources/challenge/weather_with_float.csv"); + DataSource dataSource = new WeatherDataSourceCSV(csvPath); + + // WHEN data is requested + // THEN an RuntimeException is thrown + Assertions.assertThrows(RuntimeException.class, () -> + dataSource.getData() + ); } } diff --git a/src/test/resources/challenge/weather_empty.csv b/src/test/resources/challenge/weather_empty.csv new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/challenge/weather_with_float.csv b/src/test/resources/challenge/weather_with_float.csv new file mode 100644 index 0000000..9c5af26 --- /dev/null +++ b/src/test/resources/challenge/weather_with_float.csv @@ -0,0 +1,4 @@ +Day,MxT,MnT,AvT,AvDP,1HrP TPcpn,PDir,AvSp,Dir,MxS,SkyC,MxR,Mn,R AvSLP +1.1,8.8,5.9,74,53.8,0,280,9.6,270,17,1.6,93,23,1004.5 +2.0,7.9,6.3,71,46.5,0,330,8.7,340,23,3.3,70,28,1004.5 +3.0,7.7,5.5,66,39.6,0,350,5,350,9,2.8,59,24,1016.8 \ No newline at end of file From b4f558cca2c88951f8e0b45abe767a172f81fdb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Tue, 3 Jan 2023 12:31:05 +0100 Subject: [PATCH 6/7] Update comment --- .../challenge/weather/{ => data}/WeatherDataSourceCSV.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) rename src/main/java/de/bcxp/challenge/weather/{ => data}/WeatherDataSourceCSV.java (84%) diff --git a/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java similarity index 84% rename from src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java rename to src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java index 3c442e9..05b4f78 100644 --- a/src/main/java/de/bcxp/challenge/weather/WeatherDataSourceCSV.java +++ b/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java @@ -13,9 +13,8 @@ public class WeatherDataSourceCSV implements DataSource { public WeatherDataSourceCSV(Path csvPath) throws FileNotFoundException { // FileNotFoundException is an exception type that is clearly specific to a file-based implementation of the // DataSource interface. Usually we should avoid throwing implementation-specific exceptions that are not - // defined by the interface. But for the constructor it's okay under the assumption that the only place that - // will use the implementation-specific constructor is the main function in which the different dependencies - // are plugged together. + // defined by the interface. But for the constructor it's okay under the assumption that it will only be used in + // the main function in which the different dependencies are plugged together. this.csvFileReader = new FileReader(csvPath.toString()); } From 8016687007fb034f06bad1c8bb8f8b55c742c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn?= Date: Tue, 3 Jan 2023 12:42:48 +0100 Subject: [PATCH 7/7] Add UI for weather --- src/main/java/de/bcxp/challenge/App.java | 24 ++++---- .../challenge/weather/WeatherStatsApp.java | 38 ++++++++++++ .../weather/{ => data}/DataSource.java | 2 +- .../weather/data/WeatherDataSourceCSV.java | 2 +- .../weather/{ => data}/WeatherRecord.java | 2 +- .../weather/{ => stats}/WeatherStats.java | 4 +- .../bcxp/challenge/weather/ui/WeatherUi.java | 10 ++++ .../challenge/weather/ui/WeatherUiCli.java | 14 +++++ .../weather/WeatherDataSourceCsvTest.java | 7 ++- .../weather/WeatherDataSourceTest.java | 2 + .../weather/WeatherStatsAppTest.java | 33 +++++++++++ .../challenge/weather/WeatherStatsTest.java | 2 + .../challenge/weather/WeatherUiCliTest.java | 59 +++++++++++++++++++ .../utils/WeatherDataSourceFactory.java | 6 +- .../weather/utils/WeatherDataSourceMock.java | 4 +- .../weather/utils/WeatherUiMock.java | 20 +++++++ 16 files changed, 207 insertions(+), 22 deletions(-) create mode 100644 src/main/java/de/bcxp/challenge/weather/WeatherStatsApp.java rename src/main/java/de/bcxp/challenge/weather/{ => data}/DataSource.java (61%) rename src/main/java/de/bcxp/challenge/weather/{ => data}/WeatherRecord.java (95%) rename src/main/java/de/bcxp/challenge/weather/{ => stats}/WeatherStats.java (86%) create mode 100644 src/main/java/de/bcxp/challenge/weather/ui/WeatherUi.java create mode 100644 src/main/java/de/bcxp/challenge/weather/ui/WeatherUiCli.java create mode 100644 src/test/java/de/bcxp/challenge/weather/WeatherStatsAppTest.java create mode 100644 src/test/java/de/bcxp/challenge/weather/WeatherUiCliTest.java create mode 100644 src/test/java/de/bcxp/challenge/weather/utils/WeatherUiMock.java diff --git a/src/main/java/de/bcxp/challenge/App.java b/src/main/java/de/bcxp/challenge/App.java index f5224f2..2a62585 100644 --- a/src/main/java/de/bcxp/challenge/App.java +++ b/src/main/java/de/bcxp/challenge/App.java @@ -1,9 +1,11 @@ package de.bcxp.challenge; -import de.bcxp.challenge.weather.DataSource; -import de.bcxp.challenge.weather.WeatherDataSourceCSV; -import de.bcxp.challenge.weather.WeatherRecord; -import de.bcxp.challenge.weather.WeatherStats; +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherDataSourceCSV; +import de.bcxp.challenge.weather.data.WeatherRecord; +import de.bcxp.challenge.weather.WeatherStatsApp; +import de.bcxp.challenge.weather.ui.WeatherUi; +import de.bcxp.challenge.weather.ui.WeatherUiCli; import java.io.FileNotFoundException; import java.nio.file.Path; @@ -14,17 +16,17 @@ public final class App { private static final int EXIT_ERROR_DATA_WEATHER = 1; /** - * This is the main entry method of your program. - * @param args The CLI arguments passed + * The app's main entry point assembles all dependencies and injects them into the core modules. + * @param args CLI arguments, currently not used */ public static void main(String... args) { - Iterable weatherData = tryInitWeatherDataSource( - Path.of("./src/main/resources/de/bcxp/challenge/weather.csv") - ).getData(); + Path csvWeather = Path.of("./src/main/resources/de/bcxp/challenge/weather.csv"); + DataSource weatherDataSource = tryInitWeatherDataSource(csvWeather); + WeatherUi weatherUi = new WeatherUiCli(); - WeatherRecord recordWithSmallestTempSpread = WeatherStats.findMinTemperatureSpread(weatherData); - System.out.printf("Day with smallest temperature spread: %s%n", recordWithSmallestTempSpread.getDay()); + WeatherStatsApp weatherStatsApp = new WeatherStatsApp(weatherDataSource, weatherUi); + weatherStatsApp.run(); String countryWithHighestPopulationDensity = "Some country"; // Your population density analysis function call … System.out.printf("Country with highest population density: %s%n", countryWithHighestPopulationDensity); diff --git a/src/main/java/de/bcxp/challenge/weather/WeatherStatsApp.java b/src/main/java/de/bcxp/challenge/weather/WeatherStatsApp.java new file mode 100644 index 0000000..2d631e7 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/WeatherStatsApp.java @@ -0,0 +1,38 @@ +package de.bcxp.challenge.weather; + +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherRecord; +import de.bcxp.challenge.weather.stats.WeatherStats; +import de.bcxp.challenge.weather.ui.WeatherUi; + + +public class WeatherStatsApp { + + private final DataSource dataSource; + private final WeatherUi weatherUi; + + public WeatherStatsApp(DataSource dataSource, WeatherUi weatherUi) { + this.dataSource = dataSource; + this.weatherUi = weatherUi; + } + + /** + * Core business logic: Read data, calculate stats, display them. + */ + public void run() { + + try { + + Iterable data = this.dataSource.getData(); + WeatherRecord minSpreadRecord = WeatherStats.findMinTemperatureSpread(data); + this.weatherUi.showMinTempSpread(minSpreadRecord); + + } catch (Exception e) { + // There's not much error handling going on here. But this block is a good way to indicate that high-level + // error handling is part of the business logic and should probably go here. + System.out.printf("Error while calculating weather stats:%n%s%n", e); + } + + } + +} diff --git a/src/main/java/de/bcxp/challenge/weather/DataSource.java b/src/main/java/de/bcxp/challenge/weather/data/DataSource.java similarity index 61% rename from src/main/java/de/bcxp/challenge/weather/DataSource.java rename to src/main/java/de/bcxp/challenge/weather/data/DataSource.java index 9ab7789..98ae615 100644 --- a/src/main/java/de/bcxp/challenge/weather/DataSource.java +++ b/src/main/java/de/bcxp/challenge/weather/data/DataSource.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.weather; +package de.bcxp.challenge.weather.data; public interface DataSource { diff --git a/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java index 05b4f78..eead0c5 100644 --- a/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java +++ b/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.weather; +package de.bcxp.challenge.weather.data; import com.opencsv.bean.CsvToBeanBuilder; diff --git a/src/main/java/de/bcxp/challenge/weather/WeatherRecord.java b/src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java similarity index 95% rename from src/main/java/de/bcxp/challenge/weather/WeatherRecord.java rename to src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java index 85a0e2f..aa59100 100644 --- a/src/main/java/de/bcxp/challenge/weather/WeatherRecord.java +++ b/src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java @@ -1,4 +1,4 @@ -package de.bcxp.challenge.weather; +package de.bcxp.challenge.weather.data; import com.opencsv.bean.CsvBindByName; diff --git a/src/main/java/de/bcxp/challenge/weather/WeatherStats.java b/src/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java similarity index 86% rename from src/main/java/de/bcxp/challenge/weather/WeatherStats.java rename to src/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java index 1a92a98..ff02ef9 100644 --- a/src/main/java/de/bcxp/challenge/weather/WeatherStats.java +++ b/src/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java @@ -1,6 +1,8 @@ -package de.bcxp.challenge.weather; +package de.bcxp.challenge.weather.stats; +import de.bcxp.challenge.weather.data.WeatherRecord; + import java.util.Comparator; import java.util.stream.Stream; import java.util.stream.StreamSupport; diff --git a/src/main/java/de/bcxp/challenge/weather/ui/WeatherUi.java b/src/main/java/de/bcxp/challenge/weather/ui/WeatherUi.java new file mode 100644 index 0000000..520ec79 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/ui/WeatherUi.java @@ -0,0 +1,10 @@ +package de.bcxp.challenge.weather.ui; + + +import de.bcxp.challenge.weather.data.WeatherRecord; + +public interface WeatherUi { + + void showMinTempSpread(WeatherRecord weatherRecord); + +} diff --git a/src/main/java/de/bcxp/challenge/weather/ui/WeatherUiCli.java b/src/main/java/de/bcxp/challenge/weather/ui/WeatherUiCli.java new file mode 100644 index 0000000..387b551 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/ui/WeatherUiCli.java @@ -0,0 +1,14 @@ +package de.bcxp.challenge.weather.ui; + + +import de.bcxp.challenge.weather.data.WeatherRecord; + +public class WeatherUiCli implements WeatherUi { + + @Override + public void showMinTempSpread(WeatherRecord minSpreadRecord) { + String minSpreadDay = minSpreadRecord != null ? String.valueOf(minSpreadRecord.getDay()) : "[N/A]"; + System.out.printf("Day with smallest temperature spread: %s%n", minSpreadDay); + } + +} diff --git a/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java index a81a20f..54c7948 100644 --- a/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java @@ -1,5 +1,8 @@ package de.bcxp.challenge.weather; +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherDataSourceCSV; +import de.bcxp.challenge.weather.data.WeatherRecord; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -24,7 +27,7 @@ public void testInvalidPathThrowsException() { @Test - public void testEmptyCsvResultsInRuntimeException() throws FileNotFoundException { + public void testEmptyCsvThrowsRuntimeException() throws FileNotFoundException { // GIVEN a data source for an empty CSV file Path csvPath = Path.of("./src/test/resources/challenge/weather_empty.csv"); @@ -42,7 +45,7 @@ public void testEmptyCsvResultsInRuntimeException() throws FileNotFoundException @Test - public void testUnexpectedFloatValuesLeadToRuntimeException() throws FileNotFoundException { + public void testUnexpectedFloatValuesThrowRuntimeException() throws FileNotFoundException { // GIVEN a data source pointing to a CSV with float values instead of the expected integers Path csvPath = Path.of("./src/test/resources/challenge/weather_with_float.csv"); diff --git a/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java index 6518f90..b05c8f2 100644 --- a/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java @@ -1,5 +1,7 @@ package de.bcxp.challenge.weather; +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherRecord; import de.bcxp.challenge.weather.utils.WeatherDataSourceEnum; import de.bcxp.challenge.weather.utils.WeatherDataSourceFactory; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/de/bcxp/challenge/weather/WeatherStatsAppTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherStatsAppTest.java new file mode 100644 index 0000000..f3bacab --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/WeatherStatsAppTest.java @@ -0,0 +1,33 @@ +package de.bcxp.challenge.weather; + + +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherRecord; +import de.bcxp.challenge.weather.stats.WeatherStats; +import de.bcxp.challenge.weather.utils.WeatherDataSourceMock; +import de.bcxp.challenge.weather.utils.WeatherUiMock; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class WeatherStatsAppTest { + + @Test + void testAppDisplaysDayWithMinTempSpread() { + + // GIVEN a WeatherStatsApp + DataSource dataSource = new WeatherDataSourceMock(WeatherDataSourceMock.MOCK_RECORDS); + WeatherUiMock ui = new WeatherUiMock(); + WeatherStatsApp app = new WeatherStatsApp(dataSource, ui); + + // WHEN the app runs + app.run(); + + // THEN it "displays" the weather record with minimum spread + WeatherRecord expectedRecord = WeatherStats.findMinTemperatureSpread(dataSource.getData()); + assertEquals(1, ui.shownRecords.size()); + assertEquals(expectedRecord, ui.shownRecords.get(0)); + } + +} diff --git a/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java index fa52b4a..827a66b 100644 --- a/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java +++ b/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java @@ -1,6 +1,8 @@ package de.bcxp.challenge.weather; +import de.bcxp.challenge.weather.data.WeatherRecord; +import de.bcxp.challenge.weather.stats.WeatherStats; import de.bcxp.challenge.weather.utils.WeatherDataSourceMock; import org.junit.jupiter.api.Test; diff --git a/src/test/java/de/bcxp/challenge/weather/WeatherUiCliTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherUiCliTest.java new file mode 100644 index 0000000..9b47edd --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/WeatherUiCliTest.java @@ -0,0 +1,59 @@ +package de.bcxp.challenge.weather; + + +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherRecord; +import de.bcxp.challenge.weather.ui.WeatherUi; +import de.bcxp.challenge.weather.ui.WeatherUiCli; +import de.bcxp.challenge.weather.utils.WeatherDataSourceMock; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public class WeatherUiCliTest { + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + void setUpStreams() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } + + @Test + void testRecordIsPrinted() { + + // GIVEN a CLI user interface and a weather record + WeatherUi ui = new WeatherUiCli(); + DataSource dataSource = new WeatherDataSourceMock(WeatherDataSourceMock.MOCK_RECORDS); + WeatherRecord record = dataSource.getData().iterator().next(); + + // WHEN the record is shown in the UI + ui.showMinTempSpread(record); + + // THEN it is printed to (mocked) stdout + assert(outContent.toString().contains("Day with smallest temperature spread: 1")); + } + + @Test + void testNullRecordIsPrinted() { + + // GIVEN a CLI user interface and a weather record + WeatherUi ui = new WeatherUiCli(); + + // WHEN the record is shown in the UI + ui.showMinTempSpread(null); + + // THEN it is printed to (mocked) stdout + assert(outContent.toString().contains("Day with smallest temperature spread: [N/A]")); + } + +} diff --git a/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java index 77ef6a9..5863bdb 100644 --- a/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java @@ -1,8 +1,8 @@ package de.bcxp.challenge.weather.utils; -import de.bcxp.challenge.weather.DataSource; -import de.bcxp.challenge.weather.WeatherDataSourceCSV; -import de.bcxp.challenge.weather.WeatherRecord; +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherDataSourceCSV; +import de.bcxp.challenge.weather.data.WeatherRecord; import java.io.FileNotFoundException; import java.nio.file.Path; diff --git a/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java index 0b811f3..1b42daf 100644 --- a/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java @@ -1,8 +1,8 @@ package de.bcxp.challenge.weather.utils; -import de.bcxp.challenge.weather.DataSource; -import de.bcxp.challenge.weather.WeatherRecord; +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherRecord; import java.util.Arrays; import java.util.List; diff --git a/src/test/java/de/bcxp/challenge/weather/utils/WeatherUiMock.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherUiMock.java new file mode 100644 index 0000000..aea87cd --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherUiMock.java @@ -0,0 +1,20 @@ +package de.bcxp.challenge.weather.utils; + + +import de.bcxp.challenge.weather.data.WeatherRecord; +import de.bcxp.challenge.weather.ui.WeatherUi; + +import java.util.ArrayList; +import java.util.List; + + +public class WeatherUiMock implements WeatherUi { + + public List shownRecords = new ArrayList<>(); + + @Override + public void showMinTempSpread(WeatherRecord weatherRecord) { + this.shownRecords.add(weatherRecord); + } + +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy