Skip to content

Weather Stats #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,21 @@
</properties>

<dependencies>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.7.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.0-M1</version>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
42 changes: 33 additions & 9 deletions src/main/java/de/bcxp/challenge/App.java
Original file line number Diff line number Diff line change
@@ -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<WeatherRecord> 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<WeatherRecord> 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;
}

}
38 changes: 38 additions & 0 deletions src/main/java/de/bcxp/challenge/weather/WeatherStatsApp.java
Original file line number Diff line number Diff line change
@@ -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<WeatherRecord> dataSource;
private final WeatherUi weatherUi;

public WeatherStatsApp(DataSource<WeatherRecord> dataSource, WeatherUi weatherUi) {
this.dataSource = dataSource;
this.weatherUi = weatherUi;
}

/**
* Core business logic: Read data, calculate stats, display them.
*/
public void run() {

try {

Iterable<WeatherRecord> 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);
}

}

}
6 changes: 6 additions & 0 deletions src/main/java/de/bcxp/challenge/weather/data/DataSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package de.bcxp.challenge.weather.data;


public interface DataSource<T> {
Iterable<T> getData();
}
Original file line number Diff line number Diff line change
@@ -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<WeatherRecord> {

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<WeatherRecord> getData() {

CsvToBeanBuilder<WeatherRecord> beanBuilder = new CsvToBeanBuilder<>(this.csvFileReader);

return beanBuilder
.withOrderedResults(true)
.withType(WeatherRecord.class)
.build()
.parse();
}

}
36 changes: 36 additions & 0 deletions src/main/java/de/bcxp/challenge/weather/data/WeatherRecord.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
25 changes: 25 additions & 0 deletions src/main/java/de/bcxp/challenge/weather/stats/WeatherStats.java
Original file line number Diff line number Diff line change
@@ -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<WeatherRecord> records) {

Stream<WeatherRecord> 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();
}

}
10 changes: 10 additions & 0 deletions src/main/java/de/bcxp/challenge/weather/ui/WeatherUi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.bcxp.challenge.weather.ui;


import de.bcxp.challenge.weather.data.WeatherRecord;

public interface WeatherUi {

void showMinTempSpread(WeatherRecord weatherRecord);

}
14 changes: 14 additions & 0 deletions src/main/java/de/bcxp/challenge/weather/ui/WeatherUiCli.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
25 changes: 0 additions & 25 deletions src/test/java/de/bcxp/challenge/AppTest.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<WeatherRecord> 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<WeatherRecord> dataSource = new WeatherDataSourceCSV(csvPath);

// WHEN data is requested
// THEN an RuntimeException is thrown
Assertions.assertThrows(RuntimeException.class, () ->
dataSource.getData()
);
}

}
60 changes: 60 additions & 0 deletions src/test/java/de/bcxp/challenge/weather/WeatherDataSourceTest.java
Original file line number Diff line number Diff line change
@@ -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<WeatherRecord> dataSource = WeatherDataSourceFactory.newInstance(dataSourceEnum);
assertNotNull(dataSource);

// WHEN data is read into a list
List<WeatherRecord> 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<WeatherRecord> dataSource = WeatherDataSourceFactory.newInstance(dataSourceEnum);
assertNotNull(dataSource);

// WHEN data is read into a list
List<WeatherRecord> 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());
}

}
Loading
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