Python 如何为py.test中的所有测试跨模块共享变量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22768976/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 01:37:54  来源:igfitidea点击:

how to share a variable across modules for all tests in py.test

pythonunit-testingtestingglobal-variablespytest

提问by Trevor

I have multiple tests run by py.test that are located in multiple classes in multiple files.

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

What is the simplest way to share a large dictionary - which I do not want to duplicate - with every method of every class in every file to be used by py.test?

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

In short, I need to make a "global variable" for every test. Outside of py.test, I have no use for this variable, so I don't want to store it in the files being tested. I made frequent use of py.test's fixtures, but this seems overkill for this need. Maybe it's the only way?

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

回答by Frank T

Having a big dictionary of globals that every test uses is probably a bad idea. If possible, I suggest refactoring your tests to avoid this sort of thing.

拥有一个每个测试都使用的大全局变量字典可能是一个坏主意。如果可能,我建议重构您的测试以避免此类事情。

That said, here is how I would do it: define an autouse fixturethat adds a reference to the dictionary in the global namespace of every function.

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

Here is some code. It's all in the same file, but you can move the fixture out to conftest.pyat the top level of your tests.

这是一些代码。它们都在同一个文件中,但您可以将夹具移到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'

Here is the output from when I run this code:

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

$ 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 ===============================

Note that you can't use a session-scoped fixture because then you don't have access to each function object. Because of this, I'm making sure to define my big global dictionary once and use references to it -- if I defined the dictionary in that assignment statement, a new copy would be made each time.

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

In closing, doing anything like this is probably a bad idea. Good luck though :)

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

回答by flub

Update: pytest-namespace hook is deprecated/removed. Do not use.See #3735for details.

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

You mention the obvious and least magical option: using a fixture. You can apply it to entire modules using pytestmark = pytest.mark.usefixtures('big_dict')in your module, but then it won't be in your namespace so explicitly requesting it might be best.

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

Alternatively you can assign things into the pytest namespace using the hook:

或者,您可以使用钩子将内容分配到 pytest 命名空间中:

# conftest.py

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

And now you have pytest.my_big_dict. The fixture is probably still nicer though.

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

回答by user2859458

There are tons of things I love about py.test, but one thing I absolutely HATE is how poorly it plays with code intelligence tools. I disagree that an autouse fixture to declarea variable is the "most clear" method in this case because not only does it completely baffle my linter, but also anyone else who is not familiar with how py.test works. There is a lot of magic there, imo.

py.test 有很多我喜欢的地方,但我绝对讨厌的一件事是它与代码智能工具的配合太差。我不同意在这种情况下使用 autouse 固定装置来声明变量是“最清晰”的方法,因为它不仅完全阻碍了我的 linter,而且还阻碍了其他任何不熟悉 py.test 工作原理的人。那里有很多魔法,imo。

So, one thing you can do that doesn't make your linter explode and doesn't require TestCase boilerplate is to create a module called globals. Inside this module, stub the names of the things you want global to {} or None and import the global module into your tests. Then in your conftest.py file, use the py.test hooks to set (or reset) your global variable(s) as appropriate. This has the advantage of giving you the stub to work with when building tests and the full data for the tests at runtime.

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

For example, you can use the pytest_configure()hook to set your dict right when py.test starts up. Or, if you wanted to make sure the data was pristine between each test, you could autouse a fixture to assign your global variable to your known state before each 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

One other advantage to this approach is you can use type hinting in your globals module to give you code completion on the global objects in your test, which probably isn't necessary for a dict but I find it handy when I am using an object (such as webdriver). :)

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

回答by Kirill

You can add your global variable as an option inside the pytest_addoptionhook. It is possible to do it explicitly with addoptionor use set_defaultsmethod if you want your attribute be determined without any inspection of the command line, docs

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



When option was defined, you can paste it inside any fixture with request.config.getoptionand then pass it to the test explicitly or with autouse. Alternatively, you can pass your option into almost any hook inside the configobject.

定义选项后,您可以将其粘贴到任何夹具中request.config.getoption,然后显式或使用自动将其传递给测试。或者,您可以将选项传递到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"

回答by hoefling

I'm suprised no answer mentioned caching yet: since version 2.8, pytesthas a powerful cache mechanism.

我很惊讶还没有提到缓存的答案:从 2.8 版开始,pytest有一个强大的缓存机制。

Usage example

使用示例

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

Access the data dict in tests via builtin requestfixture:

通过内置request夹具访问测试中的数据字典:

def test_spam(request):
    data = request.config.cache.get('my_data')
    assert data['spam'] == 'eggs'

Sharing the data between test runs

在测试运行之间共享数据

The cool thing about request.cacheis that it is persisted on disk, so it can be even shared between test runs. This comes handy when you running tests distributed (pytest-xdist) or have some long-running data generation which does not change once generated:

很酷的事情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)

Now the tests won't need to recalculate the value on different test runs unless you clear the cache on disk explicitly. Take a look what's currently in the cache:

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

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

Rerun the tests with the --cache-clearflag to delete the cache and force the data to be recalculated. Or just remove the .pytest_cachedirectory in the project root dir.

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

Where to go from here

从这往哪儿走

The related section in pytestdocs: Cache: working with cross-testrun state.

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