Skip to content

Commit 6a465eb

Browse files
author
Sylvain MARIE
committed
Marks on cases are now working with all pytest versions. Fixed #23
Ids for marked tests are now better managed. A new function `get_pytest_parametrize_args` is now used to transform the list of cases obtained by `get_all_cases(module)`, into the list of marked cases and ids required by `@pytest.mark.parametrize`. The doc has been updated to explain this for advanced users wishing to perform this step manually.
1 parent d50a873 commit 6a465eb

File tree

5 files changed

+88
-36
lines changed

5 files changed

+88
-36
lines changed

docs/api_reference.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,19 @@ is equivalent to:
127127

128128
```python
129129
import pytest
130-
from pytest_cases import get_all_cases
130+
from pytest_cases import get_all_cases, get_pytest_parametrize_args
131131

132132
# import the module containing the test cases
133133
import test_foo_cases
134134

135135
# manually list the available cases
136136
cases = get_all_cases(module=test_foo_cases)
137137

138+
# transform into required arguments for pytest (applying the pytest marks if needed)
139+
marked_cases, cases_ids = get_pytest_parametrize_args(cases)
140+
138141
# parametrize the fixture manually
139-
@pytest.fixture(params=cases)
142+
@pytest.fixture(params=marked_cases, ids=cases_ids)
140143
def foo_fixture(request):
141144
case_data = request.param # type: CaseData
142145
...
@@ -161,31 +164,34 @@ Using it with a non-None `module` argument is equivalent to
161164
So
162165

163166
```python
164-
from pytest_cases import cases_data, CaseData
167+
from pytest_cases import cases_data
165168

166169
# import the module containing the test cases
167170
import test_foo_cases
168171

169-
@cases_data(test_foo_cases)
170-
def test_foo(case_data: CaseData):
172+
@cases_data(module=test_foo_cases)
173+
def test_foo(case_data):
171174
...
172175
```
173176

174177
is equivalent to:
175178

176179
```python
177180
import pytest
178-
from pytest_cases import get_all_cases, CaseData
181+
from pytest_cases import get_all_cases, get_pytest_parametrize_args
179182

180183
# import the module containing the test cases
181184
import test_foo_cases
182185

183186
# manually list the available cases
184187
cases = get_all_cases(module=test_foo_cases)
185188

