Splitting a conftest.py file into several smaller conftest-like parts
Question:
I’ve got a large conftest.py file that I wish to split into smaller parts, for two reasons:
- The file is very large (~1000 lines, including documentation)
- Some of the fixtures depend on other fixtures, and I have no reason to expose those other fixtures as part of the conftest “API” when users look for relevant fixtures
I am not aware of any mechanism provided by pytest to resolve conftest files in multiple locations within the same folder, so I contrived one, below:
import sys
import os
sys.path.append(os.path.dirname(__file__))
from _conftest_private_part_1 import *
from _conftest_private_part_2 import *
from _conftest_private_part_3 import *
@pytest.fixture
def a_fixture_that_is_part_of_the_public_conftest_api():
pass
This works for my needs, but I do wonder if there is a better way.
Answers:
You shouldn’t need any fancy magic for that. py.test automatically adds the path of the current test file to sys.path
, as well as all parent paths up to the directory it was targeted at.
Because of that, you don’t even need to put that shared code into a conftest.py
. You can just put into plain modules or packages and then import it (if you want to share fixtures, those have to be in a conftest.py
).
Also, there is this note about importing from conftest.py
in the documentation:
If you have conftest.py files which do not reside in a python package
directory (i.e. one containing an __init__.py
) then “import conftest
”
can be ambiguous because there might be other conftest.py
files as
well on your PYTHONPATH
or sys.path
. It is thus good practise for
projects to either put conftest.py
under a package scope or to never
import anything from a conftest.py
file.
You can put your stuff in other modules and reference them using a pytest_plugins
variable in your conftest.py
:
pytest_plugins = ['module1', 'module2']
This will also work if your conftest.py
has hooks on them.
This works for me and seems easier/clearer:
Top level tests/conftest.py (example of re-usable print debug of requests.Response):
import pytest
import requests
from requests_toolbelt.utils import dump
@pytest.fixture(scope="session")
def print_response(response: requests.Response):
data = dump.dump_all(response)
print("========================")
print(data.decode('utf-8'))
print("========================")
print("response.url = {}".format(response.url))
print("response.request = {}".format(response.request))
print("response.status_code = {}".format(response.status_code))
print("response.headers['content-type'] = {}".format(response.headers['content-type']))
print("response.encoding = {}".format(response.encoding))
try:
print("response.json = {}".format(response.json()))
except Exception:
print("response.text = {}".format(response.text))
print("response.end")
From lower level conftest import the higher level conftest code – e.g., tests/package1/conftest.py:
from tests.conftest import *
Then, in your lower level tests within tests/package1/test_*.py, you simply import via:
from tests.package1 import conftest
And then you have the merged configtests from one conftest available. Repeat this pattern for your other lower level detailed/modular conftest.py files throughout the tests hierarchy.
Alternativelly, you could try:
In my_fixtures.py
:
@pytest.fixture
def some_fixture():
yield
In conftest.py
:
import my_fixtures
# Adding the fixture to attributes of `conftest` will register it
some_fixture = my_fixtures.some_fixture
It seems that pytest
detect fixture by iterating over conftest
attributes and checking for some attr._pytestfixturefunction
added by @pytest.fixture
.
So as long as conftest.py
contains fixture attributes, it doesn’t really matter which file is the fixture defined.
I haven’t tried it but I would expect the following to work too:
# In `conftest.py`
from my_fixtures import *
I’ve got a large conftest.py file that I wish to split into smaller parts, for two reasons:
- The file is very large (~1000 lines, including documentation)
- Some of the fixtures depend on other fixtures, and I have no reason to expose those other fixtures as part of the conftest “API” when users look for relevant fixtures
I am not aware of any mechanism provided by pytest to resolve conftest files in multiple locations within the same folder, so I contrived one, below:
import sys
import os
sys.path.append(os.path.dirname(__file__))
from _conftest_private_part_1 import *
from _conftest_private_part_2 import *
from _conftest_private_part_3 import *
@pytest.fixture
def a_fixture_that_is_part_of_the_public_conftest_api():
pass
This works for my needs, but I do wonder if there is a better way.
You shouldn’t need any fancy magic for that. py.test automatically adds the path of the current test file to sys.path
, as well as all parent paths up to the directory it was targeted at.
Because of that, you don’t even need to put that shared code into a conftest.py
. You can just put into plain modules or packages and then import it (if you want to share fixtures, those have to be in a conftest.py
).
Also, there is this note about importing from conftest.py
in the documentation:
If you have conftest.py files which do not reside in a python package
directory (i.e. one containing an__init__.py
) then “import conftest
”
can be ambiguous because there might be otherconftest.py
files as
well on yourPYTHONPATH
orsys.path
. It is thus good practise for
projects to either putconftest.py
under a package scope or to never
import anything from aconftest.py
file.
You can put your stuff in other modules and reference them using a pytest_plugins
variable in your conftest.py
:
pytest_plugins = ['module1', 'module2']
This will also work if your conftest.py
has hooks on them.
This works for me and seems easier/clearer:
Top level tests/conftest.py (example of re-usable print debug of requests.Response):
import pytest
import requests
from requests_toolbelt.utils import dump
@pytest.fixture(scope="session")
def print_response(response: requests.Response):
data = dump.dump_all(response)
print("========================")
print(data.decode('utf-8'))
print("========================")
print("response.url = {}".format(response.url))
print("response.request = {}".format(response.request))
print("response.status_code = {}".format(response.status_code))
print("response.headers['content-type'] = {}".format(response.headers['content-type']))
print("response.encoding = {}".format(response.encoding))
try:
print("response.json = {}".format(response.json()))
except Exception:
print("response.text = {}".format(response.text))
print("response.end")
From lower level conftest import the higher level conftest code – e.g., tests/package1/conftest.py:
from tests.conftest import *
Then, in your lower level tests within tests/package1/test_*.py, you simply import via:
from tests.package1 import conftest
And then you have the merged configtests from one conftest available. Repeat this pattern for your other lower level detailed/modular conftest.py files throughout the tests hierarchy.
Alternativelly, you could try:
In my_fixtures.py
:
@pytest.fixture
def some_fixture():
yield
In conftest.py
:
import my_fixtures
# Adding the fixture to attributes of `conftest` will register it
some_fixture = my_fixtures.some_fixture
It seems that pytest
detect fixture by iterating over conftest
attributes and checking for some attr._pytestfixturefunction
added by @pytest.fixture
.
So as long as conftest.py
contains fixture attributes, it doesn’t really matter which file is the fixture defined.
I haven’t tried it but I would expect the following to work too:
# In `conftest.py`
from my_fixtures import *