In Pytest, What Is the Use of Conftest.Py Files

In pytest, what is the use of conftest.py files?

Is this the correct use of conftest.py?

Yes it is. Fixtures are a potential and common use of conftest.py. The
fixtures that you will define will be shared among all tests in your test suite. However, defining fixtures in the root conftest.py might be useless and it would slow down testing if such fixtures are not used by all tests.

Does it have other uses?

Yes it does.

  • Fixtures: Define fixtures for static data used by tests. This data can be accessed by all tests in the suite unless specified otherwise. This could be data as well as helpers of modules which will be passed to all tests.

  • External plugin loading: conftest.py is used to import external plugins or modules. By defining the following global variable, pytest will load the module and make it available for its test. Plugins are generally files defined in your project or other modules which might be needed in your tests. You can also load a set of predefined plugins as explained here.

    pytest_plugins = "someapp.someplugin"

  • Hooks: You can specify hooks such as setup and teardown methods and much more to improve your tests. For a set of available hooks, read Hooks link. Example:

      def pytest_runtest_setup(item):
    """ called before ``pytest_runtest_call(item). """
    #do some stuff`
  • Test root path: This is a bit of a hidden feature. By defining conftest.py in your root path, you will have pytest recognizing your application modules without specifying PYTHONPATH. In the background, py.test modifies your sys.path by including all submodules which are found from the root path.

Can I have more than one conftest.py file?

Yes you can and it is strongly recommended if your test structure is somewhat complex. conftest.py files have directory scope. Therefore, creating targeted fixtures and helpers is good practice.

When would I want to do that? Examples will be appreciated.

Several cases could fit:

Creating a set of tools or hooks for a particular group of tests.

root/mod/conftest.py

def pytest_runtest_setup(item):
print("I am mod")
#do some stuff

test root/mod2/test.py will NOT produce "I am mod"

Loading a set of fixtures for some tests but not for others.

root/mod/conftest.py

@pytest.fixture()
def fixture():
return "some stuff"

root/mod2/conftest.py

@pytest.fixture()
def fixture():
return "some other stuff"

root/mod2/test.py

def test(fixture):
print(fixture)

Will print "some other stuff".

Overriding hooks inherited from the root conftest.py.

root/mod/conftest.py

def pytest_runtest_setup(item):
print("I am mod")
#do some stuff

root/conftest.py

def pytest_runtest_setup(item):
print("I am root")
#do some stuff

By running any test inside root/mod, only "I am mod" is printed.

You can read more about conftest.py here.

EDIT:

What if I need plain-old helper functions to be called from a number
of tests in different modules - will they be available to me if I put
them in a conftest.py? Or should I simply put them in a helpers.py
module and import and use it in my test modules?

You can use conftest.py to define your helpers. However, you should follow common practice. Helpers can be used as fixtures at least in pytest. For example in my tests I have a mock redis helper which I inject into my tests this way.

root/helper/redis/redis.py

@pytest.fixture
def mock_redis():
return MockRedis()

root/tests/stuff/conftest.py

pytest_plugin="helper.redis.redis"

root/tests/stuff/test.py

def test(mock_redis):
print(mock_redis.get('stuff'))

This will be a test module that you can freely import in your tests. NOTE that you could potentially name redis.py as conftest.py if your module redis contains more tests. However, that practice is discouraged because of ambiguity.

If you want to use conftest.py, you can simply put that helper in your root conftest.py and inject it when needed.

root/tests/conftest.py

@pytest.fixture
def mock_redis():
return MockRedis()

root/tests/stuff/test.py

def test(mock_redis):
print(mock_redis.get(stuff))

Another thing you can do is to write an installable plugin. In that case your helper can be written anywhere but it needs to define an entry point to be installed in your and other potential test frameworks. See this.

If you don't want to use fixtures, you could of course define a simple helper and just use the plain old import wherever it is needed.

root/tests/helper/redis.py

class MockRedis():
# stuff

root/tests/stuff/test.py

from helper.redis import MockRedis

def test():
print(MockRedis().get(stuff))

However, here you might have problems with the path since the module is not in a child folder of the test. You should be able to overcome this (not tested) by adding an __init__.py to your helper

root/tests/helper/init.py

from .redis import MockRedis

Or simply adding the helper module to your PYTHONPATH.

Can the conftest.py file be put in another package aside the tests package for pytest?

The answer is no, this is not possible without workarounds, and I would advice against it.

