-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
TST: Use fixtures instead of setup_method for index tests #28865
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@@ -58,13 +58,14 @@ def test_view(self): | |||
tm.assert_index_equal(result, i_view) | |||
|
|||
def test_map_callable(self): | |||
expected = self.index + self.index.freq | |||
result = self.index.map(lambda x: x + x.freq) | |||
index = self.create_index() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Planning to convert self.create_index()
into a fixture in a follow up PR; in this PR I've used it to replace references to indexes set via setup_method
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm probably not be following the logic correctly, but self.create_index() is a different index to self.index for the datetimelike tests?
i.e. tdi
def setup_method(self, method):
self.indices = dict(index=tm.makeTimedeltaIndex(10))
self.setup_indices()
so self.index is created with self.setup_indices() and not the same as
def create_index(self):
return pd.to_timedelta(range(5), unit="d") + pd.offsets.Hour(1)
import pandas.util.testing as tm | ||
from pandas.util.testing import assert_almost_equal | ||
|
||
|
||
class TestIndex(Base): | ||
_holder = Index | ||
|
||
def setup_method(self, method): | ||
self.indices = dict( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't create an indices
fixture for this class since there was already an identical indices
fixture in conftest.py
, so sharing that one here. I updated the conftest.py
fixture to use the same naming conventions converted to snake_case, e.g. unicodeIndex
--> unicode_index
.
) | ||
self.setup_indices() | ||
@pytest.fixture | ||
def index(self, request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is pretty much the only non-standard thing I had to do. A lot of the tests in this file only tested a subset of the indexes in indices
, so I needed a way to mimic that with fixtures. The idea here is to use indirect parametrization to allow this behavior, e.g.
@pytest.mark.parametrize("index", ["int", "uint", "float"], indirect=True)
Would result in this fixture returning the three indexes keyed by "int"
, "uint"
, "float"
in indices_dict
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is pretty much the only non-standard thing I had to do
this is OK. used in io.formats.test_to_html for the biggie_df_fixture.
the alternative is to create a fixture yielding just the ids which is then requested by the indices fixture. (see filepath_or_buffer_id and filepath_or_buffer fixtures in io.formats.test_format.) and then override the id fixture directly at function, class or module level.
both patterns (direct and indirect parameterisation) are equivalent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking at this in detail. I don't think you need to create a separate fixture in order to do this. you could apply indirect parametrisation to the indices fixture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jschendel Very Nice!
a few comments, but mostly can probably be done as a follow-on.
not finished reviewing all the changes yet. hopefully can get back to it in a few hours.
# gh-12309: Check that the "name" argument | ||
# passed at initialization is honored. | ||
if isinstance(indices, MultiIndex): | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this should be a pytest.skip instead of a return?
} | ||
|
||
|
||
@pytest.fixture(params=indices_dict.keys()) | ||
def indices(request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe a smaller diff if the fixture was named index?
Not just this PR, but in general maybe we shouldn't use the plural when creating fixtures since when the fixture is used it creates a single value at a time and so the plural name in the tests can sometimes be confusing.
with pytest.raises(InvalidIndexError, match=e): | ||
index.get_indexer(index[0:2]) | ||
if isinstance(indices, IntervalIndex): | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pytest.skip?
elif isinstance(indices, (RangeIndex, MultiIndex, CategoricalIndex)): | ||
# RangeIndex cannot be initialized from data | ||
# MultiIndex and CategoricalIndex are tested separately | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pytest.skip?
) | ||
elif isinstance(indices, IntervalIndex): | ||
# checked in test_interval.py | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pytest.skip?
values = np.asarray(idx.values) | ||
|
||
if len(indices) == 0: | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skip?
elif isinstance(indices, DatetimeIndexOpsMixin): | ||
values[1] = iNaT | ||
elif isinstance(indices, (Int64Index, UInt64Index)): | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skip?
if isinstance(indices, DatetimeIndexOpsMixin): | ||
values[1] = iNaT | ||
elif isinstance(indices, (Int64Index, UInt64Index)): | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skip?
with pytest.raises(NotImplementedError, match=msg): | ||
idx.fillna(idx[0]) | ||
if len(indices) == 0: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
skip?
@@ -58,13 +58,14 @@ def test_view(self): | |||
tm.assert_index_equal(result, i_view) | |||
|
|||
def test_map_callable(self): | |||
expected = self.index + self.index.freq | |||
result = self.index.map(lambda x: x + x.freq) | |||
index = self.create_index() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm probably not be following the logic correctly, but self.create_index() is a different index to self.index for the datetimelike tests?
i.e. tdi
def setup_method(self, method):
self.indices = dict(index=tm.makeTimedeltaIndex(10))
self.setup_indices()
so self.index is created with self.setup_indices() and not the same as
def create_index(self):
return pd.to_timedelta(range(5), unit="d") + pd.offsets.Hour(1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some more comments, again many could be a follow-on.
still have more files to review. see what others think but maybe this PR needs to be done in smaller chunks.
@@ -41,80 +41,65 @@ | |||
from pandas.core.indexes.api import Index, MultiIndex | |||
from pandas.core.sorting import safe_sort | |||
from pandas.tests.indexes.common import Base | |||
from pandas.tests.indexes.conftest import indices_dict |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we shouldn't really be importing from a conftest file. probably best to move indices_dict to common and then import from there.
) | ||
self.setup_indices() | ||
@pytest.fixture | ||
def index(self, request): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking at this in detail. I don't think you need to create a separate fixture in order to do this. you could apply indirect parametrisation to the indices fixture.
new_index = self.dateIndex[None, :] | ||
@pytest.mark.parametrize("index", ["datetime"], indirect=True) | ||
def test_new_axis(self, index): | ||
new_index = index[None, :] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here and elsewhere I think result
may be more appropriate than new_index
@@ -570,37 +555,50 @@ def test_constructor_cast(self): | |||
with pytest.raises(ValueError, match=msg): | |||
Index(["a", "b", "c"], dtype=float) | |||
|
|||
def test_view_with_args(self): | |||
restricted = ["unicodeIndex", "strIndex", "catIndex", "boolIndex", "empty"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be better to keep this and just do a pytest.skip if in restricted.
d = self.dateIndex[0] | ||
assert self.dateIndex.asof(d) == d | ||
assert isna(self.dateIndex.asof(d - timedelta(1))) | ||
@pytest.mark.parametrize("index", ["datetime"], indirect=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests of a single index type probably don't belong in base.
index = self.indices[name] | ||
expected = Index(np.arange(len(index), 0, -1)) | ||
|
||
@pytest.mark.parametrize( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather than duplicating the parameterisation, is it worth making mapper a fixture?
def _check_method_works(self, method, index): | ||
method(index) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only used twice, probably best to inline.
@@ -1402,7 +1408,7 @@ def test_format(self): | |||
expected = [str(index[0])] | |||
assert formatted == expected | |||
|
|||
self.strIndex[:0].format() | |||
Index([]).format() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this changing?
tm.assert_index_equal(dropped, expected) | ||
|
||
@pytest.mark.parametrize("index", ["string", "int", "float"], indirect=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this combination is common could create an alias to the mark.
index.contains(1) | ||
def test_deprecated_contains(self, indices): | ||
# deprecated for all types except IntervalIndex | ||
warning = FutureWarning if not isinstance(indices, pd.IntervalIndex) else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may be more appropriate to skip on IntervalIndex
lgtm. @simonjayhawkins ok here? |
@jreback I think good to merge and tidy up as a folow-up. |
thanks @simonjayhawkins agreed nice @jschendel |
The common index tests use
setup_method
to create adict
of indexes to test against:pandas/pandas/tests/indexes/test_range.py
Lines 25 to 30 in df2e081
This
dict
of indexes is then iterated over within the tests:pandas/pandas/tests/indexes/common.py
Lines 359 to 363 in df2e081
The bulk of this PR involves converting
self.indices
into a parametrized fixture of indexes, and adjusting the tests to support this (largely just unindenting). I had to do this conversion for all indexes at once since common test code for all index classes utilizes this pattern, so the diff is fairly large, but it should be relatively simple changes.I also had to make some changes to references to specific indexes as well (e.g.
self.index
,self.strIndex
, etc.) since thesetup_method
code also directly set each index in thedict
as a class attribute.