diff --git a/_pytest/python.py b/_pytest/python.py index 93144815c..0160b18ae 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1,4 +1,5 @@ """ Python test discovery, setup and run of test functions. """ +import fnmatch import py import inspect import sys @@ -127,9 +128,10 @@ def pytest_addoption(parser): default=['test_*.py', '*_test.py'], help="glob-style file patterns for Python test module discovery") parser.addini("python_classes", type="args", default=["Test",], - help="prefixes for Python test class discovery") + help="prefixes or glob names for Python test class discovery") parser.addini("python_functions", type="args", default=["test",], - help="prefixes for Python test function and method discovery") + help="prefixes or glob names for Python test function and " + "method discovery") def pytest_cmdline_main(config): if config.option.showfixtures: @@ -307,14 +309,22 @@ class PyobjMixin(PyobjContext): class PyCollector(PyobjMixin, pytest.Collector): def funcnamefilter(self, name): - for prefix in self.config.getini("python_functions"): - if name.startswith(prefix): - return True + return self._matches_prefix_or_glob_option('python_functions', name) def classnamefilter(self, name): - for prefix in self.config.getini("python_classes"): - if name.startswith(prefix): + return self._matches_prefix_or_glob_option('python_classes', name) + + def _matches_prefix_or_glob_option(self, option_name, name): + """ + checks if the given name matches the prefix or glob-pattern defined + in ini configuration. + """ + for option in self.config.getini(option_name): + if name.startswith(option): return True + elif fnmatch.fnmatch(name, option): + return True + return False def collect(self): if not getattr(self.obj, "__test__", True): diff --git a/doc/en/customize.txt b/doc/en/customize.txt index bea6f1dee..74d30f7ab 100644 --- a/doc/en/customize.txt +++ b/doc/en/customize.txt @@ -115,17 +115,33 @@ Builtin configuration file options .. confval:: python_classes - One or more name prefixes determining which test classes - are considered as test modules. + One or more name prefixes or glob-style patterns determining which classes + are considered for test collection. Here is an example of how to collect + tests from classes that end in ``Suite``:: + + # content of pytest.ini + [pytest] + python_classes = *Suite + + Note that ``unittest.TestCase`` derived classes are always collected + regardless of this option, as ``unittest``'s own collection framework is used + to collect those tests. .. confval:: python_functions - One or more name prefixes determining which test functions - and methods are considered as test modules. Note that this - has no effect on methods that live on a ``unittest.TestCase`` - derived class. + One or more name prefixes or glob-patterns determining which test functions + and methods are considered tests. Here is an example of how + to collect test functions and methods that end in ``_test``:: - See :ref:`change naming conventions` for examples. + # content of pytest.ini + [pytest] + python_functions = *_test + + Note that this has no effect on methods that live on a ``unittest + .TestCase`` derived class, as ``unittest``'s own collection framework is used + to collect those tests. + + See :ref:`change naming conventions` for more detailed examples. .. confval:: doctest_optionflags diff --git a/doc/en/example/pythoncollection.txt b/doc/en/example/pythoncollection.txt index e7226b3c2..c51ad97d2 100644 --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -26,17 +26,17 @@ the :confval:`python_files`, :confval:`python_classes` and [pytest] python_files=check_*.py python_classes=Check - python_functions=check + python_functions=*_check -This would make ``pytest`` look for ``check_`` prefixes in -Python filenames, ``Check`` prefixes in classes and ``check`` prefixes -in functions and classes. For example, if we have:: +This would make ``pytest`` look for tests in files that match the ``check_* +.py`` glob-pattern, ``Check`` prefixes in classes, and functions and methods +that match ``*_check``. For example, if we have:: # content of check_myapp.py class CheckMyApp: - def check_simple(self): + def simple_check(self): pass - def check_complex(self): + def complex_check(self): pass then the test collection looks like this:: @@ -48,14 +48,14 @@ then the test collection looks like this:: - - + + ============================= in 0.01 seconds ============================= .. note:: - the ``python_functions`` and ``python_classes`` has no effect + the ``python_functions`` and ``python_classes`` options has no effect for ``unittest.TestCase`` test discovery because pytest delegates detection of test case methods to unittest code. diff --git a/testing/test_collection.py b/testing/test_collection.py index 754f3c9ab..e8e47690f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -528,6 +528,30 @@ class Test_genitems: assert s.endswith("test_example_items1.testone") print(s) + def test_class_and_functions_discovery_using_glob(self, testdir): + """ + tests that python_classes and python_functions config options work + as prefixes and glob-like patterns (issue #600). + """ + testdir.makeini(""" + [pytest] + python_classes = *Suite Test + python_functions = *_test test + """) + p = testdir.makepyfile(''' + class MyTestSuite: + def x_test(self): + pass + + class TestCase: + def test_y(self): + pass + ''') + items, reprec = testdir.inline_genitems(p) + ids = [x.getmodpath() for x in items] + assert ids == ['MyTestSuite.x_test', 'TestCase.test_y'] + + def test_matchnodes_two_collections_same_file(testdir): testdir.makeconftest(""" import pytest