diff --git a/pytest_bdd/bdd_module.py b/pytest_bdd/bdd_module.py new file mode 100644 index 00000000..bc874107 --- /dev/null +++ b/pytest_bdd/bdd_module.py @@ -0,0 +1,30 @@ +import sys + +try: + from types import ModuleType +except ImportError: + ModuleType = type(sys) + +from _pytest.python import Module +from .scenario import scenarios + + +class PytestBDDModule(Module): + """ + Class that represents *.feature file as python module + """ + + def _getobj(self): + # Add _test suffix to module name + module_name = self.nodeid.replace("/", ".").rsplit(".", 1)[0] + "_test" + + # Create dummy module to store tests + module = ModuleType( + name=module_name, doc="Autogenerated python module for {!r} feature file".format(str(self.fspath)) + ) + sys.modules[module_name] = module + + # Add scenarios for dummy module + scenarios(self.fspath.basename, features_base_dir=self.fspath.dirname, module=module) + + return module diff --git a/pytest_bdd/plugin.py b/pytest_bdd/plugin.py index b93e0078..6352ab73 100644 --- a/pytest_bdd/plugin.py +++ b/pytest_bdd/plugin.py @@ -7,6 +7,7 @@ from . import generation from . import reporting from . import gherkin_terminal_reporter +from .bdd_module import PytestBDDModule from .utils import CONFIG_STACK @@ -36,6 +37,7 @@ def pytest_addoption(parser): def add_bdd_ini(parser): parser.addini("bdd_features_base_dir", "Base features directory.") parser.addini("bdd_strict_gherkin", "Parse features to be strict gherkin.", type="bool", default=True) + parser.addini("bdd_auto_collect", "To automatically collect feature files.", type="bool", default=False) @pytest.mark.trylast @@ -106,3 +108,11 @@ def item_key(item): return (item.reportinfo()[:2], declaration_order) items.sort(key=item_key) + + +def pytest_collect_file(path, parent): + """ + Automatically collect *.feature files and create test modules for them. + """ + if CONFIG_STACK[-1].getini("bdd_auto_collect") and path.ext == ".feature": + return PytestBDDModule(parent, fspath=path) diff --git a/pytest_bdd/scenario.py b/pytest_bdd/scenario.py index ba12bcb8..c8403c82 100644 --- a/pytest_bdd/scenario.py +++ b/pytest_bdd/scenario.py @@ -322,8 +322,11 @@ def scenarios(*feature_paths, **kwargs): :param *feature_paths: feature file paths to use for scenarios """ - frame = inspect.stack()[1] - module = inspect.getmodule(frame[0]) + if "module" in kwargs: + module = kwargs.pop("module") + else: + frame = inspect.stack()[1] + module = inspect.getmodule(frame[0]) features_base_dir = kwargs.get("features_base_dir") if features_base_dir is None: diff --git a/tests/feature/test_auto_collect.py b/tests/feature/test_auto_collect.py new file mode 100644 index 00000000..ba83fadb --- /dev/null +++ b/tests/feature/test_auto_collect.py @@ -0,0 +1,39 @@ +"""Auto collect tests.""" +import pytest + +import textwrap + + +@pytest.fixture +def auto_collect_test_dir(testdir): + dirname = "test_auto_collect" + tests = testdir.mkpydir(dirname) + + tests.join("foo.feature").write( + textwrap.dedent( + r""" + Feature: The feature + Scenario: Some scenario + """ + ) + ) + + +def test_auto_collect_tests(auto_collect_test_dir, testdir): + testdir.makeini( + """ + [pytest] + bdd_auto_collect=true + """ + ) + + result = testdir.runpytest() + + result.stdout.fnmatch_lines("collected 1 item") + result.assert_outcomes(passed=1) + + +def test_auto_collect_tests_disabled(auto_collect_test_dir, testdir): + result = testdir.runpytest() + + result.stdout.fnmatch_lines("collected 0 items")