From c1dee07a2c4ad4c3f9470829f43e130ef7176d36 Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 26 Oct 2023 14:06:33 +0200 Subject: [PATCH 1/9] feat[DEI-54]: show more specific error in case of an AttributeError, the more specific error thrown in an underlying parse function was passed through, but not shown, giving the user little info on what is wrong with the input file --- decoimpact/data/entities/data_access_layer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/decoimpact/data/entities/data_access_layer.py b/decoimpact/data/entities/data_access_layer.py index e83153f6..1cfc95ef 100644 --- a/decoimpact/data/entities/data_access_layer.py +++ b/decoimpact/data/entities/data_access_layer.py @@ -61,8 +61,7 @@ def read_input_file(self, path: Path) -> IModelData: try: yaml_data = model_data_builder.parse_yaml_data(contents) except AttributeError as exc: - raise AttributeError("Error reading input file") from exc - + raise AttributeError(f"Error reading input file. {exc}") from exc return yaml_data def read_input_dataset(self, dataset_data: IDatasetData) -> _xr.Dataset: @@ -123,8 +122,11 @@ def read_input_dataset(self, dataset_data: IDatasetData) -> _xr.Dataset: return dataset def write_output_file( - self, dataset: _xr.Dataset, path: Path, application_version: str, - application_name: str + self, + dataset: _xr.Dataset, + path: Path, + application_version: str, + application_name: str, ) -> None: """Write XArray dataset to specified path From 25658cb2eb6e6c6ffd2fd1f052b95af55839a55d Mon Sep 17 00:00:00 2001 From: Wouter Date: Thu, 26 Oct 2023 16:17:14 +0200 Subject: [PATCH 2/9] feat[DEI54]: summarize warnings for value exceedings in step function rule --- decoimpact/business/entities/rule_processor.py | 17 ++++++++++++++++- .../entities/rules/step_function_rule.py | 15 +++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/decoimpact/business/entities/rule_processor.py b/decoimpact/business/entities/rule_processor.py index 03b61d98..5cd1dcb8 100644 --- a/decoimpact/business/entities/rule_processor.py +++ b/decoimpact/business/entities/rule_processor.py @@ -245,8 +245,23 @@ def _process_by_cell( np_array = input_variable.to_numpy() result_variable = _np.zeros_like(np_array) + # define variables to count value exceedings (for some rules) + warn_min_total = 0 + warn_max_total = 0 + warn_min = 0 + warn_max = 0 + + # execute rule and gather warnings for exceeded values (for some rules) for indices, value in _np.ndenumerate(np_array): - result_variable[indices] = rule.execute(value, logger) + result_variable[indices], warn_min, warn_max = rule.execute(value, logger) + warn_min_total += warn_min + warn_max_total += warn_max + + # show warnings values outside range (for some rules): + if warn_min_total > 0: + logger.log_warning(f"value less than min: {warn_min_total} occurence(s)") + if warn_max_total > 0: + logger.log_warning(f"value greater than max: {warn_max_total} occurence(s)") # use copy to get the same dimensions as the # original input variable diff --git a/decoimpact/business/entities/rules/step_function_rule.py b/decoimpact/business/entities/rules/step_function_rule.py index 80827b13..48ac2c3e 100644 --- a/decoimpact/business/entities/rules/step_function_rule.py +++ b/decoimpact/business/entities/rules/step_function_rule.py @@ -73,7 +73,7 @@ def validate(self, logger: ILogger) -> bool: return False return True - def execute(self, value: float, logger: ILogger) -> float: + def execute(self, value: float, logger: ILogger): """Classify a variable, based on given bins. Values lower than lowest bin will produce a warning and will be assigned class 0. @@ -86,6 +86,8 @@ def execute(self, value: float, logger: ILogger) -> float: Returns: float: response corresponding to value to classify + int: number of warnings less than minimum + int: number of warnings greater than maximum """ bins = self._limits @@ -93,14 +95,19 @@ def execute(self, value: float, logger: ILogger) -> float: # bins are constant selected_bin = -1 + warn_min = 0 + warn_max = 0 if _np.isnan(value): return value if value < _np.min(bins): - logger.log_warning("value less than min") + # logger.log_warning("value less than min") + warn_min += 1 selected_bin = 0 else: selected_bin = _np.digitize(value, bins) - 1 if value > _np.max(bins): - logger.log_warning("value greater than max") + warn_max += 1 + # if warn_max > 0: + # logger.log_warning(f"value greater than max: {warn_max} occurence(s)") - return responses[selected_bin] + return responses[selected_bin], warn_min, warn_max From 1eb24c096dfc952f561c1560633e4cce72a2411f Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 30 Oct 2023 11:35:38 +0100 Subject: [PATCH 3/9] feat[DEI-54]: change unit test --- tests/business/entities/test_rule_processor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/business/entities/test_rule_processor.py b/tests/business/entities/test_rule_processor.py index 7bd5c723..ffb17415 100644 --- a/tests/business/entities/test_rule_processor.py +++ b/tests/business/entities/test_rule_processor.py @@ -284,7 +284,7 @@ def test_process_rules_calls_cell_based_rule_execute_correctly(): rule.input_variable_names = ["test"] rule.output_variable_name = "output" - rule.execute.return_value = 1 + rule.execute.return_value = [1, 0, 0] processor = RuleProcessor([rule], dataset) @@ -298,6 +298,7 @@ def test_process_rules_calls_cell_based_rule_execute_correctly(): assert rule.execute.call_count == 6 + def test_process_rules_calls_multi_cell_based_rule_execute_correctly(): """Tests if during processing the rule its execute method of an IMultiCellBasedRule is called with the right parameter.""" @@ -330,6 +331,7 @@ def test_process_rules_calls_multi_cell_based_rule_execute_correctly(): assert rule.execute.call_count == 6 + def test_process_rules_calls_array_based_rule_execute_correctly(): """Tests if during processing the rule its execute method of an IArrayBasedRule is called with the right parameter.""" From 1d80991d04c6d5d0a96bb14a06fb323dea2eaa8f Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 30 Oct 2023 11:52:14 +0100 Subject: [PATCH 4/9] feat[DEI-54]: pass one list with both warning counters (min and max) --- .../business/entities/rule_processor.py | 27 ++++++++++--------- .../entities/rules/step_function_rule.py | 14 +++++----- .../business/entities/test_rule_processor.py | 3 ++- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/decoimpact/business/entities/rule_processor.py b/decoimpact/business/entities/rule_processor.py index 5cd1dcb8..db989418 100644 --- a/decoimpact/business/entities/rule_processor.py +++ b/decoimpact/business/entities/rule_processor.py @@ -245,23 +245,26 @@ def _process_by_cell( np_array = input_variable.to_numpy() result_variable = _np.zeros_like(np_array) - # define variables to count value exceedings (for some rules) - warn_min_total = 0 - warn_max_total = 0 - warn_min = 0 - warn_max = 0 + # define variables to count value exceedings (for some rules): min and max + warning_counter = [0, 0] + warning_counter_total = [0, 0] # execute rule and gather warnings for exceeded values (for some rules) for indices, value in _np.ndenumerate(np_array): - result_variable[indices], warn_min, warn_max = rule.execute(value, logger) - warn_min_total += warn_min - warn_max_total += warn_max + result_variable[indices], warning_counter = rule.execute(value, logger) + # update total counter for both min and max + warning_counter_total[0] += warning_counter[0] + warning_counter_total[1] += warning_counter[1] # show warnings values outside range (for some rules): - if warn_min_total > 0: - logger.log_warning(f"value less than min: {warn_min_total} occurence(s)") - if warn_max_total > 0: - logger.log_warning(f"value greater than max: {warn_max_total} occurence(s)") + if warning_counter_total[0] > 0: + logger.log_warning( + f"value less than min: {warning_counter_total[0]} occurence(s)" + ) + if warning_counter_total[1] > 0: + logger.log_warning( + f"value greater than max: {warning_counter_total[1]} occurence(s)" + ) # use copy to get the same dimensions as the # original input variable diff --git a/decoimpact/business/entities/rules/step_function_rule.py b/decoimpact/business/entities/rules/step_function_rule.py index 48ac2c3e..042b23be 100644 --- a/decoimpact/business/entities/rules/step_function_rule.py +++ b/decoimpact/business/entities/rules/step_function_rule.py @@ -95,19 +95,17 @@ def execute(self, value: float, logger: ILogger): # bins are constant selected_bin = -1 - warn_min = 0 - warn_max = 0 + warning_counter = [0, 0] if _np.isnan(value): return value if value < _np.min(bins): - # logger.log_warning("value less than min") - warn_min += 1 + # count warning exceeding min: + warning_counter[0] = 1 selected_bin = 0 else: selected_bin = _np.digitize(value, bins) - 1 if value > _np.max(bins): - warn_max += 1 - # if warn_max > 0: - # logger.log_warning(f"value greater than max: {warn_max} occurence(s)") + # count warning exceeding max: + warning_counter[1] = 1 - return responses[selected_bin], warn_min, warn_max + return responses[selected_bin], warning_counter diff --git a/tests/business/entities/test_rule_processor.py b/tests/business/entities/test_rule_processor.py index ffb17415..f330ed0a 100644 --- a/tests/business/entities/test_rule_processor.py +++ b/tests/business/entities/test_rule_processor.py @@ -284,7 +284,8 @@ def test_process_rules_calls_cell_based_rule_execute_correctly(): rule.input_variable_names = ["test"] rule.output_variable_name = "output" - rule.execute.return_value = [1, 0, 0] + # expected return value = 1; number of warnings (min and max) = 0 and 0 + rule.execute.return_value = [1, [0, 0]] processor = RuleProcessor([rule], dataset) From 0b70e15da5e6ae0aa1d651a499b33a79ad974b03 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 30 Oct 2023 21:36:49 +0100 Subject: [PATCH 5/9] feat[DEI-54]: fix unit tests (but one) --- .../entities/rules/test_step_function_rule.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/business/entities/rules/test_step_function_rule.py b/tests/business/entities/rules/test_step_function_rule.py index e462ec73..e701373c 100644 --- a/tests/business/entities/rules/test_step_function_rule.py +++ b/tests/business/entities/rules/test_step_function_rule.py @@ -46,10 +46,10 @@ def test_create_step_function(example_rule): @pytest.mark.parametrize( "input_value, expected_output_value", [ - (0.5, 10), - (1.5, 11), - (2.5, 12), - (5.5, 15), + (0.5, (10, [0, 0])), + (1.5, (11, [0, 0])), + (2.5, (12, [0, 0])), + (5.5, (15, [0, 0])), ], ) def test_execute_values_between_limits( @@ -68,7 +68,13 @@ def test_execute_values_between_limits( @pytest.mark.parametrize( "input_value, expected_output_value", - [(0, 10), (1, 11), (2, 12), (5, 15), (10, 20)], + [ + (0, (10, [0, 0])), + (1, (11, [0, 0])), + (2, (12, [0, 0])), + (5, (15, [0, 0])), + (10, (20, [0, 0])), + ], ) def test_execute_values_at_limits( example_rule, input_value: int, expected_output_value: int @@ -86,7 +92,10 @@ def test_execute_values_at_limits( @pytest.mark.parametrize( "input_value, expected_output_value, expected_log_message", - [(-1, 10, "value less than min"), (11, 20, "value greater than max")], + [ + (-1, (10, [1, 0]), "value less than min: 1 occurence(s)"), + (11, (20, [0, 1]), "value greater than max: 1 occurence(s)"), + ], ) def test_execute_values_outside_limits( example_rule, @@ -164,7 +173,14 @@ def fixture_example_rule_combined(): @pytest.mark.parametrize( "input_value, expected_output_value", - [(-1, 22), (0.5, 22), (1.5, 15), (2.5, 10), (5.5, 12), (10.5, 20)], + [ + (-1, (22, [1, 0])), + (0.5, (22, [0, 0])), + (1.5, (15, [0, 0])), + (2.5, (10, [0, 0])), + (5.5, (12, [0, 0])), + (10.5, (20, [0, 1])), + ], ) def test_execute_values_combined_dec_inc( example_rule_combined, From 9ea9c5cf895c22d8024b9530534fb12611681e64 Mon Sep 17 00:00:00 2001 From: Wouter Date: Mon, 30 Oct 2023 22:13:16 +0100 Subject: [PATCH 6/9] feat[DEI-54]: summarize warnings for response curve rule --- .../entities/rules/response_curve_rule.py | 15 +++++++++------ .../business/entities/rules/step_function_rule.py | 3 +-- .../entities/rules/test_response_curve_rule.py | 15 +++++++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/decoimpact/business/entities/rules/response_curve_rule.py b/decoimpact/business/entities/rules/response_curve_rule.py index ea2b0a05..60d367c8 100644 --- a/decoimpact/business/entities/rules/response_curve_rule.py +++ b/decoimpact/business/entities/rules/response_curve_rule.py @@ -25,7 +25,6 @@ def __init__( output_variable_name="output", description: str = "", ): - super().__init__(name, [input_variable_name], output_variable_name, description) self._input_values = _np.array(input_values) @@ -62,18 +61,22 @@ def execute(self, value: float, logger: ILogger): Returns: float: response corresponding to value to classify + int[]: number of warnings less than minimum and greater than maximum """ values_input = self._input_values values_output = self._output_values + warning_counter = [0, 0] # values are constant if value < _np.min(values_input): - logger.log_warning("value less than min") - return values_output[0] + # count warning exceeding min: + warning_counter[0] = 1 + return values_output[0], warning_counter if value > _np.max(values_input): - logger.log_warning("value greater than max") - return values_output[-1] + # count warning exceeding max: + warning_counter[1] = 1 + return values_output[-1], warning_counter - return _np.interp(value, values_input, values_output) + return _np.interp(value, values_input, values_output), warning_counter diff --git a/decoimpact/business/entities/rules/step_function_rule.py b/decoimpact/business/entities/rules/step_function_rule.py index 042b23be..df9b1146 100644 --- a/decoimpact/business/entities/rules/step_function_rule.py +++ b/decoimpact/business/entities/rules/step_function_rule.py @@ -86,8 +86,7 @@ def execute(self, value: float, logger: ILogger): Returns: float: response corresponding to value to classify - int: number of warnings less than minimum - int: number of warnings greater than maximum + int[]: number of warnings less than minimum and greater than maximum """ bins = self._limits diff --git a/tests/business/entities/rules/test_response_curve_rule.py b/tests/business/entities/rules/test_response_curve_rule.py index ac923be8..6fe2b776 100644 --- a/tests/business/entities/rules/test_response_curve_rule.py +++ b/tests/business/entities/rules/test_response_curve_rule.py @@ -47,7 +47,7 @@ def test_create_response_rule(example_rule): @pytest.mark.parametrize( "input_value, expected_output_value", - [(25, 0.5), (75, 1.1), (770, 2.1)], + [(25, (0.5, [0, 0])), (75, (1.1, [0, 0])), (770, (2.1, [0, 0]))], ) def test_execute_response_rule_values_between_limits( example_rule, input_value: int, expected_output_value: float @@ -66,8 +66,8 @@ def test_execute_response_rule_values_between_limits( @pytest.mark.parametrize( "input_value, expected_output_value, expected_log_message", [ - (-1, 0, "value less than min"), - (6000, 3, "value greater than max"), + (-1, (0, [1, 0]), "value less than min: 1 occurence(s)"), + (6000, (3, [0, 1]), "value greater than max: 1 occurence(s)"), ], ) def test_execute_response_rule_values_outside_limits( @@ -131,7 +131,14 @@ def fixture_example_rule_combined(): @pytest.mark.parametrize( "input_value, expected_output_value", - [(-1, 22), (0.5, 18.5), (1.5, 12.5), (3.5, 11), (7.5, 16), (10.5, 20)], + [ + (-1, (22, [1, 0])), + (0.5, (18.5, [0, 0])), + (1.5, (12.5, [0, 0])), + (3.5, (11, [0, 0])), + (7.5, (16, [0, 0])), + (10.5, (20, [0, 1])), + ], ) def test_execute_values_combined_dec_inc( example_rule_combined, From 404d1a166205a01387cf58e90d3d458658b65e63 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 31 Oct 2023 14:57:22 +0100 Subject: [PATCH 7/9] feat[DEI-54]: move unit test to test_rule_processor --- .../rules/test_response_curve_rule.py | 27 ++--------- .../entities/rules/test_step_function_rule.py | 30 +++--------- .../business/entities/test_rule_processor.py | 48 +++++++++++++++++++ 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/tests/business/entities/rules/test_response_curve_rule.py b/tests/business/entities/rules/test_response_curve_rule.py index 6fe2b776..ec40832e 100644 --- a/tests/business/entities/rules/test_response_curve_rule.py +++ b/tests/business/entities/rules/test_response_curve_rule.py @@ -13,7 +13,10 @@ import numpy as _np import pytest +import xarray as _xr +from decoimpact.business.entities.rule_processor import RuleProcessor +from decoimpact.business.entities.rules.i_cell_based_rule import ICellBasedRule from decoimpact.business.entities.rules.response_curve_rule import ResponseCurveRule from decoimpact.crosscutting.i_logger import ILogger @@ -63,30 +66,6 @@ def test_execute_response_rule_values_between_limits( logger.log_warning.assert_not_called() -@pytest.mark.parametrize( - "input_value, expected_output_value, expected_log_message", - [ - (-1, (0, [1, 0]), "value less than min: 1 occurence(s)"), - (6000, (3, [0, 1]), "value greater than max: 1 occurence(s)"), - ], -) -def test_execute_response_rule_values_outside_limits( - example_rule, - input_value: int, - expected_output_value: int, - expected_log_message: str, -): - """ - Test the function execution with input values outside the interval limits. - """ - # Arrange - logger = Mock(ILogger) - - # Assert - assert example_rule.execute(input_value, logger) == expected_output_value - logger.log_warning.assert_called_with(expected_log_message) - - def test_inputs_and_outputs_have_different_lengths(example_rule): """ Test the function execution when input and outputs have different lengths diff --git a/tests/business/entities/rules/test_step_function_rule.py b/tests/business/entities/rules/test_step_function_rule.py index e701373c..e3bd4b02 100644 --- a/tests/business/entities/rules/test_step_function_rule.py +++ b/tests/business/entities/rules/test_step_function_rule.py @@ -9,10 +9,16 @@ """ +from typing import Dict, List + import numpy as _np import pytest +import xarray as _xr from mock import Mock +from decoimpact.business.entities.rule_processor import RuleProcessor +from decoimpact.business.entities.rules.i_cell_based_rule import ICellBasedRule +from decoimpact.business.entities.rules.i_rule import IRule from decoimpact.business.entities.rules.step_function_rule import StepFunctionRule from decoimpact.crosscutting.i_logger import ILogger @@ -90,30 +96,6 @@ def test_execute_values_at_limits( logger.log_warning.assert_not_called() -@pytest.mark.parametrize( - "input_value, expected_output_value, expected_log_message", - [ - (-1, (10, [1, 0]), "value less than min: 1 occurence(s)"), - (11, (20, [0, 1]), "value greater than max: 1 occurence(s)"), - ], -) -def test_execute_values_outside_limits( - example_rule, - input_value: int, - expected_output_value: int, - expected_log_message: str, -): - """ - Test the function execution with input values outside the interval limits. - """ - # Arrange - logger = Mock(ILogger) - - # Assert - assert example_rule.execute(input_value, logger) == expected_output_value - logger.log_warning.assert_called_with(expected_log_message) - - def test_limits_and_responses_have_different_lengths(example_rule): """ Test the function execution when limits and responses have different lengths diff --git a/tests/business/entities/test_rule_processor.py b/tests/business/entities/test_rule_processor.py index f330ed0a..443fb9c4 100644 --- a/tests/business/entities/test_rule_processor.py +++ b/tests/business/entities/test_rule_processor.py @@ -27,6 +27,7 @@ IMultiCellBasedRule, ) from decoimpact.business.entities.rules.i_rule import IRule +from decoimpact.business.entities.rules.step_function_rule import StepFunctionRule from decoimpact.business.entities.rules.time_aggregation_rule import TimeAggregationRule from decoimpact.crosscutting.i_logger import ILogger from decoimpact.data.api.i_time_aggregation_rule_data import ITimeAggregationRuleData @@ -71,6 +72,17 @@ def _create_test_rules() -> List[IRule]: return [rule1, rule2, rule3, rule4] +@pytest.fixture(name="example_rule") +def fixture_example_rule(): + """Inititaion of StepFunctionRule to be reused in the following tests""" + return StepFunctionRule( + "rule_name", + "input_variable_name", + [0, 1, 2, 5, 10], + [10, 11, 12, 15, 20], + ) + + def test_creating_rule_processor_without_rules_should_throw_exception(): """ Tests if absence of rules is correctly checked during creation of the processor. @@ -508,3 +520,39 @@ def test_execute_rule_throws_error_for_unknown_input_variable(): + "in input datasets or in calculated output dataset." ) assert exception_raised.args[0] == expected_message + + +@pytest.mark.parametrize( + "input_value, expected_output_value, expected_log_message", + [ + (-1, (10, [1, 0]), "value less than min: 1 occurence(s)"), + (11, (20, [0, 1]), "value greater than max: 1 occurence(s)"), + ], +) +def test_process_values_outside_limits( + example_rule, + input_value: int, + expected_output_value: int, + expected_log_message: str, +): + """ + Test the function execution with input values outside the interval limits. + """ + # Arrange + logger = Mock(ILogger) + dataset = _xr.Dataset() + dataset["test1"] = _xr.DataArray(input_value) + rule = Mock(ICellBasedRule) + rule.input_variable_names = ["test1"] + rule.output_variable_name = "output" + rule.execute.return_value = expected_output_value + processor = RuleProcessor([rule], dataset) + + # Act + assert processor.initialize(logger) + processor.process_rules(dataset, logger) + + # Assert + assert example_rule.execute(input_value, logger) == expected_output_value + processor.process_rules(dataset, logger) + logger.log_warning.assert_called_with(expected_log_message) From d6fd452ec90df67300c7d05ff0a66afff975a480 Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 31 Oct 2023 15:34:56 +0100 Subject: [PATCH 8/9] feat[DEI-54]: account for NaN values: then also return counter --- decoimpact/business/entities/rules/step_function_rule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoimpact/business/entities/rules/step_function_rule.py b/decoimpact/business/entities/rules/step_function_rule.py index df9b1146..6caccb13 100644 --- a/decoimpact/business/entities/rules/step_function_rule.py +++ b/decoimpact/business/entities/rules/step_function_rule.py @@ -96,7 +96,7 @@ def execute(self, value: float, logger: ILogger): selected_bin = -1 warning_counter = [0, 0] if _np.isnan(value): - return value + return value, warning_counter if value < _np.min(bins): # count warning exceeding min: warning_counter[0] = 1 From c95b4b5bf1550e163a3aa4a5742bd8993f6c7a4e Mon Sep 17 00:00:00 2001 From: Wouter Date: Tue, 31 Oct 2023 16:58:25 +0100 Subject: [PATCH 9/9] feat[DEI-54]: move fixture down --- .../business/entities/test_rule_processor.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/business/entities/test_rule_processor.py b/tests/business/entities/test_rule_processor.py index 443fb9c4..cda263f6 100644 --- a/tests/business/entities/test_rule_processor.py +++ b/tests/business/entities/test_rule_processor.py @@ -72,17 +72,6 @@ def _create_test_rules() -> List[IRule]: return [rule1, rule2, rule3, rule4] -@pytest.fixture(name="example_rule") -def fixture_example_rule(): - """Inititaion of StepFunctionRule to be reused in the following tests""" - return StepFunctionRule( - "rule_name", - "input_variable_name", - [0, 1, 2, 5, 10], - [10, 11, 12, 15, 20], - ) - - def test_creating_rule_processor_without_rules_should_throw_exception(): """ Tests if absence of rules is correctly checked during creation of the processor. @@ -522,6 +511,17 @@ def test_execute_rule_throws_error_for_unknown_input_variable(): assert exception_raised.args[0] == expected_message +@pytest.fixture(name="example_rule") +def fixture_example_rule(): + """Inititaion of StepFunctionRule to be reused in the following tests""" + return StepFunctionRule( + "rule_name", + "input_variable_name", + [0, 1, 2, 5, 10], + [10, 11, 12, 15, 20], + ) + + @pytest.mark.parametrize( "input_value, expected_output_value, expected_log_message", [