From c87d500bbd2e55bafc128e9243d2532c533bfb45 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 20 Mar 2024 20:01:52 +0000 Subject: [PATCH 1/2] Convert to pytest --- .../tests/unit/fileformats/cf/test_CFGroup.py | 11 +- .../unit/fileformats/cf/test_CFReader.py | 278 ++++++++---------- 2 files changed, 126 insertions(+), 163 deletions(-) diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py index e1b4b7a7cd..f35501a12b 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py @@ -3,7 +3,6 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. """Unit tests for the :class:`iris.fileformats.cf.CFGroup` class.""" - from unittest.mock import MagicMock from iris.fileformats.cf import ( @@ -13,14 +12,10 @@ CFGroup, ) -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -class Tests(tests.IrisTest): +class Tests: # TODO: unit tests for existing functionality pre 2021-03-11. - def setUp(self): + def setup_method(self): self.cf_group = CFGroup() def test_non_data_names(self): @@ -41,4 +36,4 @@ def test_non_data_names(self): expected_names = [var.cf_name for var in (aux_var, coord_var, coord_var2)] expected = set(expected_names) - self.assertEqual(expected, self.cf_group.non_data_variable_names) + assert expected == self.cf_group.non_data_variable_names diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py index 667c679bfb..c17a0df6f6 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py @@ -3,14 +3,10 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. """Unit tests for the `iris.fileformats.cf.CFReader` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - from unittest import mock import numpy as np +import pytest from iris.fileformats.cf import CFReader @@ -53,29 +49,31 @@ def netcdf_variable( return ncvar -class Test_translate__global_attributes(tests.IrisTest): - def setUp(self): +class Test_translate__global_attributes: + @pytest.fixture(autouse=True) + def _setup(self, mocker): ncvar = netcdf_variable("ncvar", "height", np.float64) ncattrs = mock.Mock(return_value=["dimensions"]) getncattr = mock.Mock(return_value="something something_else") - self.dataset = mock.Mock( + dataset = mock.Mock( file_format="NetCDF4", variables={"ncvar": ncvar}, ncattrs=ncattrs, getncattr=getncattr, ) - - def test_create_global_attributes(self): - with mock.patch( + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - global_attrs = CFReader("dummy").cf_group.global_attributes - self.assertEqual(global_attrs["dimensions"], "something something_else") + return_value=dataset, + ) + + def test_create_global_attributes(self, mocker): + global_attrs = CFReader("dummy").cf_group.global_attributes + assert global_attrs["dimensions"] == "something something_else" -class Test_translate__formula_terms(tests.IrisTest): - def setUp(self): +class Test_translate__formula_terms: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.delta = netcdf_variable("delta", "height", np.float64, bounds="delta_bnds") self.delta_bnds = netcdf_variable("delta_bnds", "height bnds", np.float64) self.sigma = netcdf_variable("sigma", "height", np.float64, bounds="sigma_bnds") @@ -124,53 +122,50 @@ def setUp(self): file_format="NetCDF4", variables=self.variables, ncattrs=ncattrs ) # Restrict the CFReader functionality to only performing translations. - build_patch = mock.patch("iris.fileformats.cf.CFReader._build_cf_groups") - reset_patch = mock.patch("iris.fileformats.cf.CFReader._reset") - build_patch.start() - reset_patch.start() - self.addCleanup(build_patch.stop) - self.addCleanup(reset_patch.stop) - - def test_create_formula_terms(self): - with mock.patch( + mocker.patch("iris.fileformats.cf.CFReader._build_cf_groups") + mocker.patch("iris.fileformats.cf.CFReader._reset") + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check there is a singular data variable. - group = cf_group.data_variables - self.assertEqual(len(group), 1) - self.assertEqual(list(group.keys()), ["temp"]) - self.assertIs(group["temp"].cf_data, self.temp) - # Check there are three coordinates. - group = cf_group.coordinates - self.assertEqual(len(group), 3) - coordinates = ["height", "lat", "lon"] - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check there are three auxiliary coordinates. - group = cf_group.auxiliary_coordinates - self.assertEqual(len(group), 3) - aux_coordinates = ["delta", "sigma", "orography"] - self.assertEqual(set(group.keys()), set(aux_coordinates)) - for name in aux_coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check all the auxiliary coordinates are formula terms. - formula_terms = cf_group.formula_terms - self.assertEqual(set(group.items()), set(formula_terms.items())) - # Check there are three bounds. - group = cf_group.bounds - self.assertEqual(len(group), 3) - bounds = ["height_bnds", "delta_bnds", "sigma_bnds"] - self.assertEqual(set(group.keys()), set(bounds)) - for name in bounds: - self.assertEqual(group[name].cf_data, getattr(self, name)) + ) + def test_create_formula_terms(self, mocker): + cf_group = CFReader("dummy").cf_group + assert len(cf_group) == len(self.variables) + # Check there is a singular data variable. + group = cf_group.data_variables + assert len(group) == 1 + assert list(group.keys()) == ["temp"] + assert group["temp"].cf_data is self.temp + # Check there are three coordinates. + group = cf_group.coordinates + assert len(group) == 3 + coordinates = ["height", "lat", "lon"] + assert set(group.keys()) == set(coordinates) + for name in coordinates: + assert group[name].cf_data is getattr(self, name) + # Check there are three auxiliary coordinates. + group = cf_group.auxiliary_coordinates + assert len(group) == 3 + aux_coordinates = ["delta", "sigma", "orography"] + assert set(group.keys()) == set(aux_coordinates) + for name in aux_coordinates: + assert group[name].cf_data is getattr(self, name) + # Check all the auxiliary coordinates are formula terms. + formula_terms = cf_group.formula_terms + assert set(group.items()) == set(formula_terms.items()) + # Check there are three bounds. + group = cf_group.bounds + assert len(group) == 3 + bounds = ["height_bnds", "delta_bnds", "sigma_bnds"] + assert set(group.keys()) == set(bounds) + for name in bounds: + assert group[name].cf_data == getattr(self, name) -class Test_build_cf_groups__formula_terms(tests.IrisTest): - def setUp(self): + +class Test_build_cf_groups__formula_terms: + @pytest.fixture(autouse=True) + def _setup(self, mocker): self.delta = netcdf_variable("delta", "height", np.float64, bounds="delta_bnds") self.delta_bnds = netcdf_variable("delta_bnds", "height bnds", np.float64) self.sigma = netcdf_variable("sigma", "height", np.float64, bounds="sigma_bnds") @@ -224,122 +219,95 @@ def setUp(self): ) # Restrict the CFReader functionality to only performing translations # and building first level cf-groups for variables. - patcher = mock.patch("iris.fileformats.cf.CFReader._reset") - patcher.start() - self.addCleanup(patcher.stop) - - def test_associate_formula_terms_with_data_variable(self): - with mock.patch( + mocker.patch("iris.fileformats.cf.CFReader._reset") + mocker.patch( "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check the cf-group associated with the data variable. - temp_cf_group = cf_group["temp"].cf_group - # Check the data variable is associated with eight variables. - self.assertEqual(len(temp_cf_group), 8) - # Check there are three coordinates. - group = temp_cf_group.coordinates - self.assertEqual(len(group), 3) - coordinates = ["height", "lat", "lon"] - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check the height coordinate is bounded. - group = group["height"].cf_group - self.assertEqual(len(group.bounds), 1) - self.assertIn("height_bnds", group.bounds) - self.assertIs(group["height_bnds"].cf_data, self.height_bnds) - # Check there are five auxiliary coordinates. - group = temp_cf_group.auxiliary_coordinates - self.assertEqual(len(group), 5) - aux_coordinates = ["delta", "sigma", "orography", "x", "y"] - self.assertEqual(set(group.keys()), set(aux_coordinates)) - for name in aux_coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check all the auxiliary coordinates are formula terms. - formula_terms = cf_group.formula_terms - self.assertTrue(set(formula_terms.items()).issubset(list(group.items()))) - # Check the terms by root. - for name, term in zip(aux_coordinates, ["a", "b", "orog"]): - self.assertEqual( - formula_terms[name].cf_terms_by_root, dict(height=term) - ) - # Check the bounded auxiliary coordinates. - for name, name_bnds in zip( - ["delta", "sigma"], ["delta_bnds", "sigma_bnds"] - ): - aux_coord_group = group[name].cf_group - self.assertEqual(len(aux_coord_group.bounds), 1) - self.assertIn(name_bnds, aux_coord_group.bounds) - self.assertIs( - aux_coord_group[name_bnds].cf_data, - getattr(self, name_bnds), - ) + ) + + def test_associate_formula_terms_with_data_variable(self, mocker): + cf_group = CFReader("dummy").cf_group + assert len(cf_group) == len(self.variables) + # Check the cf-group associated with the data variable. + temp_cf_group = cf_group["temp"].cf_group + # Check the data variable is associated with eight variables. + assert len(temp_cf_group) == 8 + # Check there are three coordinates. + group = temp_cf_group.coordinates + assert len(group) == 3 + coordinates = ["height", "lat", "lon"] + assert set(group.keys()) == set(coordinates) + for name in coordinates: + assert group[name].cf_data is getattr(self, name) + # Check the height coordinate is bounded. + group = group["height"].cf_group + assert len(group.bounds) == 1 + assert "height_bnds" in group.bounds + assert group["height_bnds"].cf_data is self.height_bnds + # Check there are five auxiliary coordinates. + group = temp_cf_group.auxiliary_coordinates + assert len(group) == 5 + aux_coordinates = ["delta", "sigma", "orography", "x", "y"] + assert set(group.keys()) == set(aux_coordinates) + for name in aux_coordinates: + assert group[name].cf_data is getattr(self, name) + # Check all the auxiliary coordinates are formula terms. + formula_terms = cf_group.formula_terms + assert set(formula_terms.items()).issubset(list(group.items())) + # Check the terms by root. + for name, term in zip(aux_coordinates, ["a", "b", "orog"]): + assert formula_terms[name].cf_terms_by_root == dict(height=term) + # Check the bounded auxiliary coordinates. + for name, name_bnds in zip(["delta", "sigma"], ["delta_bnds", "sigma_bnds"]): + aux_coord_group = group[name].cf_group + assert len(aux_coord_group.bounds) == 1 + assert name_bnds in aux_coord_group.bounds + assert aux_coord_group[name_bnds].cf_data is getattr(self, name_bnds) def test_promote_reference(self): - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check the number of data variables. - self.assertEqual(len(cf_group.data_variables), 1) - self.assertEqual(list(cf_group.data_variables.keys()), ["temp"]) - # Check the number of promoted variables. - self.assertEqual(len(cf_group.promoted), 1) - self.assertEqual(list(cf_group.promoted.keys()), ["orography"]) - # Check the promoted variable dependencies. - group = cf_group.promoted["orography"].cf_group.coordinates - self.assertEqual(len(group), 2) - coordinates = ("lat", "lon") - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) + cf_group = CFReader("dummy").cf_group + assert len(cf_group) == len(self.variables) + # Check the number of data variables. + assert len(cf_group.data_variables) == 1 + assert list(cf_group.data_variables.keys()) == ["temp"] + # Check the number of promoted variables. + assert len(cf_group.promoted) == 1 + assert list(cf_group.promoted.keys()) == ["orography"] + # Check the promoted variable dependencies. + group = cf_group.promoted["orography"].cf_group.coordinates + assert len(group) == 2 + coordinates = ("lat", "lon") + assert set(group.keys()) == set(coordinates) + for name in coordinates: + assert group[name].cf_data is getattr(self, name) def test_formula_terms_ignore(self): self.orography.dimensions = ["lat", "wibble"] - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), mock.patch("warnings.warn") as warn: + with pytest.warns(match="Ignoring formula terms variable"): cf_group = CFReader("dummy").cf_group group = cf_group.promoted - self.assertEqual(list(group.keys()), ["orography"]) - self.assertIs(group["orography"].cf_data, self.orography) - self.assertEqual(warn.call_count, 1) + assert list(group.keys()) == ["orography"] + assert group["orography"].cf_data is self.orography def test_auxiliary_ignore(self): self.x.dimensions = ["lat", "wibble"] - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), mock.patch("warnings.warn") as warn: + with pytest.warns(match="Ignoring variable 'x'"): cf_group = CFReader("dummy").cf_group promoted = ["x", "orography"] group = cf_group.promoted - self.assertEqual(set(group.keys()), set(promoted)) + assert set(group.keys()) == set(promoted) for name in promoted: - self.assertIs(group[name].cf_data, getattr(self, name)) - self.assertEqual(warn.call_count, 1) + assert group[name].cf_data is getattr(self, name) def test_promoted_auxiliary_ignore(self): self.wibble = netcdf_variable("wibble", "lat wibble", np.float64) self.variables["wibble"] = self.wibble self.orography.coordinates = "wibble" - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), mock.patch("warnings.warn") as warn: + with pytest.warns(match="Ignoring variable 'wibble'") as warns: cf_group = CFReader("dummy").cf_group.promoted promoted = ["wibble", "orography"] - self.assertEqual(set(cf_group.keys()), set(promoted)) + assert set(cf_group.keys()) == set(promoted) for name in promoted: - self.assertIs(cf_group[name].cf_data, getattr(self, name)) - self.assertEqual(warn.call_count, 2) - - -if __name__ == "__main__": - tests.main() + assert cf_group[name].cf_data is getattr(self, name) + # we should have got 2 warnings + assert len(warns.list) == 2 From 04ed352074b1d248297afc47b87781f891390b0d Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 21 Mar 2024 11:54:13 +0000 Subject: [PATCH 2/2] Review changes. --- lib/iris/tests/unit/fileformats/cf/test_CFGroup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py index f35501a12b..a6c9bee6e8 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py @@ -5,6 +5,8 @@ """Unit tests for the :class:`iris.fileformats.cf.CFGroup` class.""" from unittest.mock import MagicMock +import pytest + from iris.fileformats.cf import ( CFAuxiliaryCoordinateVariable, CFCoordinateVariable, @@ -15,7 +17,8 @@ class Tests: # TODO: unit tests for existing functionality pre 2021-03-11. - def setup_method(self): + @pytest.fixture(autouse=True) + def _setup(self): self.cf_group = CFGroup() def test_non_data_names(self): @@ -36,4 +39,4 @@ def test_non_data_names(self): expected_names = [var.cf_name for var in (aux_var, coord_var, coord_var2)] expected = set(expected_names) - assert expected == self.cf_group.non_data_variable_names + assert self.cf_group.non_data_variable_names == expected