如何在py.test中的所有测试的模块之间共享变量

时间:2014-03-31 18:08:16

标签: python unit-testing testing global-variables pytest

我有多个由py.test运行的测试,它们位于多个文件的多个类中。

共享一个大字典的最简单方法是什么?我不想复制它 - 每个文件中每个类的每个方法都要由py.test使用?

简而言之,我需要为每个测试制作一个“全局变量”。在py.test之外,我没有使用这个变量,所以我不想将它存储在被测试的文件中。我经常使用py.test的灯具,但这似乎有点过分了。也许这是唯一的方法?

5 个答案:

答案 0 :(得分:17)

更新:pytest-namespace hook is deprecated/removed。请勿使用。有关详细信息,请参阅#3735

你提到了明显且最不神奇的选择:使用夹具。您可以使用模块中的pytestmark = pytest.mark.usefixtures('big_dict')将其应用于整个模块,但它不会在您的命名空间中,因此明确请求它可能是最好的。

或者,您可以使用hook:

将内容分配到pytest命名空间中
# conftest.py

def pytest_namespace():
    return {'my_big_dict': {'foo': 'bar'}}

现在你有pytest.my_big_dict。夹具可能仍然更好。

答案 1 :(得分:12)

我喜欢py.test有很多东西,但我绝对讨厌的一件事是代码智能工具有多糟糕。我不同意在这种情况下,声明变量的autouse夹具是“最清晰”的方法,因为它不仅完全阻碍了我的linter,而且还有其他任何不熟悉py.test的人。作品。那里有很多魔法,imo。

所以,你可以做的一件事就是不要让你的linter爆炸并且不需要TestCase样板来创建一个名为globals的模块。在此模块中,将您想要全局的内容的名称存根到{}或None,并将全局模块导入到测试中。然后在conftest.py文件中,使用py.test钩子根据需要设置(或重置)全局变量。这样做的好处是可以在构建测试时为您提供存根,并在运行时为测试提供完整的数据。

例如,您可以使用pytest_configure()挂钩在py.test启动时设置您的dict。或者,如果您想确保每次测试之间的数据都是原始的,那么您可以在每次测试之前使用夹具将全局变量分配给已知状态。

# globals.py
my_data = {}  # Create a stub for your variable


# test_module.py
import globals as gbl

def test_foo():
    assert gbl.my_data['foo'] == 'bar'  # The global is in the namespace when creating tests


# conftest.py
import globals as gbl
my_data = {'foo': 'bar'}  # Create the master copy in conftest

@pytest.fixture(autouse=True)
def populate_globals():
    gbl.my_data = my_data  # Assign the master value to the global before each test

这种方法的另一个优点是你可以在你的全局模块中使用类型提示来为你的测试中的全局对象提供代码完成,这对于dict来说可能不是必需的,但是当我使用时我发现它很方便一个对象(如webdriver)。 :)

答案 2 :(得分:7)

每个测试都使用一个大型的全局字典可能是一个坏主意。如果可能的话,我建议重构你的测试以避免这种情况。

那就是说,我就是这样做的:定义一个autouse fixture,在每个函数的全局命名空间中添加对字典的引用。

这是一些代码。它们都在同一个文件中,但您可以将测试结果移到测试顶层的conftest.py

import pytest

my_big_global = {'key': 'value'}

@pytest.fixture(autouse=True)
def myglobal(request):
    request.function.func_globals['foo'] = my_big_global

def test_foo():
    assert foo['key'] == 'value'

def test_bar():
    assert foo['key'] == 'bar'

这是我运行此代码时的输出:

$ py.test test_global.py -vv
======================================= test session starts =======================================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- env/bin/python
collected 2 items

test_global.py:9: test_foo PASSED
test_global.py:12: test_bar FAILED

============================================ FAILURES =============================================
____________________________________________ test_bar _____________________________________________

    def test_bar():
>       assert foo['key'] == 'bar'
E       assert 'value' == 'bar'
E         - value
E         + bar

test_global.py:13: AssertionError
=============================== 1 failed, 1 passed in 0.01 seconds ===============================

请注意,您无法使用会话范围的装置,因为您无法访问每个功能对象。因此,我确保一次定义我的大型全局字典并使用对它的引用 - 如果我在该赋值语句中定义了字典,则每次都会生成一个新的副本。

最后,做这样的事情可能是一个坏主意。祝你好运:)

答案 3 :(得分:5)

我很惊讶没有回答提到缓存:从版本2.8开始,pytest具有强大的缓存机制。

用法示例

@pytest.fixture(autouse=True)
def init_cache(request):
    data = request.config.cache.get('my_data', None)
    data = {'spam': 'eggs'}
    request.config.cache.set('my_data', data)

通过内置request fixture:

访问测试中的数据字典
def test_spam(request):
    data = request.config.cache.get('my_data')
    assert data['spam'] == 'eggs'

在测试运行之间共享数据

关于request.cache的一个很酷的事情是它存在于磁盘上,所以它甚至可以在测试运行之间共享。当你运行分布式测试(pytest-xdist)或者有一些长时间运行的数据生成时,这很方便,这些数据一旦生成就不会改变:

@pytest.fixture(autouse=True)
def generate_data(request):
    data = request.config.cache.get('my_data', None)
    if data is None:
        data = long_running_generation_function()
        request.config.cache.set('my_data', data)

现在测试不需要重新计算不同测试运行的值,除非您明确清除磁盘上的缓存。看看目前在缓存中的内容:

$ pytest --cache-show
...
my_data contains:
  {'spam': 'eggs'}

使用--cache-clear标志重新运行测试以删除缓存并强制重新计算数据。或者只删除项目根目录中的.pytest_cache目录。

从这里开始

pytest文档中的相关部分:Cache: working with cross-testrun state

答案 4 :(得分:1)

您可以在pytest_addoption挂钩中添加全局变量作为选项。 如果您希望在不检查命令行的情况下确定属性,可以使用addoption或使用set_defaults方法明确执行此操作docs

当定义选项时,您可以将其粘贴到request.config.getoption的任何夹具中,然后将其显式传递给测试或使用autouse传递给测试。 或者,您可以将选项传递到config对象内的几乎任何钩子。

#conftest.py
def pytest_addoption(parser):    
    parser.addoption("--my_global_var", default="foo")
    parser.set_defaults(my_hidden_var="bar")

@pytest.fixture()
def my_hidden_var(request):
    return request.config.getoption("my_hidden_var")

#test.py
def test_my_hidden_var(my_hidden_var):
    assert my_hidden_var == "bar"