Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow having multiple conftest.py on same level #3582

Closed
ikonst opened this issue Jun 14, 2018 · 19 comments
Closed

Allow having multiple conftest.py on same level #3582

ikonst opened this issue Jun 14, 2018 · 19 comments
Labels
type: question general question, might be closed after 2 weeks of inactivity

Comments

@ikonst
Copy link
Contributor

ikonst commented Jun 14, 2018

In our project, the conftest.py files have grown so large that we felt the need to refactor them into smaller modules. However, modules imported by conftest.py using import cannot contribute fixtures.

We've tried using pytest_plugins but unfortunately (as the documentation says and as my testing confirms), importing a plugin with fixtures in one conftest.py makes them available in tests in parent and sibling hierarchies.

I'd like to propose a simple solution, to have pytest look for conftest.py and then conftest_*.py (under the same directory). This way, unruly conftest.py files could be easily split up.

(Let me know if it's an acceptable solution, I can implement it.)

@pytestbot
Copy link
Contributor

GitMate.io thinks possibly related issues are #3212 (Having all options from all conftest.py for all collected tests), #1931 (Nested conftest.py), #2532 (TypeError on conftest.py during collection), #11 (documentation typo "conftest.py"), and #1404 (conftest.py loaded in wrong order.).

@nicoddemus
Copy link
Member

nicoddemus commented Jun 14, 2018

Hi @ikonst,

For your use case, where you want fixtures in separate files just as a means to keep things more manageable, you can import the fixtures normally into the conftest.py file:

# conftest.py
from ._app_fixture import app
from ._regression_fixture import regression

And so on. Importing fixtures into test files in general is not recommended as it might lead to subtle problems, but importing them into the conftest.py file (and nowhere else) looks like it will solve your problem.

@nicoddemus nicoddemus added the type: question general question, might be closed after 2 weeks of inactivity label Jun 14, 2018
@ikonst
Copy link
Contributor Author

ikonst commented Jun 14, 2018

Would that force me to import fixtures one by one, though?

e.g. let's say I have 100 fixtures: 50 are data model fixtures (that I'd like to store in "conftest_data_models.py") and 50 are network call fixtures ("conftest_network_calls.py").

@nicoddemus
Copy link
Member

No, you can import them using *; as long as they are in the module's namespace by the time pytest looks for fixtures, it will work.

@RonnyPfannschmidt
Copy link
Member

@ikonst note that due to bugs pytest_plugins in conftests also breaks strangely

@blueyed
Copy link
Contributor

blueyed commented Jun 14, 2018

I wonder if it would be feasible or possible already to have a module "conftest"?
I.e. conftest/__init__.py and several other files in conftest/, that get imported in __init__.py.

@nicoddemus
Copy link
Member

Currently we look explicitly for conftest.py files:

conftestpath = parent.join("conftest.py")
if conftestpath.isfile():
mod = self._importconftest(conftestpath)
clist.append(mod)

As to change this to support packages, it is possible I guess, but it would mean to change other places that also look for conftest.py files, for example:

if self.fspath.basename == "conftest.py":
module = self.config.pluginmanager._importconftest(self.fspath)

pytest/src/_pytest/fixtures.py

Lines 1054 to 1057 in 4d0297b

if p.basename.startswith("conftest.py"):
nodeid = p.dirpath().relto(self.config.rootdir)
if p.sep != nodes.SEP:
nodeid = nodeid.replace(p.sep, nodes.SEP)

Because of the possibility of introducing more bugs, I'm not sure it is worth trying to add support for this given that the "workaround" is pretty simple: import your symbols into the conftest.py file, which you would need to do anyway with the conftest/__init__.py approach.

@RonnyPfannschmidt
Copy link
Member

im -1 on this

@nicoddemus
Copy link
Member

Yep, I agree with @RonnyPfannschmidt here.

@ikonst did importing the fixtures into the conftest.py fixed your original issue?

@ikonst
Copy link
Contributor Author

