109

Question

How can I import helper functions in test files without creating packages in the test directory?


Context

I'd like to create a test helper function that I can import in several tests. Say, something like this:

# In common_file.py

def assert_a_general_property_between(x, y):
    # test a specific relationship between x and y
    assert ...


# In test/my_test.py

def test_something_with(x):
    some_value = some_function_of_(x)
    assert_a_general_property_between(x, some_value)

Using Python 3.5, with py.test 2.8.2


Current "solution"

I'm currently doing this via importing a module inside my project's test directory (which is now a package), but I'd like to do it with some other mechanism if possible (so that my test directory doesn't have packages but just tests, and the tests can be run on an installed version of the package, as is recommended here in the py.test documentation on good practices).

Juan Carlos Coto
  • 11,900
  • 22
  • 62
  • 102
  • 23
    It seems crazy that pytest discourages `__init__.py`-files but at the same time provide no alternative to sharing helper functions between tests. My hair is turning gray over this. – qff Nov 30 '16 at 18:49
  • What surprised me was the lack of `short test summary info` when I import a plain, sister, file, say `helpers` containing a function that fails an `assert`. If the functions **aren't** `import`ed, I still get as helpful output as if they were inline in the `test_` function being run. – John Aug 21 '22 at 12:08

9 Answers9

106

You could define a helper class in conftest.py, then create a fixture that returns that class (or an instance of it, depending on what you need).

import pytest


class Helpers:
    @staticmethod
    def help_me():
        return "no"


@pytest.fixture
def helpers():
    return Helpers

Then in your tests, you can use the fixture:

def test_with_help(helpers):
    helpers.help_me()
