Adds conftest.py of common fixtures for integration tests#1273
Adds conftest.py of common fixtures for integration tests#1273
conftest.py of common fixtures for integration tests#1273Conversation
…or-common-fixtures
|
Notes from discussion:
|
|
Note for this PR (or rather for future improvements):
|
|
I've added a documentation page, I tried to find the balance between being explicit and concise (had help from Claude to be honest). I am wondering about putting actual values in the documentation as this implies that we keep them up to date with the file (although the values should technically not change). Quick access: https://climada-python--1273.org.readthedocs.build/en/1273/development/Guide_test_fixtures.html |
There was a problem hiding this comment.
This is a good first step towards using common fixtures for all our unit tests! I found a few issues in the code. Other than that, I think we should add an API reference for fixtures to the docs (this might require loading the conftest.py as well. Not sure how to do that atm).
I think most of the functions defined here should not be fixtures because they are not intended for use outside this file. The only "real" fixtures I see:
- exposures_factory
- exposures
- hazard_factory
- hazard
- impf_factory
- linear_impact_function
- impfset_factory
- impfset
Comments on the tutorial:
- Add a target at the top of the new tutorial file and use it for linking with a
refdirective. This will keep links valid if file paths change. See https://myst-parser.readthedocs.io/en/latest/syntax/cross-referencing.html https://myst-parser.readthedocs.io/en/latest/syntax/cross-referencing.html#reference-roles - To make the advantage of a fixture more obvious, add another test to the example that uses
sample_user - Should be
attention: "Never import fixtures directly" https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-attention
For use of directives in MyST, see https://myst-parser.readthedocs.io/en/latest/syntax/roles-and-directives.html#directives-a-block-level-extension-point - Remove explicit information on fixture contents, like "There are 4 events..." (should be in docstring, as mentioned above)
- "Tip" should use
tip, "Note" should usenotedirective https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-tip - Errors:
- "Usefull references"
from climada.entity.exposures?stronger_hazard = ...; # Intensities are halved ...😂
| # Sanity checks | ||
| for const in [VALUES, CATEGORIES, EXP_LONS, EXP_LATS]: | ||
| assert len(const) == len( | ||
| VALUES | ||
| ), "VALUES, REGIONS, CATEGORIES, EXP_LONS, EXP_LATS should all have the same lengths." | ||
|
|
||
| for const in [EVENT_IDS, EVENT_NAMES, DATES, FREQUENCY]: | ||
| assert len(const) == len( | ||
| EVENT_IDS | ||
| ), "EVENT_IDS, EVENT_NAMES, DATES, FREQUENCY should all have the same lengths." |
There was a problem hiding this comment.
If these assertions fail, importing this file will result in an error. I think this is dangerous, as the resulting errors are hard to understand an occur at weird points in time. Are those assertions needed at all? Shouldn't the constructed objects like Exposures complain?
| ), "EVENT_IDS, EVENT_NAMES, DATES, FREQUENCY should all have the same lengths." | ||
|
|
||
|
|
||
| @pytest.fixture(scope="session") |
There was a problem hiding this comment.
Other scopes than function are primarily intended to avoid reconstructing "expensive" objects. The way I see it, this applies to none of the objects here and can have serious side effects, as modifications will persist throughout the test session. I suggest using the default (smallest) scope everywhere, at least in this file.
|
|
||
|
|
||
| @pytest.fixture(scope="session") | ||
| def exposure_values(): |
There was a problem hiding this comment.
Do you foresee this being used as a fixture to an actual test? I don't, so I would keep this a "regular" function and only declare fixtures that are intended for use.
| A set of reusable fixtures for testing purpose. | ||
|
|
||
| The objective of this file is to provide minimalistic, understandable and consistent | ||
| default objects for unit and integration testing. | ||
|
|
||
| Values are chosen such that: | ||
| - Exposure value of the first points is 0. (First location should always have 0 impacts) | ||
| - Category / Group id of all points is 1, except for third point, valued at 2000 (Impacts on that category are always a share of 2000) | ||
| - Hazard centroids are the exposure centroids shifted by `HAZARD_JITTER` on both lon and lat. | ||
| - There are 4 events, with frequencies == 0.03, 0.01, 0.006, 0.004, 0, | ||
| such that impacts for RP250, 100 and 50 and 20 are at_event, | ||
| (freq sorted cumulate to 1/250, 1/100, 1/50 and 1/20). | ||
| - Hazard intensity is: | ||
| * Event 1: zero everywhere (always no impact) | ||
| * Event 2: max intensity at first centroid (also always no impact (first centroid is 0)) | ||
| * Event 3: half max intensity at second centroid (impact == half second centroid) | ||
| * Event 4: quarter max intensity everywhere (impact == 1/4 total value) | ||
| * Event 5: max intensity everywhere (but zero frequency) | ||
| With max intensity set at 100 | ||
| - Impact function is the "identity function", x intensity is x% damages | ||
| - Impact values should be: | ||
| * AAI = 18 = 1000*1/2*0.006+(1000+2000+3000+4000+5000)*0.25*0.004 | ||
| * RP20 = event1 = 0 | ||
| * RP50 = event2 = 0 | ||
| * RP100 = event3 = 500 = 1000*1/2 | ||
| * RP250 = event4 = 3750 = (1000+2000+3000+4000+5000)*0.25 |
There was a problem hiding this comment.
Fixtures are Python functions, so I suggest adding this information to fixture docstrings and creating an API reference. Individual fixtures can be documented with autofunction. See https://docs.pytest.org/en/stable/reference/reference.html#fixtures and https://github.com/pytest-dev/pytest/blob/f93656d611f3c9cd22f346dfeda52cddb778c589/doc/en/reference/reference.rst?plain=1#L325
|
|
||
| @pytest.fixture(scope="session") | ||
| def exposure_geometry(): | ||
| return [Point(lon, lat) for lon, lat in zip(EXP_LONS, EXP_LATS)] |
There was a problem hiding this comment.
|
|
||
| # Frequency are choosen so that they cumulate nicely | ||
| # to correspond to 250, 100, 50, and 20y return periods (for impacts) | ||
| FREQUENCY = np.array([0.03, 0.01, 0.006, 0.004, 0.0]) |
There was a problem hiding this comment.
Do the math for clarity?
| FREQUENCY = np.array([0.03, 0.01, 0.006, 0.004, 0.0]) | |
| FREQUENCY = np.array([1/20, 1/50, 1/100, 1/200, 0.0, dtype="float"]) |
|
|
||
| # Exposure coordinates | ||
| EXP_LONS = np.array([4, 4.25, 4.5, 4, 4.25, 4.5]) | ||
| EXP_LATS = np.array([45, 45, 45, 45.25, 45.25, 45.25]) |
There was a problem hiding this comment.
Hard to read. As values don't matter, choose digits that are easier to distinguish (also against the LON values)
| EXP_LATS = np.array([45, 45, 45, 45.25, 45.25, 45.25]) | |
| EXP_LATS = np.array([33, 33, 33, 33.25, 33.25, 33.25]) |
| EXPOSURE_REF_YEAR = 2020 | ||
| EXPOSURE_VALUE_UNIT = "USD" | ||
| VALUES = np.array([0, 1000, 2000, 3000, 4000, 5000]) | ||
| CATEGORIES = np.array([1, 1, 2, 1, 1, 3]) |
There was a problem hiding this comment.
Good first step, but I think we need to parameterize asap: Use multiple CRS, build exposures of size 1 and >1, etc.

This is based on data test used for the integration tests of the static and interpolated trajectories.
The objective is to provide:
Changes proposed in this PR:
We need to decide if this is something we actually want.
Otherwise, I will move the content to the trajectory integration test directly.
PR Author Checklist
develop)PR Reviewer Checklist