If you use a framework, you should use the conventions that the framework provides - this makes the usage easier, and you won't run into problems because of some usage that the framework does not expect. By framework here I mean pytest, which is more than an executable, as it provides (and expects) a certain infrastructure (which certainly could be documented better...). You can find more information for example in answers to the SO question In pytest, what is the use of conftest.py files?.

pytest uses conftest.py files to provide fixtures, plugins and hooks to tests in a hierarchical way.

So, say, you have the structure:

tests
test_a
test_a1.py
test_a2.py
test_b
test_b1.py
test_b2.py

where the tests in test_a use a fixture local_fixture, the tests in test_b use another fixture local_fixture, and all tests use the common fixture common_fixture. The easiest way to do this is the following:

tests
conftest.py -> contains common_fixture
test_a
conftest.py -> contains local_fixture for test_a...
test_a1.py
test_a2.py
test_b
conftest.py -> contains local_fixture for test_b...
test_b1.py
test_b2.py

There can also be conftest.py files in plugins that are provided by other modules, and all of these fixtures you can just use without importing them, or doing some PYTHONPATH magic. You just have to go with the common conventions instead of using your own to be able to use that power.

Can I use single conftest.py for different test directories?

You can combine the fixtures into a conftest.py file within the top-level test directory. Per this documentation, conftest files are per directory. The conftest file (and fixtures and plugins therein) should be available to test files in that directory and in child directories. If you do that, I recommend making your test directory into a python package by adding a __init__.py file (per the note in this documentation).

Using conftest.py vs. importing fixtures from dedicate modules

There's not a huge amount of difference, it's mainly just down to preference. I mainly use conftest.py to pull in fixures that are required, but not directly used by your test. So you may have a fixture that does something useful with a database, but needs a database connection to do so. So you make the db_connection fixture available in conftest.py, and then your test only has to do something like:

conftest.py

from tests.database_fixtures import db_connection

__all__ = ['db_connection']

tests/database_fixtures.py

import pytest

@pytest.fixture
def db_connection():
...

@pytest.fixture
def new_user(db_connection):
...

test/test_user.py

from tests.database_fixtures import new_user

def test_user(new_user):
assert new_user.id > 0 # or whatever the test needs to do

If you didn't make db_connection available in conftest.py or directly import it then pytest would fail to find the db_connection fixture when trying to use the new_user fixture. If you directly import db_connection into your test file, then linters will complain that it is an unused import. Worse, some may remove it, and cause your tests to fail. So making the db_connection available in conftest.py, to me, is the simplest solution.

Overriding Fixtures

The one significant difference is that it is easier to override fixtures using conftest.py. Say you have a directory layout of:

./
├─ conftest.py
└─ tests/
├─ test_foo.py
└─ bar/
├─ conftest.py
└─ test_foobar.py

In conftest.py you could have:

import pytest

@pytest.fixture
def some_value():
return 'foo'

And then in tests/bar/conftest.py you could have:

import pytest

@pytest.fixture
def some_value(some_value):
return some_value + 'bar'

Having multiple conftests allows you to override a fixture whilst still maintaining access to the original fixture. So following tests would all work.

tests/test_foo.py

def test_foo(some_value):
assert some_value == 'foo'

tests/bar/test_foobar.py

def test_foobar(some_value):
assert some_value == 'foobar'

You can still do this without conftest.py, but it's a bit more complicated. You'd need to do something like:

import pytest

# in this scenario we would have something like:
# mv contest.py tests/custom_fixtures.py
from tests.custom_fixtures import some_value as original_some_value

@pytest.fixture
def some_value(original_some_value):
return original_some_value + 'bar'

def test_foobar(some_value):
assert some_value == 'foobar'

In Pytest, does conftest.py at the root level get overridden by conftests deeper in the tree?

I would add conftest.py to ROOT folder, only if I was 100% sure all my tests are using all imported fixtures there, otherwise you are just slowing down automation execution which is critical.

I have tested the following:

Tests
conftest.py
test_folder_1
conftest.py
test_folder_2

Both conftest.py files had fixture function with the same name.
in test_folder_1/conftest.py - I have set scope="function" and returned different value compared to the same fixture in Tests/conftest.py (which had scope="class").

In a test under test_folder_1,
I was able to use the updated fixture from test_folder_1/conftest.py.
I am not able to apply this fixture on a class, as the scope changed.



Related Topics



Leave a reply



Submit