|
1 | 1 | import datetime |
2 | | -import unittest |
3 | 2 | from unittest.mock import MagicMock |
4 | 3 |
|
5 | 4 | import numpy as np |
6 | 5 | import pandas as pd |
| 6 | +import pytest |
7 | 7 |
|
8 | 8 | from climada.entity.exposures import Exposures |
9 | 9 | from climada.entity.impact_funcs import ImpactFunc, ImpactFuncSet |
|
12 | 12 | from climada.trajectories.snapshot import Snapshot |
13 | 13 | from climada.util.constants import EXP_DEMO_H5, HAZ_DEMO_H5 |
14 | 14 |
|
| 15 | +# --- Fixtures --- |
| 16 | + |
| 17 | + |
| 18 | +@pytest.fixture(scope="module") |
| 19 | +def shared_data(): |
| 20 | + """Load heavy HDF5 data once per module to speed up tests.""" |
| 21 | + exposure = Exposures.from_hdf5(EXP_DEMO_H5) |
| 22 | + hazard = Hazard.from_hdf5(HAZ_DEMO_H5) |
| 23 | + impfset = ImpactFuncSet( |
| 24 | + [ |
| 25 | + ImpactFunc( |
| 26 | + "TC", |
| 27 | + 3, |
| 28 | + intensity=np.array([0, 20]), |
| 29 | + mdd=np.array([0, 0.5]), |
| 30 | + paa=np.array([0, 1]), |
| 31 | + ) |
| 32 | + ] |
| 33 | + ) |
| 34 | + return exposure, hazard, impfset |
15 | 35 |
|
16 | | -class TestSnapshot(unittest.TestCase): |
17 | | - |
18 | | - def setUp(self): |
19 | | - # Create mock objects for testing |
20 | | - self.mock_exposure = Exposures.from_hdf5(EXP_DEMO_H5) |
21 | | - self.mock_hazard = Hazard.from_hdf5(HAZ_DEMO_H5) |
22 | | - self.mock_impfset = ImpactFuncSet( |
23 | | - [ |
24 | | - ImpactFunc( |
25 | | - "TC", |
26 | | - 3, |
27 | | - intensity=np.array([0, 20]), |
28 | | - mdd=np.array([0, 0.5]), |
29 | | - paa=np.array([0, 1]), |
30 | | - ) |
31 | | - ] |
32 | | - ) |
33 | | - self.mock_measure = MagicMock(spec=Measure) |
34 | | - self.mock_measure.name = "Test Measure" |
35 | | - |
36 | | - # Setup mock return values for measure.apply |
37 | | - self.mock_modified_exposure = MagicMock(spec=Exposures) |
38 | | - self.mock_modified_hazard = MagicMock(spec=Hazard) |
39 | | - self.mock_modified_impfset = MagicMock(spec=ImpactFuncSet) |
40 | | - self.mock_measure.apply.return_value = ( |
41 | | - self.mock_modified_exposure, |
42 | | - self.mock_modified_impfset, |
43 | | - self.mock_modified_hazard, |
44 | | - ) |
45 | 36 |
|
46 | | - def test_init_with_int_date(self): |
47 | | - snapshot = Snapshot( |
48 | | - exposure=self.mock_exposure, |
49 | | - hazard=self.mock_hazard, |
50 | | - impfset=self.mock_impfset, |
51 | | - date=2023, |
52 | | - ) |
53 | | - self.assertEqual(snapshot.date, datetime.date(2023, 1, 1)) |
54 | | - |
55 | | - def test_init_with_str_date(self): |
56 | | - snapshot = Snapshot( |
57 | | - exposure=self.mock_exposure, |
58 | | - hazard=self.mock_hazard, |
59 | | - impfset=self.mock_impfset, |
60 | | - date="2023-01-01", |
61 | | - ) |
62 | | - self.assertEqual(snapshot.date, datetime.date(2023, 1, 1)) |
63 | | - |
64 | | - def test_init_with_date_object(self): |
65 | | - date_obj = datetime.date(2023, 1, 1) |
66 | | - snapshot = Snapshot( |
67 | | - exposure=self.mock_exposure, |
68 | | - hazard=self.mock_hazard, |
69 | | - impfset=self.mock_impfset, |
70 | | - date=date_obj, |
71 | | - ) |
72 | | - self.assertEqual(snapshot.date, date_obj) |
73 | | - |
74 | | - def test_init_with_invalid_date(self): |
75 | | - with self.assertRaises(ValueError): |
76 | | - Snapshot( |
77 | | - exposure=self.mock_exposure, |
78 | | - hazard=self.mock_hazard, |
79 | | - impfset=self.mock_impfset, |
80 | | - date="invalid-date", |
81 | | - ) |
| 37 | +@pytest.fixture |
| 38 | +def mock_context(shared_data): |
| 39 | + """Provides the exposure/hazard/impfset and a pre-configured mock measure.""" |
| 40 | + exp, haz, impf = shared_data |
82 | 41 |
|
83 | | - def test_init_with_invalid_type(self): |
84 | | - with self.assertRaises(TypeError): |
85 | | - Snapshot( |
86 | | - exposure=self.mock_exposure, |
87 | | - hazard=self.mock_hazard, |
88 | | - impfset=self.mock_impfset, |
89 | | - date=2023.5, # type: ignore |
90 | | - ) |
| 42 | + # Setup Mock Measure |
| 43 | + mock_measure = MagicMock(spec=Measure) |
| 44 | + mock_measure.name = "Test Measure" |
91 | 45 |
|
92 | | - def test_properties(self): |
93 | | - snapshot = Snapshot( |
94 | | - exposure=self.mock_exposure, |
95 | | - hazard=self.mock_hazard, |
96 | | - impfset=self.mock_impfset, |
97 | | - date=2023, |
98 | | - ) |
| 46 | + modified_exp = MagicMock(spec=Exposures) |
| 47 | + modified_haz = MagicMock(spec=Hazard) |
| 48 | + modified_imp = MagicMock(spec=ImpactFuncSet) |
99 | 49 |
|
100 | | - # We want a new reference |
101 | | - self.assertIsNot(snapshot.exposure, self.mock_exposure) |
102 | | - self.assertIsNot(snapshot.hazard, self.mock_hazard) |
103 | | - self.assertIsNot(snapshot.impfset, self.mock_impfset) |
| 50 | + mock_measure.apply.return_value = (modified_exp, modified_imp, modified_haz) |
104 | 51 |
|
105 | | - # But we want equality |
106 | | - pd.testing.assert_frame_equal(snapshot.exposure.gdf, self.mock_exposure.gdf) |
| 52 | + return { |
| 53 | + "exp": exp, |
| 54 | + "haz": haz, |
| 55 | + "imp": impf, |
| 56 | + "measure": mock_measure, |
| 57 | + "mod_exp": modified_exp, |
| 58 | + "mod_haz": modified_haz, |
| 59 | + "mod_imp": modified_imp, |
| 60 | + } |
107 | 61 |
|
108 | | - self.assertEqual(snapshot.hazard.haz_type, self.mock_hazard.haz_type) |
109 | | - self.assertEqual(snapshot.hazard.intensity.nnz, self.mock_hazard.intensity.nnz) |
110 | | - self.assertEqual(snapshot.hazard.size, self.mock_hazard.size) |
111 | 62 |
|
112 | | - self.assertEqual(snapshot.impfset, self.mock_impfset) |
| 63 | +# --- Tests --- |
113 | 64 |
|
114 | | - def test_apply_measure(self): |
115 | | - snapshot = Snapshot( |
116 | | - exposure=self.mock_exposure, |
117 | | - hazard=self.mock_hazard, |
118 | | - impfset=self.mock_impfset, |
119 | | - date=2023, |
| 65 | + |
| 66 | +def test_not_from_factory_warning(mock_context): |
| 67 | + """Test that direct __init__ call raises a warning""" |
| 68 | + with pytest.warns(UserWarning): |
| 69 | + Snapshot( |
| 70 | + exposure=mock_context["exp"], |
| 71 | + hazard=mock_context["haz"], |
| 72 | + impfset=mock_context["imp"], |
| 73 | + measure=None, |
| 74 | + date=2001, |
120 | 75 | ) |
121 | | - new_snapshot = snapshot.apply_measure(self.mock_measure) |
122 | 76 |
|
123 | | - self.assertIsNotNone(new_snapshot.measure) |
124 | | - self.assertEqual(new_snapshot.measure.name, "Test Measure") # type: ignore |
125 | | - self.assertEqual(new_snapshot.exposure, self.mock_modified_exposure) |
126 | | - self.assertEqual(new_snapshot.hazard, self.mock_modified_hazard) |
127 | | - self.assertEqual(new_snapshot.impfset, self.mock_modified_impfset) |
| 77 | + |
| 78 | +@pytest.mark.parametrize( |
| 79 | + "input_date,expected", |
| 80 | + [ |
| 81 | + (2023, datetime.date(2023, 1, 1)), |
| 82 | + ("2023-01-01", datetime.date(2023, 1, 1)), |
| 83 | + (datetime.date(2023, 1, 1), datetime.date(2023, 1, 1)), |
| 84 | + ], |
| 85 | +) |
| 86 | +def test_init_valid_dates(mock_context, input_date, expected): |
| 87 | + """Test various valid date input formats using parametrization.""" |
| 88 | + snapshot = Snapshot.from_triplet( |
| 89 | + exposure=mock_context["exp"], |
| 90 | + hazard=mock_context["haz"], |
| 91 | + impfset=mock_context["imp"], |
| 92 | + date=input_date, |
| 93 | + ) |
| 94 | + assert snapshot.date == expected |
| 95 | + |
| 96 | + |
| 97 | +def test_init_invalid_date_format(mock_context): |
| 98 | + with pytest.raises(ValueError, match="String must be in the format"): |
| 99 | + Snapshot.from_triplet( |
| 100 | + exposure=mock_context["exp"], |
| 101 | + hazard=mock_context["haz"], |
| 102 | + impfset=mock_context["imp"], |
| 103 | + date="invalid-date", |
| 104 | + ) |
128 | 105 |
|
129 | 106 |
|
130 | | -if __name__ == "__main__": |
131 | | - TESTS = unittest.TestLoader().loadTestsFromTestCase(TestSnapshot) |
132 | | - unittest.TextTestRunner(verbosity=2).run(TESTS) |
| 107 | +def test_init_invalid_date_type(mock_context): |
| 108 | + with pytest.raises( |
| 109 | + TypeError, match=r"date_arg must be an int, str, or datetime.date" |
| 110 | + ): |
| 111 | + Snapshot.from_triplet(exposure=mock_context["exp"], hazard=mock_context["haz"], impfset=mock_context["imp"], date=2023.5) # type: ignore |
| 112 | + |
| 113 | + |
| 114 | +def test_properties(mock_context): |
| 115 | + snapshot = Snapshot.from_triplet( |
| 116 | + exposure=mock_context["exp"], |
| 117 | + hazard=mock_context["haz"], |
| 118 | + impfset=mock_context["imp"], |
| 119 | + date=2023, |
| 120 | + ) |
| 121 | + |
| 122 | + # Check that it's a deep copy (new reference) |
| 123 | + assert snapshot.exposure is not mock_context["exp"] |
| 124 | + assert snapshot.hazard is not mock_context["haz"] |
| 125 | + |
| 126 | + assert snapshot.measure is None |
| 127 | + |
| 128 | + # Check data equality |
| 129 | + pd.testing.assert_frame_equal(snapshot.exposure.gdf, mock_context["exp"].gdf) |
| 130 | + assert snapshot.hazard.haz_type == mock_context["haz"].haz_type |
| 131 | + assert snapshot.impfset == mock_context["imp"] |
| 132 | + |
| 133 | + |
| 134 | +def test_reference(mock_context): |
| 135 | + snapshot = Snapshot.from_triplet( |
| 136 | + exposure=mock_context["exp"], |
| 137 | + hazard=mock_context["haz"], |
| 138 | + impfset=mock_context["imp"], |
| 139 | + date=2023, |
| 140 | + ref_only=True, |
| 141 | + ) |
| 142 | + |
| 143 | + # Check that it is a reference |
| 144 | + assert snapshot.exposure is mock_context["exp"] |
| 145 | + assert snapshot.hazard is mock_context["haz"] |
| 146 | + assert snapshot.impfset is mock_context["imp"] |
| 147 | + assert snapshot.measure is None |
| 148 | + |
| 149 | + # Check data equality |
| 150 | + pd.testing.assert_frame_equal(snapshot.exposure.gdf, mock_context["exp"].gdf) |
| 151 | + assert snapshot.hazard.haz_type == mock_context["haz"].haz_type |
| 152 | + assert snapshot.impfset == mock_context["imp"] |
| 153 | + |
| 154 | + |
| 155 | +def test_apply_measure(mock_context): |
| 156 | + snapshot = Snapshot.from_triplet( |
| 157 | + exposure=mock_context["exp"], |
| 158 | + hazard=mock_context["haz"], |
| 159 | + impfset=mock_context["imp"], |
| 160 | + date=2023, |
| 161 | + ) |
| 162 | + new_snapshot = snapshot.apply_measure(mock_context["measure"]) |
| 163 | + |
| 164 | + assert new_snapshot.measure is not None |
| 165 | + assert new_snapshot.measure.name == "Test Measure" |
| 166 | + assert new_snapshot.exposure == mock_context["mod_exp"] |
| 167 | + assert new_snapshot.hazard == mock_context["mod_haz"] |
| 168 | + assert new_snapshot.impfset == mock_context["mod_imp"] |
0 commit comments