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..2a62585 100644 --- a/src/main/java/de/bcxp/challenge/App.java +++ b/src/main/java/de/bcxp/challenge/App.java @@ -1,23 +1,47 @@ 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.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; + + 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) { - // Your preparation code … + Path csvWeather = Path.of("./src/main/resources/de/bcxp/challenge/weather.csv"); + DataSource weatherDataSource = tryInitWeatherDataSource(csvWeather); + WeatherUi weatherUi = new WeatherUiCli(); - String dayWithSmallestTempSpread = "Someday"; // Your day analysis function call … - System.out.printf("Day with smallest temperature spread: %s%n", dayWithSmallestTempSpread); + 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); } + + 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/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/data/DataSource.java b/src/main/java/de/bcxp/challenge/weather/data/DataSource.java new file mode 100644 index 0000000..98ae615 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/data/DataSource.java @@ -0,0 +1,6 @@ +package de.bcxp.challenge.weather.data; + + +public interface DataSource { + Iterable getData(); +} diff --git a/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java b/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java new file mode 100644 index 0000000..eead0c5 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/data/WeatherDataSourceCSV.java @@ -0,0 +1,33 @@ +package de.bcxp.challenge.weather.data; + +import com.opencsv.bean.CsvToBeanBuilder; + +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 it will only be used in + // the main function in which the different dependencies are plugged together. + this.csvFileReader = new FileReader(csvPath.toString()); + } + + @Override + public Iterable getData() { + + CsvToBeanBuilder beanBuilder = new CsvToBeanBuilder<>(this.csvFileReader); + + return beanBuilder + .withOrderedResults(true) + .withType(WeatherRecord.class) + .build() + .parse(); + } + +} diff --git a/src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java b/src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java new file mode 100644 index 0000000..aa59100 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java @@ -0,0 +1,36 @@ +package de.bcxp.challenge.weather.data; + +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/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java b/src/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java new file mode 100644 index 0000000..ff02ef9 --- /dev/null +++ b/src/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java @@ -0,0 +1,25 @@ +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; + +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/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/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/weather/WeatherDataSourceCsvTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java new file mode 100644 index 0000000..54c7948 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceCsvTest.java @@ -0,0 +1,61 @@ +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; + +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 CSV data source is instantiated with that path + // THEN a FileNotFoundException is thrown + Assertions.assertThrows(FileNotFoundException.class, () -> + new WeatherDataSourceCSV(invalidPath) + ); + + } + + + @Test + 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"); + 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 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"); + 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/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java new file mode 100644 index 0000000..b05c8f2 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java @@ -0,0 +1,60 @@ +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; +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/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 new file mode 100644 index 0000000..827a66b --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/WeatherStatsTest.java @@ -0,0 +1,53 @@ +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; + +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(); + } + +} 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/WeatherDataSourceEnum.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceEnum.java new file mode 100644 index 0000000..e488516 --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceEnum.java @@ -0,0 +1,6 @@ +package de.bcxp.challenge.weather.utils; + +public enum WeatherDataSourceEnum { + MOCK, + CSV, +} diff --git a/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java new file mode 100644 index 0000000..5863bdb --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceFactory.java @@ -0,0 +1,33 @@ +package de.bcxp.challenge.weather.utils; + +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; + +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() { + Iterable records = WeatherDataSourceMock.MOCK_RECORDS; + 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/weather/utils/WeatherDataSourceMock.java b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java new file mode 100644 index 0000000..1b42daf --- /dev/null +++ b/src/test/java/de/bcxp/challenge/weather/utils/WeatherDataSourceMock.java @@ -0,0 +1,29 @@ +package de.bcxp.challenge.weather.utils; + + +import de.bcxp.challenge.weather.data.DataSource; +import de.bcxp.challenge.weather.data.WeatherRecord; + +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) { + this.records = mockRecords; + } + + @Override + public Iterable getData() { + return this.records; + } + +} 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); + } + +} 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 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 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