189+
# transform into required arguments for pytest (applying the pytest marks if needed)
190+
marked_cases, cases_ids = get_pytest_parametrize_args(cases)
191+
186192
# parametrize the test function manually
187-
@pytest.mark.parametrize('case_data', cases, ids=str)
188-
def test_foo(case_data: CaseData):
193+
@pytest.mark.parametrize('case_data', marked_cases, ids=str)
194+
def test_foo(case_data):
189195
...
190196
```
191197

docs/usage/advanced.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,16 +396,19 @@ The `@cases_data` decorator is just syntactic sugar for the following two-steps
396396
397397
```python
398398
import pytest
399-
from pytest_cases import get_all_cases, CaseData
399+
from pytest_cases import get_all_cases, get_pytest_parametrize_args
400400
401401
# import the module containing the test cases
402402
import test_foo_cases
403403
404404
# manually list the available cases
405405
cases = get_all_cases(module=test_foo_cases)
406406
407+
# transform into required arguments for pytest (applying the pytest marks if needed)
408+
marked_cases, cases_ids = get_pytest_parametrize_args(cases)
409+
407410
# parametrize the test function manually
408-
@pytest.mark.parametrize('case_data', cases, ids=str)
409-
def test_with_cases_decorated(case_data: CaseData):
411+
@pytest.mark.parametrize('case_data', marked_cases, ids=cases_ids)
412+
def test_with_cases_decorated(case_data):
410413
...
411414
```

pytest_cases/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
pass
77

88
from pytest_cases.main import cases_data, CaseDataGetter, cases_fixture, pytest_fixture_plus, \
9-
unfold_expected_err, get_all_cases, THIS_MODULE
9+
unfold_expected_err, get_all_cases, THIS_MODULE, get_pytest_parametrize_args
1010

1111
__all__ = [
1212
# the 3 submodules
1313
'main', 'case_funcs', 'common',
1414
# all symbols imported above
1515
'cases_data', 'CaseData', 'CaseDataGetter', 'cases_fixture', 'pytest_fixture_plus',
16-
'unfold_expected_err', 'get_all_cases',
16+
'unfold_expected_err', 'get_all_cases', 'get_pytest_parametrize_args',
1717
'case_name', 'Given', 'ExpectedNormal', 'ExpectedError',
1818
'test_target', 'case_tags', 'THIS_MODULE', 'cases_generator', 'MultipleStepsCaseData'
1919
]

pytest_cases/main.py

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -501,47 +501,94 @@ def datasets_decorator(test_func):
501501
# First list all cases according to user preferences
502502
_cases = get_all_cases(cases, module, test_func, has_tag, filter)
503503

504-
# old: use id getter function : cases_ids = str
505-
# new: hardcode the case ids, safer (?) in case this is mixed with another fixture
506-
cases_ids = [str(c) for c in _cases]
504+
# Then transform into required arguments for pytest (applying the pytest marks if needed)
505+
marked_cases, cases_ids = get_pytest_parametrize_args(_cases)
507506

508507
# Finally create the pytest decorator and apply it
509-
parametrizer = pytest.mark.parametrize(case_data_argname, _cases, ids=cases_ids)
508+
parametrizer = pytest.mark.parametrize(case_data_argname, marked_cases, ids=cases_ids)
510509

511510
return parametrizer(test_func)
512511

513512
return datasets_decorator
514513

515514

515+
def get_pytest_parametrize_args(cases):
516+
"""
517+
Transforms a list of cases into a tuple containing the arguments to use in `@pytest.mark.parametrize`
518+
the tuple is (marked_cases, ids) where
519+
520+
- marked_cases is a list containing either the case or a pytest-marked case (using the pytest marks that were
521+
present on the case function)
522+
- ids is a list containing the case ids to use as test ids.
523+
524+
:param cases:
525+
:return: (marked_cases, ids)
526+
"""
527+
# hardcode the case ids, as simply passing 'ids=str' would not work when cases are marked cases
528+
case_ids = [str(c) for c in cases]
529+
530+
# create the pytest parameter values with the appropriate pytest marks
531+
marked_cases = [c if len(c.get_marks()) == 0 else get_marked_parameter_for_case(c, marks=c.get_marks())
532+
for c in cases]
533+
534+
return marked_cases, case_ids
535+
536+
537+
516538
# Compatibility for the way we put marks on single parameters in the list passed to @pytest.mark.parametrize
517539
# see https://docs.pytest.org/en/3.3.0/skipping.html?highlight=mark%20parametrize#skip-xfail-with-parametrize
518540

519541
try:
542+
# check if pytest.param exists
520543
_ = pytest.param
521-
def get_marked_parameter_for_case(c, marks):
522-
marks_mod = transform_marks_into_decorators(marks)
523-
return pytest.param(c, marks=marks_mod, id=str(c))
524544
except AttributeError:
545+
# if not this is how it was done
546+
# see e.g. https://docs.pytest.org/en/2.9.2/skipping.html?highlight=mark%20parameter#skip-xfail-with-parametrize
525547
def get_marked_parameter_for_case(c, marks):
526548
if len(marks) > 1:
527549
raise ValueError("Multiple marks on parameters not supported for old versions of pytest")
528550
else:
529-
markinfo = marks[0]
530-
markinfodecorator = getattr(pytest.mark, markinfo.name)
531-
return markinfodecorator(*markinfo.args)(c)
551+
# get a decorator for each of the markinfo
552+
marks_mod = transform_marks_into_decorators(marks)
553+
554+
# decorate
555+
return marks_mod[0](c)
556+
else:
557+
# Otherise pytest.param exists, it is easier
558+
def get_marked_parameter_for_case(c, marks):
559+
# get a decorator for each of the markinfo
560+
marks_mod = transform_marks_into_decorators(marks)
561+
562+
# decorate
563+
return pytest.param(c, marks=marks_mod)
532564

533565

534566
def transform_marks_into_decorators(marks):
535567
"""
536-
Transforms the provided marks (MarkInfo) into MarkDecorator
568+
Transforms the provided marks (MarkInfo) obtained from marked cases, into MarkDecorator so that they can
569+
be re-applied to generated pytest parameters in the global @pytest.mark.parametrize.
570+
537571
:param marks:
538572
:return:
539573
"""
540574
marks_mod = []
541-
for m in marks:
542-
md = pytest.mark.MarkDecorator()
543-
md.mark = m
544-
marks_mod.append(md)
575+
try:
576+
for m in marks:
577+
md = pytest.mark.MarkDecorator()
578+
if LooseVersion(pytest.__version__) >= LooseVersion('3.0.0'):
579+
md.mark = m
580+
else:
581+
md.name = m.name
582+
# md.markname = m.name
583+
md.args = m.args
584+
md.kwargs = m.kwargs
585+
586+
# markinfodecorator = getattr(pytest.mark, markinfo.name)
587+
# markinfodecorator(*markinfo.args)
588+
589+
marks_mod.append(md)
590+
except Exception as e:
591+
warn("Caught exception while trying to mark case: [%s] %s" % (type(e), e))
545592
return marks_mod
546593

547594

@@ -555,9 +602,6 @@ def get_all_cases(cases=None, # type: Union[Callable[[Any], Any],
555602
"""
556603
Lists all desired cases from the user inputs. This function may be convenient for debugging purposes.
557604
558-
Note: at the end of execution this function modifies the list of cases so that the pytest marks are applied
559-
correctly for usage of the result as the parameter in a `@pytest.mark.parametrize`.
560-
561605
:param cases: a single case or a hardcoded list of cases to use. Only one of `cases` and `module` should be set.
562606
:param module: a module or a hardcoded list of modules to use. You may use `THIS_MODULE` to indicate that the
563607
module is the current one. Only one of `cases` and `module` should be set.
@@ -592,9 +636,6 @@ def get_all_cases(cases=None, # type: Union[Callable[[Any], Any],
592636
m = sys.modules[this_module_object.__module__] if module is THIS_MODULE else module
593637
_cases = extract_cases_from_module(m, has_tag=has_tag, filter=filter)
594638

595-
# create the pytest parameters to handle pytest marks
596-
_cases = [c if len(c.get_marks()) == 0 else get_marked_parameter_for_case(c, marks=c.get_marks()) for c in _cases]
597-
598639
return _cases
599640

600641

pytest_cases/tests/simple/test_main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
from pytest_cases.tests.example_code import super_function_i_want_to_test
33

4-
from pytest_cases import cases_data, CaseDataGetter, unfold_expected_err, get_all_cases
4+
from pytest_cases import cases_data, CaseDataGetter, unfold_expected_err, get_all_cases, get_pytest_parametrize_args
55
from pytest_cases.tests.simple import test_main_cases
66

77

@@ -40,9 +40,11 @@ def test_with_cases_decorated(case_data # type: CaseDataGetter
4040

4141
# ----------------- Advanced: Manual way: -------------
4242
cases = get_all_cases(module=test_main_cases)
43+
# apply the pytest marks
44+
marked_cases, cases_ids = get_pytest_parametrize_args(cases)
4345

4446

45-
@pytest.mark.parametrize('case_data', cases, ids=str)
47+
@pytest.mark.parametrize('case_data', marked_cases, ids=cases_ids)
4648
def test_with_cases_manual(case_data # type: CaseDataGetter
4749
):
4850
""" Example unit test that is automatically parametrized with @cases_data """

0 commit comments

Comments
 (0)