Skip to content

Commit aeb72ef

Browse files
author
Sylvain MARIE
committed
@parametrize_with_cases now by default (cases=AUTO) looks for both file naming patterns test_<name>_cases.py and cases_<name>.py. Removed the AUTO2 constant. Fixed #140
Nested classes containing cases are now officially supported (they were, but undocumented). Fixed #160
1 parent e34718e commit aeb72ef

File tree

8 files changed

+99
-62
lines changed

8 files changed

+99
-62
lines changed

docs/api_reference.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Returns True if the provided object is a function or callable and, if `check_pre
197197

198198
A decorator for test functions or fixtures, to parametrize them based on test cases. It works similarly to [`@pytest.mark.parametrize`](https://docs.pytest.org/en/stable/parametrize.html): argnames represent a coma-separated string of arguments to inject in the decorated test function or fixture. The argument values (`argvalues` in [`@pytest.mark.parametrize`](https://docs.pytest.org/en/stable/parametrize.html)) are collected from the various case functions found according to `cases`, and injected as lazy values so that the case functions are called just before the test or fixture is executed.
199199

200-
By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named `test_<name>_cases.py` where `test_<name>` is the current module name. An alternate naming convention `cases_<name>.py` can be used by setting `cases=AUTO2`.
200+
By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named `test_<name>_cases.py` or if not found, `case_<name>.py`, where `test_<name>` is the current module name.
201201

202202
Finally, the `cases` argument also accepts an explicit case function, cases-containing class, module or module name; or a list of such elements. Note that both absolute and relative module names are suported.
203203

@@ -216,7 +216,7 @@ argvalues = get_parametrize_args(host_class_or_module_of_f, cases_funs)
216216

217217
- `argnames`: same than in `@pytest.mark.parametrize`
218218

219-
- `cases`: a case function, a class containing cases, a module object or a module name string (relative module names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module. `AUTO` (default) means that the module named `test_<name>_cases.py` will be loaded, where `test_<name>.py` is the module file of the decorated function. `AUTO2` allows you to use the alternative naming scheme `case_<name>.py`. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in classes following naming pattern `*Case*`. When classes are explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.
219+
- `cases`: a case function, a class containing cases, a module object or a module name string (relative module names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module. `AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `case_<name>.py`, will be loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the `*Case*` naming pattern. When classes are explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.
220220

221221
- `prefix`: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to denote different kind of cases, for example 'data_', 'algo_', 'user_', etc.
222222

pytest_cases/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
#
44
# License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
55
from .common_pytest_lazy_values import lazy_value, is_lazy
6-
from .common_others import unfold_expected_err, assert_exception, AUTO, AUTO2
6+
from .common_others import unfold_expected_err, assert_exception, AUTO
7+
8+
AUTO2 = AUTO
9+
"""Deprecated symbol, for retrocompatibility. Will be dropped soon."""
710

811
from .fixture_core1_unions import fixture_union, NOT_USED, unpack_fixture, ignore_unused
912
from .fixture_core2 import pytest_fixture_plus, fixture_plus, param_fixtures, param_fixture

pytest_cases/case_parametrizer_new.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
pass
1818

1919
from .common_mini_six import string_types
20-
from .common_others import get_code_first_line, AUTO, AUTO2, qname, funcopy
20+
from .common_others import get_code_first_line, AUTO, qname, funcopy
2121
from .common_pytest_marks import copy_pytest_marks, make_marked_parameter_value, remove_pytest_mark, filter_marks
2222
from .common_pytest_lazy_values import lazy_value
2323
from .common_pytest import safe_isclass, MiniMetafunc, is_fixture, get_fixture_name, inject_host, add_fixture_params
@@ -26,7 +26,13 @@
2626
from .case_funcs_new import matches_tag_query, is_case_function, is_case_class, CASE_PREFIX_FUN, copy_case_info, \
2727
get_case_id, get_case_marks, GEN_BY_US
2828
from .fixture__creation import check_name_available, CHANGE
29-
from .fixture_parametrize_plus import fixture_ref, _parametrize_plus, _IDGEN
29+
from .fixture_parametrize_plus import fixture_ref, _parametrize_plus
30+
31+
try:
32+
ModuleNotFoundError
33+
except NameError:
34+
# python < 3.6
35+
ModuleNotFoundError = ImportError
3036

