From 222457d7d9898681bc3e035e8a65133503d13e04 Mon Sep 17 00:00:00 2001 From: sven <42868792+FreerGit@users.noreply.github.com> Date: Thu, 12 Sep 2024 19:33:16 +0200 Subject: [PATCH 1/2] Add `discover_imports` in conf - Allows a user to define whether or not pytest should treat imported (but not in testpaths) as test classes. - Imagine a class is called TestFoo defined in src/ dir, when discover_imports is disabled, TestFoo is not treated as a test class. --- AUTHORS | 1 + changelog/12749.feature.rst | 3 ++ src/_pytest/main.py | 5 ++ src/_pytest/python.py | 6 +++ testing/test_discover_imports.py | 88 ++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 changelog/12749.feature.rst create mode 100644 testing/test_discover_imports.py diff --git a/AUTHORS b/AUTHORS index 374e6ad9bcc..b1dd40dbd4c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -400,6 +400,7 @@ Stefanie Molin Stefano Taschini Steffen Allner Stephan Obermann +Sven Sven-Hendrik Haase Sviatoslav Sydorenko Sylvain MariƩ diff --git a/changelog/12749.feature.rst b/changelog/12749.feature.rst new file mode 100644 index 00000000000..138a5bc7914 --- /dev/null +++ b/changelog/12749.feature.rst @@ -0,0 +1,3 @@ +Add :confval:`discover_imports`, when disabled (default) will make sure to not consider classes which are imported by a test file and starts with Test. + +-- by :user:`FreerGit` \ No newline at end of file diff --git a/src/_pytest/main.py b/src/_pytest/main.py index e5534e98d69..4887d336b2d 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -78,6 +78,11 @@ def pytest_addoption(parser: Parser) -> None: type="args", default=[], ) + parser.addini( + "discover_imports", + "Whether to discover tests in imported modules outside `testpaths`", + default=False, + ) group = parser.getgroup("general", "Running and selection options") group._addoption( "-x", diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 9c54dd20f80..9467b26fd02 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -741,6 +741,12 @@ def newinstance(self): return self.obj() def collect(self) -> Iterable[nodes.Item | nodes.Collector]: + if self.config.getini("discover_imports") == ("false" or False): + paths = self.config.getini("testpaths") + class_file = inspect.getfile(self.obj) + if not any(string in class_file for string in paths): + return [] + if not safe_getattr(self.obj, "__test__", True): return [] if hasinit(self.obj): diff --git a/testing/test_discover_imports.py b/testing/test_discover_imports.py new file mode 100644 index 00000000000..829b614ed46 --- /dev/null +++ b/testing/test_discover_imports.py @@ -0,0 +1,88 @@ +import pytest +import textwrap + +def test_discover_imports_enabled(pytester): + src_dir = pytester.mkdir("src") + tests_dir = pytester.mkdir("tests") + pytester.makeini(""" + [pytest] + testpaths = "tests" + discover_imports = true + """) + + src_file = src_dir / "foo.py" + + src_file.write_text(textwrap.dedent("""\ + class TestClass(object): + def __init__(self): + super().__init__() + + def test_foobar(self): + return true + """ + ), encoding="utf-8") + + test_file = tests_dir / "foo_test.py" + test_file.write_text(textwrap.dedent("""\ + import sys + import os + + current_file = os.path.abspath(__file__) + current_dir = os.path.dirname(current_file) + parent_dir = os.path.abspath(os.path.join(current_dir, '..')) + sys.path.append(parent_dir) + + from src.foo import TestClass + + class TestDomain: + def test_testament(self): + testament = TestClass() + pass + """), encoding="utf-8") + + result = pytester.runpytest() + result.assert_outcomes(errors=1) + +def test_discover_imports_disabled(pytester): + + src_dir = pytester.mkdir("src") + tests_dir = pytester.mkdir("tests") + pytester.makeini(""" + [pytest] + testpaths = "tests" + discover_imports = false + """) + + src_file = src_dir / "foo.py" + + src_file.write_text(textwrap.dedent("""\ + class Testament(object): + def __init__(self): + super().__init__() + self.collections = ["stamp", "coin"] + + def personal_property(self): + return [f"my {x} collection" for x in self.collections] + """ + ), encoding="utf-8") + + test_file = tests_dir / "foo_test.py" + test_file.write_text(textwrap.dedent("""\ + import sys + import os + + current_file = os.path.abspath(__file__) + current_dir = os.path.dirname(current_file) + parent_dir = os.path.abspath(os.path.join(current_dir, '..')) + sys.path.append(parent_dir) + + from src.foo import Testament + + class TestDomain: + def test_testament(self): + testament = Testament() + assert testament.personal_property() + """), encoding="utf-8") + + result = pytester.runpytest() + result.assert_outcomes(passed=1) \ No newline at end of file From fa3b6310c8202e820cd0195960d14d10a1f99303 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:38:20 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/12749.feature.rst | 2 +- testing/test_discover_imports.py | 38 +++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/changelog/12749.feature.rst b/changelog/12749.feature.rst index 138a5bc7914..a6fb8bf9e62 100644 --- a/changelog/12749.feature.rst +++ b/changelog/12749.feature.rst @@ -1,3 +1,3 @@ Add :confval:`discover_imports`, when disabled (default) will make sure to not consider classes which are imported by a test file and starts with Test. --- by :user:`FreerGit` \ No newline at end of file +-- by :user:`FreerGit` diff --git a/testing/test_discover_imports.py b/testing/test_discover_imports.py index 829b614ed46..cdb55916ba6 100644 --- a/testing/test_discover_imports.py +++ b/testing/test_discover_imports.py @@ -1,6 +1,8 @@ -import pytest +from __future__ import annotations + import textwrap + def test_discover_imports_enabled(pytester): src_dir = pytester.mkdir("src") tests_dir = pytester.mkdir("tests") @@ -12,18 +14,21 @@ def test_discover_imports_enabled(pytester): src_file = src_dir / "foo.py" - src_file.write_text(textwrap.dedent("""\ + src_file.write_text( + textwrap.dedent("""\ class TestClass(object): def __init__(self): super().__init__() def test_foobar(self): return true - """ - ), encoding="utf-8") + """), + encoding="utf-8", + ) test_file = tests_dir / "foo_test.py" - test_file.write_text(textwrap.dedent("""\ + test_file.write_text( + textwrap.dedent("""\ import sys import os @@ -38,13 +43,15 @@ class TestDomain: def test_testament(self): testament = TestClass() pass - """), encoding="utf-8") + """), + encoding="utf-8", + ) result = pytester.runpytest() result.assert_outcomes(errors=1) + def test_discover_imports_disabled(pytester): - src_dir = pytester.mkdir("src") tests_dir = pytester.mkdir("tests") pytester.makeini(""" @@ -55,7 +62,8 @@ def test_discover_imports_disabled(pytester): src_file = src_dir / "foo.py" - src_file.write_text(textwrap.dedent("""\ + src_file.write_text( + textwrap.dedent("""\ class Testament(object): def __init__(self): super().__init__() @@ -63,11 +71,13 @@ def __init__(self): def personal_property(self): return [f"my {x} collection" for x in self.collections] - """ - ), encoding="utf-8") + """), + encoding="utf-8", + ) test_file = tests_dir / "foo_test.py" - test_file.write_text(textwrap.dedent("""\ + test_file.write_text( + textwrap.dedent("""\ import sys import os @@ -82,7 +92,9 @@ class TestDomain: def test_testament(self): testament = Testament() assert testament.personal_property() - """), encoding="utf-8") + """), + encoding="utf-8", + ) result = pytester.runpytest() - result.assert_outcomes(passed=1) \ No newline at end of file + result.assert_outcomes(passed=1)