diff --git a/_pytest/config.py b/_pytest/config.py index 95f9627d0..fef1b5367 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -258,6 +258,7 @@ class Config(object): self.trace = self.pluginmanager.trace.root.get("config") self._conftest = Conftest(onimport=self._onimportconftest) self.hook = self.pluginmanager.hook + self._inicache = {} def _onimportconftest(self, conftestmodule): self.trace("loaded conftestmodule %r" %(conftestmodule,)) @@ -332,6 +333,13 @@ class Config(object): """ return configuration value from an ini file. If the specified name hasn't been registered through a prior ``parse.addini`` call (usually from a plugin), a ValueError is raised. """ + try: + return self._inicache[name] + except KeyError: + self._inicache[name] = val = self._getini(name) + return val + + def _getini(self, name): try: description, type, default = self._parser._inidict[name] except KeyError: diff --git a/_pytest/python.py b/_pytest/python.py index c894becb6..f6474581a 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -13,6 +13,13 @@ def pytest_addoption(parser): group.addoption('--funcargs', action="store_true", dest="showfuncargs", default=False, help="show available function arguments, sorted by plugin") + parser.addini("python_files", type="args", + 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") + parser.addini("python_functions", type="args", default=("test",), + help="prefixes for Python test function and method discovery") def pytest_cmdline_main(config): if config.option.showfuncargs: @@ -46,8 +53,13 @@ def pytest_pyfunc_call(__multicall__, pyfuncitem): def pytest_collect_file(path, parent): ext = path.ext pb = path.purebasename - if ext == ".py" and (pb.startswith("test_") or pb.endswith("_test") or - parent.session.isinitpath(path)): + if ext == ".py": + if not parent.session.isinitpath(path): + for pat in parent.config.getini('python_files'): + if path.fnmatch(pat): + break + else: + return return parent.ihook.pytest_pycollect_makemodule( path=path, parent=parent) @@ -145,9 +157,14 @@ class PyobjMixin(object): class PyCollectorMixin(PyobjMixin, pytest.Collector): def funcnamefilter(self, name): - return name.startswith('test') + for prefix in self.config.getini("python_functions"): + if name.startswith(prefix): + return True + def classnamefilter(self, name): - return name.startswith('Test') + for prefix in self.config.getini("python_classes"): + if name.startswith(prefix): + return True def collect(self): # NB. we avoid random getattrs and peek in the __dict__ instead diff --git a/doc/customize.txt b/doc/customize.txt index bbaae35c9..5797e857c 100644 --- a/doc/customize.txt +++ b/doc/customize.txt @@ -86,3 +86,18 @@ builtin configuration file options This would tell py.test to not recurse into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory. +.. confval:: python_discovery + + Determines names and patterns for finding Python tests, specified + through indendent ``name = value`` settings with these possible names:: + + [pytest] + python_discovery = + file = + func = + class = + + See :ref:`change naming conventions` for examples. the ``class`` + to be empty in which case all non-underscore empty classes + will be considered. + diff --git a/doc/example/pythoncollection.txt b/doc/example/pythoncollection.txt index 98c7071ec..954368905 100644 --- a/doc/example/pythoncollection.txt +++ b/doc/example/pythoncollection.txt @@ -12,19 +12,63 @@ You can set the :confval:`norecursedirs` option in an ini-file, for example your This would tell py.test to not recurse into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory. -always try to interpret arguments as Python packages +.. _`change naming conventions`: + +change naming conventions +----------------------------------------------------- + +You can configure different naming conventions by setting +the :confval:`pytest_pycollect` configuration option. Example:: + + # content of setup.cfg + # can also be defined in in tox.ini or pytest.ini file + [pytest] + python_files=check_*.py + python_classes=Check + python_functions=check + +This would make py.test look for ``check_`` prefixes in +Python filenames, ``Check`` prefixes in classes and ``check`` prefixes +in functions and classes. For example, if we have:: + + # content of check_myapp.py + class CheckMyApp: + def check_simple(self): + pass + def check_complex(self): + pass + +then the test collection looks like this:: + + $ py.test --collectonly + + + + + + +interpret cmdline arguments as Python packages ----------------------------------------------------- You can use the ``--pyargs`` option to make py.test try interpreting arguments as python package names, deriving -their file system path and then running the test. Through -an ini-file and the :confval:`addopts` option you can make -this change more permanently:: +their file system path and then running the test. For +example if you have unittest2 installed you can type:: + + py.test --pyargs unittest2.test.test_skipping -q + +which will run the respective test module. Like with +other options, through an ini-file and the :confval:`addopts` option you +can make this change more permanently:: # content of pytest.ini [pytest] addopts = --pyargs +Now a simple invocation of ``py.test NAME`` will check +if NAME exists as an importable package/module and otherwise +treat it as a filesystem path. + finding out what is collected ----------------------------------------------- diff --git a/testing/test_python.py b/testing/test_python.py index 82f02d826..a2d9e3670 100644 --- a/testing/test_python.py +++ b/testing/test_python.py @@ -1208,3 +1208,32 @@ class TestRaises: +def test_customized_python_discovery(testdir): + testdir.makeini(""" + [pytest] + python_files=check_*.py + python_classes=Check + python_functions=check + """) + p = testdir.makepyfile(""" + def check_simple(): + pass + class CheckMyApp: + def check_meth(self): + pass + """) + p2 = p.new(basename=p.basename.replace("test", "check")) + p.move(p2) + result = testdir.runpytest("--collectonly", "-s") + result.stdout.fnmatch_lines([ + "*check_customized*", + "*check_simple*", + "*CheckMyApp*", + "*check_meth*", + ]) + + result = testdir.runpytest() + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*2 passed*", + ]) diff --git a/tox.ini b/tox.ini index 0c20f30f7..86f89d646 100644 --- a/tox.ini +++ b/tox.ini @@ -73,3 +73,6 @@ minversion=2.0 plugins=pytester addopts= -rxf --pyargs --doctest-modules rsyncdirs=tox.ini pytest.py _pytest testing +python_files=test_*.py *_test.py +python_classes=Test Acceptance +python_functions=test