ikonst commented Jun 15, 2018

Thanks for all your help. I'll test this on my codebase and report back.

@nicoddemus
Copy link
Member

Thanks!

@ikonst
Copy link
Contributor Author

ikonst commented Jun 16, 2018

from .fixtures.models import * worked out great

@blueyed
Copy link
Contributor

blueyed commented Jun 16, 2018

Great.
Closing this issue then.

@ikonst
Copy link
Contributor Author

ikonst commented Jul 5, 2018

To follow up, my approach to import all fixtures under fixtures/* is adding to conftest.py:

from pkgutil import walk_packages

from . import fixtures

for package in walk_packages(fixtures.__path__, prefix=fixtures.__name__ + '.'):
    module = package.module_finder.find_module(package.name).load_module()
    globals().update({k: v for k, v in vars(module).items() if not k.startswith('_')})

Having to copy-paste this code snippet across multiple conftest.py files seems suboptimal, but so does implicitly specifying all fixtures, e.g.

from .fixtures.foo import *  # noqa
from .fixtures.bar import *  # noqa

and the # noqas are somewhat noisy.

Furthermore, I didn't find a way to write a pytest plugin to hook into loading of conftest.py files, in order to inject this functionality.

@tpvasconcelos
Copy link

@nicoddemus any suggestions on how to achieve this when using the recommended importlib import mode? It seems to be impossible to perform any imports since tests/ is not recognised as a package and isn't inserted in the sys.path like in the default prepend import mode.

Any suggestions are welcome here! :)

@tpvasconcelos
Copy link

tpvasconcelos commented Jan 10, 2024

For reference, I opted for using the pythonpath option to point to a separate directory containing helpers/utilities for the test suite.


As an example,

  1. Add an extras/ directory to sys.path using the pythonpath option in the pytest.ini config file

    pythonpath = extras
  2. Add your helper modules/package here:

    .
    ├── pytest.ini
    ├── extras/
    │   └── test_helpers/
    │       ├── helper_module_foo.py
    │       └── helper_module_bar.py
    
  3. Impor them from your test modules as:

    from test_helpers.helper_module_foo import foo
    from test_helpers.helper_module_bar import bar
  4. Extra credit if you write tests for your test helpers.

The final project tree looks something like this for me:

.
├── pytest.ini
├── extras/
│   └── test_helpers/
│       ├── helper_module_foo.py
│       └── helper_module_bar.py
├── tests/
│   ├── conftest.py
│   ├── extras/
│   │   └── test_test_helpers/
│   │       ├── test_helper_module_foo.py
│   │       └── test_helper_module_foo.py
│   └── unit/
│       └── # my unit test suite goes here...

@danielschenk
Copy link

The from .foo import * workaround works fine for fixtures not starting with an underscore. However, in a project I'm working on we have some fixtures which we deem internal (eg. because they are intended to be purely autouse, or for use by another fixture in the same file only) which we start with an underscore to clarify this in a Pythonic way.

Since the star import only considers non-underscore items, this workaround does not feel great to me. I have to either do explicit imports or modify __all__ to get this working, or just don't use underscores anymore in my fixture names.

I think that automatic fixture imports only working from test files or conftest.py is one of the biggest hassles for medium to large size pytest projects. conftest.py tends to grow out of control and become hard to read/navigate pretty quickly, and having to create subfolders just to split across multiple conftest.py files also isn't really nice (imagine having 5 conftest.py tabs open in your IDE...).

@The-Compiler
Copy link
Member

@danielschenk It sounds like you just want to use pytest_plugins instead, at least if this is in the root conftest.py file (which I assume it is).

@danielschenk
Copy link

@danielschenk It sounds like you just want to use pytest_plugins instead, at least if this is in the root conftest.py file (which I assume it is).

We will add sibling test folders in the near future which should be (largely) independent, so that is not going to work for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: question general question, might be closed after 2 weeks of inactivity
Projects
None yet
Development

No branches or pull requests

8 participants