holger krekel avatar holger krekel committed ae80b0f

introduce new discovery mechanism
XXX experiment with using it before introducing it or wait
for feature request

Comments (0)

Files changed (6)

_pytest/config.py

         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,))
         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:
             raise ValueError("unknown configuration value: %r" %(name,))

_pytest/python.py

     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:
 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)
 
 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

doc/customize.txt

    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 = <glob-pattern> <glob-pattern>
+            func = <function-or-method-name-prefix>
+            class = <class-name-prefix>
+
+   See :ref:`change naming conventions` for examples. the ``class``
+   to be empty in which case all non-underscore empty classes
+   will be considered.
+

doc/example/pythoncollection.txt

 
 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
+    <Module 'check_myapp.py'>
+      <Class 'CheckMyApp'>
+        <Instance '()'>
+          <Function 'check_simple'>
+          <Function 'check_complex'>
+
+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
 -----------------------------------------------
 

testing/test_python.py

 
 
 
+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*",
+    ])
 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
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.