3137

3238
THIS_MODULE = object()
@@ -36,7 +42,7 @@
3642
from typing import Literal, Optional # noqa
3743
from types import ModuleType # noqa
3844

39-
ModuleRef = Union[str, ModuleType, Literal[AUTO], Literal[AUTO2], Literal[THIS_MODULE]] # noqa
45+
ModuleRef = Union[str, ModuleType, Literal[AUTO], Literal[THIS_MODULE]] # noqa
4046

4147
except: # noqa
4248
pass
@@ -66,8 +72,7 @@ def parametrize_with_cases(argnames, # type: Union[str, List[str]
6672
just before the test or fixture is executed.
6773
6874
By default (`cases=AUTO`) the list of test cases is automatically drawn from the python module file named
69-
`test_<name>_cases.py` where `test_<name>` is the current module name. An alternate naming convention
70-
`cases_<name>.py` can be used by setting `cases=AUTO2`.
75+
`test_<name>_cases.py` or if not found, `cases_<name>.py`, where `test_<name>` is the current module name.
7176
7277
Finally, the `cases` argument also accepts an explicit case function, cases-containing class, module or module name;
7378
or a list of such elements. Note that both absolute and relative module names are suported.
@@ -86,11 +91,12 @@ def parametrize_with_cases(argnames, # type: Union[str, List[str]
8691
:param argnames: same than in @pytest.mark.parametrize
8792
:param cases: a case function, a class containing cases, a module object or a module name string (relative module
8893
names accepted). Or a list of such items. You may use `THIS_MODULE` or `'.'` to include current module.
89-
`AUTO` (default) means that the module named `test_<name>_cases.py` will be loaded, where `test_<name>.py` is
90-
the module file of the decorated function. `AUTO2` allows you to use the alternative naming scheme
91-
`case_<name>.py`. When a module is listed, all of its functions matching the `prefix`, `filter` and `has_tag`
92-
are selected, including those functions nested in classes following naming pattern `*Case*`. When classes are
93-
explicitly provided in the list, they can have any name and do not need to follow this `*Case*` pattern.
94+
`AUTO` (default) means that the module named `test_<name>_cases.py` or if not found, `case_<name>.py`, will be
95+
loaded, where `test_<name>.py` is the module file of the decorated function. When a module is listed, all of
96+
its functions matching the `prefix`, `filter` and `has_tag` are selected, including those functions nested in
97+
classes following naming pattern `*Case*`. Nested subclasses are taken into account, as long as they follow the
98+
`*Case*` naming pattern. When classes are explicitly provided in the list, they can have any name and do not
99+
need to follow this `*Case*` pattern.
94100
:param prefix: the prefix for case functions. Default is 'case_' but you might wish to use different prefixes to
95101
denote different kind of cases, for example 'data_', 'algo_', 'user_', etc.
96102
:param glob: an optional glob-like pattern for case ids, for example "*_success" or "*_failure". Note that this
@@ -263,9 +269,9 @@ def get_all_cases(parametrization_target, # type: Callable
263269
else:
264270
# module
265271
if c is AUTO:
272+
# First try `test_<name>_cases.py` Then `case_<name>.py`
266273
c = import_default_cases_module(parametrization_target)
267-
elif c is AUTO2:
268-
c = import_default_cases_module(parametrization_target, alt_name=True)
274+
269275
elif c is THIS_MODULE or c == '.':
270276
c = caller_module_name
271277
new_cases = extract_cases_from_module(c, package_name=parent_pkg_name, case_fun_prefix=prefix)
@@ -504,33 +510,35 @@ def _get_fixture_cases(module # type: ModuleType
504510
return cache
505511

506512

507-
def import_default_cases_module(f, alt_name=False):
513+
def import_default_cases_module(f):
508514
"""
509515
Implements the `module=AUTO` behaviour of `@parameterize_cases`: based on the decorated test function `f`,
510-
it finds its containing module name "<test_module>.py" and then tries to import the python module
511-
"<test_module>_cases.py".
516+
it finds its containing module name "test_<module>.py" and then tries to import the python module
517+
"test_<module>_cases.py".
512518
513-
Alternately if `alt_name=True` the name pattern to use will be `cases_<module>.py` when the test module is named
514-
`test_<module>.py`.
519+
If the module is not found it looks for the alternate file `cases_<module>.py`.
515520
516521
:param f: the decorated test function
517-
:param alt_name: a boolean (default False) to use the alternate naming scheme.
518522
:return:
519523
"""
520-
if alt_name:
524+
# First try `test_<name>_cases.py`
525+
cases_module_name1 = "%s_cases" % f.__module__
526+
try:
527+
cases_module = import_module(cases_module_name1)
528+
except ModuleNotFoundError:
529+
# Then try `case_<name>.py`
521530
parts = f.__module__.split('.')
522531
assert parts[-1][0:5] == 'test_'
523-
cases_module_name = "%s.cases_%s" % ('.'.join(parts[:-1]), parts[-1][5:])
524-
else:
525-
cases_module_name = "%s_cases" % f.__module__
532+
cases_module_name2 = "%s.cases_%s" % ('.'.join(parts[:-1]), parts[-1][5:])
533+
try:
534+
cases_module = import_module(cases_module_name2)
535+
except ModuleNotFoundError:
536+
# Nothing worked
537+
raise ValueError("Error importing test cases module to parametrize function %r: unable to import AUTO "
538+
"cases module %r nor %r. Maybe you wish to import cases from somewhere else ? In that case"
539+
"please specify `cases=...`."
540+
% (f, cases_module_name1, cases_module_name2))
526541

527-
try:
528-
cases_module = import_module(cases_module_name)
529-
except ImportError:
530-
raise ValueError("Error importing test cases module to parametrize function %r: unable to import AUTO%s "
531-
"cases module %r. Maybe you wish to import cases from somewhere else ? In that case please "
532-
"specify `cases=...`."
533-
% (f, '2' if alt_name else '', cases_module_name))
534542
return cases_module
535543

536544

pytest_cases/common_others.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,6 @@ def __exit__(self, exc_type, exc_val, exc_tb):
210210
AUTO = object()
211211
"""Marker for automatic defaults"""
212212

213-
AUTO2 = object()
214-
"""Marker that alternate automatic defaults"""
215-
216213

217214
def get_function_host(func):
218215
"""

pytest_cases/tests/cases/doc/test_doc.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import pytest
66

77
from pytest_harvest import get_session_synthesis_dct
8-
from pytest_cases import parametrize_with_cases, AUTO2, fixture, case
8+
from pytest_cases import parametrize_with_cases, fixture, case, AUTO
99
from pytest_cases.common_pytest_marks import has_pytest_param
1010

11-
from . import cases_doc
11+
from . import cases_doc_alternate
1212
from .example import foo
1313

1414

@@ -35,28 +35,6 @@ def test_foo_default_cases_file_synthesis(request):
3535
]
3636

3737

38-
@parametrize_with_cases("a,b", cases=AUTO2)
39-
def test_foo_alternate_cases_file_and_two_marked_skip(a, b):
40-
assert isinstance(foo(a, b), tuple)
41-
42-
43-
def test_foo_alternate_cases_file_and_two_marked_skip_synthesis(request):
44-
results_dct = get_session_synthesis_dct(request, filter=test_foo_alternate_cases_file_and_two_marked_skip,
45-
test_id_format='function')
46-
if has_pytest_param:
47-
assert list(results_dct) == [
48-
'test_foo_alternate_cases_file_and_two_marked_skip[hello]',
49-
'test_foo_alternate_cases_file_and_two_marked_skip[two_negative_ints0]',
50-
'test_foo_alternate_cases_file_and_two_marked_skip[two_negative_ints1]'
51-
]
52-
else:
53-
assert list(results_dct) == [
54-
'test_foo_alternate_cases_file_and_two_marked_skip[0hello[0]-hello[1]]',
55-
'test_foo_alternate_cases_file_and_two_marked_skip[2two_negative_ints[0]-two_negative_ints[1]]',
56-
'test_foo_alternate_cases_file_and_two_marked_skip[4two_negative_ints[0]-two_negative_ints[1]]'
57-
]
58-
59-
6038
def strange_ints():
6139
""" Inputs are two negative integers """
6240
return -1, -2
@@ -138,7 +116,7 @@ def test_foo_cls_synthesis(request):
138116
]
139117

140118

141-
@parametrize_with_cases("a,b", cases=(CasesFoo, strange_ints, cases_doc, CasesFoo, '.test_doc_cases'))
119+
@parametrize_with_cases("a,b", cases=(CasesFoo, strange_ints, cases_doc_alternate, CasesFoo, '.test_doc_cases'))
142120
def test_foo_cls_list(a, b):
143121
assert isinstance(foo(a, b), tuple)
144122

@@ -151,7 +129,7 @@ def test_foo_cls_list_synthesis(request):
151129
'test_foo_cls_list[two_negative_ints0]',
152130
# strange_ints
153131
'test_foo_cls_list[strange_ints]',
154-
# cases_doc.py
132+
# cases_doc_alternate.py
155133
'test_foo_cls_list[hello]',
156134
'test_foo_cls_list[two_negative_ints1]',
157135
'test_foo_cls_list[two_negative_ints2]',
@@ -169,7 +147,7 @@ def test_foo_cls_list_synthesis(request):
169147

170148

171149
@fixture
172-
@parametrize_with_cases("a,b")
150+
@parametrize_with_cases("a,b", cases=AUTO) # just checking that explicit AUTO is same as implicit
173151
def c(a, b):
174152
return a + b
175153

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pytest_harvest import get_session_synthesis_dct
2+
3+
from pytest_cases.common_pytest_marks import has_pytest_param
4+
from pytest_cases import parametrize_with_cases, AUTO
5+
6+
from .example import foo
7+
8+
9+
@parametrize_with_cases("a,b")
10+
def test_foo_alternate_cases_file_and_two_marked_skip(a, b):
11+
assert isinstance(foo(a, b), tuple)
12+
13+
14+
@parametrize_with_cases("a,b", cases=AUTO)
15+
def test_foo_alternate_cases_file_and_two_marked_skip(a, b):
16+
assert isinstance(foo(a, b), tuple)
17+
18+
19+
def test_foo_alternate_cases_file_and_two_marked_skip_synthesis(request):
20+
results_dct = get_session_synthesis_dct(request, filter=test_foo_alternate_cases_file_and_two_marked_skip,
21+
test_id_format='function')
22+
if has_pytest_param:
23+
assert list(results_dct) == [
24+
'test_foo_alternate_cases_file_and_two_marked_skip[hello]',
25+
'test_foo_alternate_cases_file_and_two_marked_skip[two_negative_ints0]',
26+
'test_foo_alternate_cases_file_and_two_marked_skip[two_negative_ints1]'
27+
]
28+
else:
29+
assert list(results_dct) == [
30+
'test_foo_alternate_cases_file_and_two_marked_skip[0hello[0]-hello[1]]',
31+
'test_foo_alternate_cases_file_and_two_marked_skip[2two_negative_ints[0]-two_negative_ints[1]]',
32+
'test_foo_alternate_cases_file_and_two_marked_skip[4two_negative_ints[0]-two_negative_ints[1]]'
33+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from pytest_cases import parametrize_with_cases
2+
3+
4+
class FooCases:
5+
def case_1(self):
6+
return 1
7+
8+
class SubFooCases:
9+
def case_2(self):
10+
return 2
11+
12+
def case_3(self):
13+
return 3
14+
15+
16+
@parametrize_with_cases("x", FooCases)
17+
def test_foo(x):
18+
print(x)

0 commit comments

Comments
 (0)