augurar
  • 12,081
  • 6
  • 50
  • 65
  • 12
    Although this pattern feels a bit hacky, it's really pytest's fault IMO (why not provide a mechanism for such an obvious requirement?) and I find it preferable than manipulating the import path (which I try to avoid as much as possible in general) as the accepted answer does. – cjauvin Apr 11 '20 at 01:11
  • Would this technique allow them to be used in the special pytest callbacks such as `pytest_collection_modifyitems`? – jxramos Dec 17 '20 at 23:36
  • @jxramos This approach makes the helper object available as an ordinary pytest fixture. You could use [`pytest.mark.usefixtures`](https://docs.pytest.org/en/stable/reference.html#pytest-mark-usefixtures) to dynamically add the fixture by name to a test item during the collection phase. – augurar Dec 21 '20 at 06:04
62

my option is to create an extra dir in tests dir and add it to pythonpath in the conftest so.

tests/
    helpers/
      utils.py
      ...
    conftest.py
setup.cfg

in the conftest.py

import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers'))

in setup.cfg

[pytest]
norecursedirs=tests/helpers

this module will be available with import utils, only be careful to name clashing.

A. Sarid
  • 3,916
  • 2
  • 31
  • 56
sax
  • 3,708
  • 19
  • 22
  • 2
    I really liked this solution, especially because it keeps the code's import path configuration with the tests', which, to me, makes for a simpler design. Thanks! – Juan Carlos Coto Nov 16 '15 at 20:48
  • Can you add a snippet on how to import the classes in utils.py in any test_x.py? – timekeeper Oct 02 '17 at 03:35
  • 1
    @sax, what is the benefit of using this over just defining a helper function in the `test_module.py` using `def helper_fun():`. As long as the helper function doesn't start with `test_helper_fun`, it wont be collected by pytest. But it if you want to run it in a particular test function you can still call `helper_func`. Is there a reason to complicate it as such? – alpha_989 Apr 25 '18 at 20:09
  • If you indeed want to keep it in a separate folder, and dont want to mix the helper functions with the `test_module.py`, cant you just use import `from tests.helper.util import helper_fun` to be able to access the helper function in the test_function? – alpha_989 Apr 25 '18 at 20:10
  • using pytest is reccomended do not have `tests` directory importable, so you cannot use your function from other modules – sax Apr 26 '18 at 18:02
  • When you use your helper function in a test, use "from utils import function_name" for the import in your test file. – Dave Jul 10 '20 at 15:07
37

While searching for a solution for this problem I came across this SO question and ended up adopting the same approach. Creating a helpers package, munging sys.path to make it importable and then just importing it...

This did not seem the best approach, so, I created pytest-helpers-namespace. This plugin allows you to register helper functions on your conftest.py:

import pytest

pytest_plugins = ['helpers_namespace']

@pytest.helpers.register
def my_custom_assert_helper(blah):
    assert blah

# One can even specify a custom name for the helper
@pytest.helpers.register(name='assertme')
def my_custom_assert_helper_2(blah):
    assert blah

# And even namespace helpers
@pytest.helpers.asserts.register(name='me')
def my_custom_assert_helper_3(blah):
    assert blah

And then, within a test case function body just use it like

def test_this():
    assert pytest.helpers.my_custom_assert_helper(blah) 

def test_this_2():
    assert pytest.helpers.assertme(blah)

def test_this_3():
    assert pytest.helpers.asserts.me(blah)

Its pretty simple and the documentation pretty small. Take a look and tell me if it addresses your problem too.

s0undt3ch
  • 755
  • 6
  • 12
  • Cool, I'll take a look. Thanks! – Juan Carlos Coto Apr 03 '16 at 16:34
  • I tried this, however I always get 'RuntimeError: The helper being called was not registred' errors. – Michael Barton May 05 '16 at 22:26
  • Could you please file a ticket against the plugin with an example of how to trigger your RuntimeError. https://github.com/saltstack/pytest-helpers-namespace – s0undt3ch May 06 '16 at 08:40
  • @s0undt3ch thanks for making this plugin. I cant seem to be able to use fixtures predefined in the conftest.py along side with help. How would I use a fixture along side with helper functions? – Ken Jun 18 '19 at 21:07
  • Ken, could you please file an issue against the plugins repo? Ever since pytest dropped namespace support, the plugin relies on a hack to work. Maybe that's the issue, maybe it's not. – s0undt3ch Jun 19 '19 at 06:19
27

Create a helpers package in tests folder:

tests/
    helpers/
      __init__.py
      utils.py
      ...
    # make sure no __init__.py in here!
setup.cfg

in setup.cfg:

[pytest]
norecursedirs=tests/helpers

the helpers will be available with import helpers.

ruohola
  • 21,987
  • 6
  • 62
  • 97
guyskk
  • 2,468
  • 1
  • 15
  • 13
22

To access a method from different modules without creating packages, and have that function operate as a helper function I found the following helpful:

conftest.py:

@pytest.fixture
def compare_test_vs_actual():
    def a_function(test, actual):
        print(test, actual)
    return a_function

test_file.py:

def test_service_command_add(compare_test_vs_actual):
    compare_test_vs_actual("hello", "world")
tknightowl
  • 329
  • 2
  • 4
  • This serves as a way to create dynamic fixtures as well! – CMCDragonkai Nov 02 '18 at 07:12
  • 2
    Or `def _compare_test_vs_actual(): pass` then `@fixture;def compare_test_vs_actual():return _compare_test_vs_actual`. Clearer with less nesting, although if you want to receive a fixture in your fixture the above is probably cleaner. – Chris Jun 10 '19 at 16:03
7

It is possible that some of you will be completely uphold by my suggestion. However, very simple way of using common function or value from other modules is to inject it directly into a common workspace. Example:
conftest.py:

import sys

def my_function():
   return 'my_function() called'

sys.modules['pytest'].common_funct = my_function

test_me.py

import pytest

def test_A():
   print(pytest.common_funct())
Uri
  • 479
  • 1
  • 4
  • 10
4

With pytest 7 (at least) the easiest way to accomplish this is to add the following configuration to you pytest.ini or pyproject.toml. This adds the tests directory to your sys.path and makes the respective modules available:

pythonpath = ["tests"]

Maybe it's even better to separate test-helpers from the tests, but that's a matter of style.

As an example, using a separate directory, and using a pyproject.toml it would look like this:

[tool.pytest.ini_options]
pythonpath = ["test_helpers"]
testpaths = ["tests"]
addopts = [
    "--import-mode=importlib",
]
src/
    ...
tests/
    ...
test_helpers/
    ...

This configures the importlib import mode recommended here, designed exactly to prevent modifying the sys.path, though, if you are careful with your naming and you need to extract common test helpers, this can be a viable option.

0

Injecting a shared object into the pytest package from conftest.py has worked well for me.

# conftest.py
import pytest


class shared:
    @staticmethod
    def assert_a_general_property_between(x, y):
        ...

pytest.shared = shared
# test.py
import pytest


def test_something_with(x):
    some_value = some_function_of_(x)
    pytest.shared.assert_a_general_property_between(x, some_value)
Till Hoffmann
  • 9,479
  • 6
  • 46
  • 64
-2

As another option, this directory structure worked for me:

mypkg/
    ...
test_helpers/
    __init__.py
    utils.py  
    ...
tests/
    my_test.py
    ...

And then in my_test.py import the utilities using: from test_helpers import utils

Racing Tadpole
  • 4,270
  • 6
  • 37
  • 56
  • 2
    I believe this is the same situation I describe in the question, where code and tests are not clearly separated. For releases and such, you would have to think about excluding `test_helpers` as well, for instance. – Juan Carlos Coto Aug 22 '16 at 12:16