ChatGPT解决这个技术问题 Extra ChatGPT

Create and import helper functions in tests without creating packages in test directory using py.test

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).

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.

a
augurar

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()

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.
Would this technique allow them to be used in the special pytest callbacks such as pytest_collection_modifyitems?
@jxramos This approach makes the helper object available as an ordinary pytest fixture. You could use pytest.mark.usefixtures to dynamically add the fixture by name to a test item during the collection phase.
A
A. Sarid

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.


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!
Can you add a snippet on how to import the classes in utils.py in any test_x.py?
@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?
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?
using pytest is reccomended do not have tests directory importable, so you cannot use your function from other modules
s
s0undt3ch

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.


Cool, I'll take a look. Thanks!
I tried this, however I always get 'RuntimeError: The helper being called was not registred' errors.
Could you please file a ticket against the plugin with an example of how to trigger your RuntimeError. github.com/saltstack/pytest-helpers-namespace
@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, 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.
t
tknightowl

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")

This serves as a way to create dynamic fixtures as well!
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.
r
ruohola

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.


This feels like the cleanest solution to me and works flawlessly. This should be the reference way imo. I'm glad I kept scrolling down when viewing the answers here!
suggest to use pytest.ini as config file as recommended in official document.
U
Uri

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())

What’s the difference between this approach and defining it as a fixture?
This approach allows you to use helper functions outside of the test functions.For example while defining pytest.parametrization arguments
R
Racing Tadpole

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


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.