mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-12-14 07:46:18 +00:00
Add test to PVForecast (#174)
* Add documentation to class_pv_forecast.py. Added documentation. Beware mostly generated by ChatGPT. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> * Add CacheFileStore, datetime and logger utilities. The `CacheFileStore` class is a singleton-based, thread-safe key-value store for managing temporary file objects, allowing the creation, retrieval, and management of cache files. The utility modules offer a flexible logging setup (`get_logger`) and utilities to handle different date-time formats (`to_datetime`, `to_timestamp`) and timezone detection (`to_timezone). - Cache files are automatically valid for the the current date unless specified otherwise. This is to mimic the current behaviour used in several classes. - The logger supports rotating log files to prevent excessive log file size. - The `to_datetime` and `to_timestamp`functions support a wide variety of input types and formats. They provide the time conversion that is e.g. used in PVForecast. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> * Improve testability of PVForecast Improvements for testing of PVForecast - Use common utility functions to allow for general testing at one spot. - to_datetime - CacheFileStore - Use logging instead of print to easily capture in testing. - Add validation of the json schema for Akkudoktor PV forecast data. - Allow to create an empty PVForecast instance as base instance for testing. - Make process_data() complete for filling a PVForecast instance for testing. - Normalize forecast datetime to timezone of system given in loaded data. - Do not print report but provide report for test checks. - Get rid of cache file path using the CachFileStore to automate cache file usage. - Improved module documentation. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> * Add test for PVForecast and newly extracted utility modules. - Add test for PVForecast - Add test for CacheFileStore in the new cachefilestore module - Add test for to_datetime, to_timestamp, to_timezone in the new datetimeutil module - Add test for get_logger in the new logutil module Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> --------- Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> Co-authored-by: Normann <github@koldrack.com>
This commit is contained in:
282
tests/test_pv_forecast.py
Normal file
282
tests/test_pv_forecast.py
Normal file
@@ -0,0 +1,282 @@
|
||||
"""Test Module for PV Power Forecasting Module.
|
||||
|
||||
This test module is designed to verify the functionality of the `PVForecast` class
|
||||
and its methods in the `class_pv_forecast` module. The tests include validation for
|
||||
forecast data processing, updating AC power measurements, retrieving forecast data,
|
||||
and caching behavior.
|
||||
|
||||
Fixtures:
|
||||
sample_forecast_data: Provides sample forecast data in JSON format for testing.
|
||||
pv_forecast_instance: Provides an instance of `PVForecast` class with sample data loaded.
|
||||
|
||||
Test Cases:
|
||||
- test_generate_cache_filename: Verifies correct cache filename generation based on URL and date.
|
||||
- test_update_ac_power_measurement: Tests updating AC power measurement for a matching date.
|
||||
- test_update_ac_power_measurement_no_match: Ensures no updates occur when there is no matching date.
|
||||
- test_get_temperature_forecast_for_date: Tests retrieving the temperature forecast for a specific date.
|
||||
- test_get_pv_forecast_for_date_range: Verifies retrieval of AC power forecast for a specified date range.
|
||||
- test_get_forecast_dataframe: Ensures forecast data can be correctly converted into a Pandas DataFrame.
|
||||
- test_cache_loading: Tests loading forecast data from a cached file to ensure caching works as expected.
|
||||
|
||||
Usage:
|
||||
This test module uses `pytest` and requires the `akkudoktoreos.class_pv_forecast.py` module to be present.
|
||||
Run the tests using the command: `pytest test_pv_forecast.py`.
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from akkudoktoreos.class_pv_forecast import PVForecast, validate_pv_forecast_data
|
||||
from akkudoktoreos.datetimeutil import to_datetime
|
||||
|
||||
DIR_TESTDATA = Path(__file__).absolute().parent.joinpath("testdata")
|
||||
|
||||
FILE_TESTDATA_PV_FORECAST_INPUT_1 = DIR_TESTDATA.joinpath("pv_forecast_input_1.json")
|
||||
FILE_TESTDATA_PV_FORECAST_RESULT_1 = DIR_TESTDATA.joinpath("pv_forecast_result_1.txt")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_forecast_data():
|
||||
"""Fixture that returns sample forecast data."""
|
||||
with open(FILE_TESTDATA_PV_FORECAST_INPUT_1, "r") as f_in:
|
||||
input_data = json.load(f_in)
|
||||
return input_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_forecast_report():
|
||||
"""Fixture that returns sample forecast data report."""
|
||||
with open(FILE_TESTDATA_PV_FORECAST_RESULT_1, "r") as f_res:
|
||||
input_data = f_res.read()
|
||||
return input_data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_forecast_start(sample_forecast_data):
|
||||
"""Fixture that returns the start date of the sample forecast data."""
|
||||
forecast_start_str = sample_forecast_data["values"][0][0]["datetime"]
|
||||
assert forecast_start_str == "2024-10-06T00:00:00.000+02:00"
|
||||
|
||||
timezone_name = sample_forecast_data["meta"]["timezone"]
|
||||
assert timezone_name == "Europe/Berlin"
|
||||
|
||||
forecast_start = to_datetime(forecast_start_str, to_timezone=timezone_name, to_naiv=True)
|
||||
assert forecast_start == datetime(2024, 10, 6)
|
||||
|
||||
return forecast_start
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pv_forecast_empty_instance():
|
||||
"""Fixture that returns an empty instance of PVForecast."""
|
||||
empty_instance = PVForecast()
|
||||
assert empty_instance.get_forecast_start() is None
|
||||
|
||||
return empty_instance
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pv_forecast_instance(sample_forecast_data, sample_forecast_start):
|
||||
"""Fixture that returns an instance of PVForecast with sample data loaded."""
|
||||
pv_forecast = PVForecast(
|
||||
data=sample_forecast_data,
|
||||
forecast_start=sample_forecast_start,
|
||||
prediction_hours=48,
|
||||
)
|
||||
return pv_forecast
|
||||
|
||||
|
||||
def test_validate_pv_forecast_data(sample_forecast_data):
|
||||
"""Test validation of PV forecast data on sample data."""
|
||||
ret = validate_pv_forecast_data({})
|
||||
assert ret is None
|
||||
|
||||
ret = validate_pv_forecast_data(sample_forecast_data)
|
||||
assert ret == "Akkudoktor"
|
||||
|
||||
|
||||
def test_process_data(sample_forecast_data, sample_forecast_start):
|
||||
"""Test data processing using sample data."""
|
||||
pv_forecast_instance = PVForecast(forecast_start=sample_forecast_start)
|
||||
|
||||
# Assure the start date is correctly set by init funtion
|
||||
forecast_start = pv_forecast_instance.get_forecast_start()
|
||||
expected_start = sample_forecast_start
|
||||
assert forecast_start == expected_start
|
||||
|
||||
# Assure the prediction hours are unset
|
||||
assert pv_forecast_instance.prediction_hours is None
|
||||
|
||||
# Load forecast with sample data - throws exceptions on error
|
||||
pv_forecast_instance.process_data(data=sample_forecast_data)
|
||||
|
||||
|
||||
def test_update_ac_power_measurement(pv_forecast_instance, sample_forecast_start):
|
||||
"""Test updating AC power measurement for a specific date."""
|
||||
forecast_start = pv_forecast_instance.get_forecast_start()
|
||||
assert forecast_start == sample_forecast_start
|
||||
|
||||
updated = pv_forecast_instance.update_ac_power_measurement(forecast_start, 1000)
|
||||
assert updated is True
|
||||
forecast_data = pv_forecast_instance.get_forecast_data()
|
||||
assert forecast_data[0].ac_power_measurement == 1000
|
||||
|
||||
|
||||
def test_update_ac_power_measurement_no_match(pv_forecast_instance):
|
||||
"""Test updating AC power measurement where no date matches."""
|
||||
date_time = datetime(2023, 10, 2, 1, 0, 0)
|
||||
updated = pv_forecast_instance.update_ac_power_measurement(date_time, 1000)
|
||||
assert not updated
|
||||
|
||||
|
||||
def test_get_temperature_forecast_for_date(pv_forecast_instance, sample_forecast_start):
|
||||
"""Test fetching temperature forecast for a specific date."""
|
||||
forecast_temps = pv_forecast_instance.get_temperature_forecast_for_date(sample_forecast_start)
|
||||
assert len(forecast_temps) == 24
|
||||
assert forecast_temps[0] == 7.0
|
||||
assert forecast_temps[1] == 6.5
|
||||
assert forecast_temps[2] == 6.0
|
||||
|
||||
# Assure function bails out if there is no timezone name available for the system.
|
||||
tz_name = pv_forecast_instance._tz_name
|
||||
pv_forecast_instance._tz_name = None
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
forecast_temps = pv_forecast_instance.get_temperature_forecast_for_date(
|
||||
sample_forecast_start
|
||||
)
|
||||
pv_forecast_instance._tz_name = tz_name
|
||||
assert (
|
||||
exc_info.value.args[0] == "Processing without PV system timezone info ist not implemented!"
|
||||
)
|
||||
|
||||
|
||||
def test_get_temperature_for_date_range(pv_forecast_instance, sample_forecast_start):
|
||||
"""Test fetching temperature forecast for a specific date range."""
|
||||
end_date = sample_forecast_start + timedelta(hours=24)
|
||||
forecast_temps = pv_forecast_instance.get_temperature_for_date_range(
|
||||
sample_forecast_start, end_date
|
||||
)
|
||||
assert len(forecast_temps) == 48
|
||||
assert forecast_temps[0] == 7.0
|
||||
assert forecast_temps[1] == 6.5
|
||||
assert forecast_temps[2] == 6.0
|
||||
|
||||
# Assure function bails out if there is no timezone name available for the system.
|
||||
tz_name = pv_forecast_instance._tz_name
|
||||
pv_forecast_instance._tz_name = None
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
forecast_temps = pv_forecast_instance.get_temperature_for_date_range(
|
||||
sample_forecast_start, end_date
|
||||
)
|
||||
pv_forecast_instance._tz_name = tz_name
|
||||
assert (
|
||||
exc_info.value.args[0] == "Processing without PV system timezone info ist not implemented!"
|
||||
)
|
||||
|
||||
|
||||
def test_get_forecast_for_date_range(pv_forecast_instance, sample_forecast_start):
|
||||
"""Test fetching AC power forecast for a specific date range."""
|
||||
end_date = sample_forecast_start + timedelta(hours=24)
|
||||
forecast = pv_forecast_instance.get_pv_forecast_for_date_range(sample_forecast_start, end_date)
|
||||
assert len(forecast) == 48
|
||||
assert forecast[0] == 0.0
|
||||
assert forecast[1] == 0.0
|
||||
assert forecast[2] == 0.0
|
||||
|
||||
# Assure function bails out if there is no timezone name available for the system.
|
||||
tz_name = pv_forecast_instance._tz_name
|
||||
pv_forecast_instance._tz_name = None
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
forecast = pv_forecast_instance.get_pv_forecast_for_date_range(
|
||||
sample_forecast_start, end_date
|
||||
)
|
||||
pv_forecast_instance._tz_name = tz_name
|
||||
assert (
|
||||
exc_info.value.args[0] == "Processing without PV system timezone info ist not implemented!"
|
||||
)
|
||||
|
||||
|
||||
def test_get_forecast_dataframe(pv_forecast_instance):
|
||||
"""Test converting forecast data to a DataFrame."""
|
||||
df = pv_forecast_instance.get_forecast_dataframe()
|
||||
assert len(df) == 288
|
||||
assert list(df.columns) == ["date_time", "dc_power", "ac_power", "windspeed_10m", "temperature"]
|
||||
assert df.iloc[0]["dc_power"] == 0.0
|
||||
assert df.iloc[1]["ac_power"] == 0.0
|
||||
assert df.iloc[2]["temperature"] == 6.0
|
||||
|
||||
|
||||
def test_load_data_from_file(server, pv_forecast_empty_instance):
|
||||
"""Test loading data from file."""
|
||||
# load from valid address file path
|
||||
filepath = FILE_TESTDATA_PV_FORECAST_INPUT_1
|
||||
data = pv_forecast_empty_instance.load_data_from_file(filepath)
|
||||
assert len(data) > 0
|
||||
|
||||
|
||||
def test_load_data_from_url(server, pv_forecast_empty_instance):
|
||||
"""Test loading data from url."""
|
||||
# load from valid address of our server
|
||||
url = f"{server}/gesamtlast_simple?year_energy=2000&"
|
||||
data = pv_forecast_empty_instance.load_data_from_url(url)
|
||||
assert len(data) > 0
|
||||
|
||||
# load from invalid address of our server
|
||||
url = f"{server}/invalid?"
|
||||
data = pv_forecast_empty_instance.load_data_from_url(url)
|
||||
assert data == f"Failed to load data from `{url}`. Status Code: 404"
|
||||
|
||||
|
||||
def test_load_data_from_url_with_caching(
|
||||
server, pv_forecast_empty_instance, sample_forecast_data, sample_forecast_start
|
||||
):
|
||||
"""Test loading data from url with cache."""
|
||||
# load from valid address of our server
|
||||
url = f"{server}/gesamtlast_simple?year_energy=2000&"
|
||||
data = pv_forecast_empty_instance.load_data_from_url_with_caching(url)
|
||||
assert len(data) > 0
|
||||
|
||||
# load from invalid address of our server
|
||||
url = f"{server}/invalid?"
|
||||
data = pv_forecast_empty_instance.load_data_from_url_with_caching(url)
|
||||
assert data == f"Failed to load data from `{url}`. Status Code: 404"
|
||||
|
||||
|
||||
def test_report_ac_power_and_measurement(pv_forecast_instance, sample_forecast_report):
|
||||
"""Test reporting."""
|
||||
report = pv_forecast_instance.report_ac_power_and_measurement()
|
||||
assert report == sample_forecast_report
|
||||
|
||||
|
||||
def test_timezone_behaviour(
|
||||
pv_forecast_instance, sample_forecast_report, sample_forecast_start, other_timezone
|
||||
):
|
||||
"""Test PVForecast in another timezone."""
|
||||
current_time = datetime.now()
|
||||
|
||||
# Test updating AC power measurement for a specific date.
|
||||
date_time = pv_forecast_instance.get_forecast_start()
|
||||
assert date_time == sample_forecast_start
|
||||
updated = pv_forecast_instance.update_ac_power_measurement(date_time, 1000)
|
||||
assert updated is True
|
||||
forecast_data = pv_forecast_instance.get_forecast_data()
|
||||
assert forecast_data[0].ac_power_measurement == 1000
|
||||
|
||||
# Test fetching temperature forecast for a specific date.
|
||||
forecast_temps = pv_forecast_instance.get_temperature_forecast_for_date(sample_forecast_start)
|
||||
assert len(forecast_temps) == 24
|
||||
assert forecast_temps[0] == 7.0
|
||||
assert forecast_temps[1] == 6.5
|
||||
assert forecast_temps[2] == 6.0
|
||||
|
||||
# Test fetching AC power forecast
|
||||
end_date = sample_forecast_start + timedelta(hours=24)
|
||||
forecast = pv_forecast_instance.get_pv_forecast_for_date_range(sample_forecast_start, end_date)
|
||||
assert len(forecast) == 48
|
||||
assert forecast[0] == 1000.0 # changed before
|
||||
assert forecast[1] == 0.0
|
||||
assert forecast[2] == 0.0
|
||||
Reference in New Issue
Block a user