使用 pytest 标记的预测试任务?

Posted

技术标签:

【中文标题】使用 pytest 标记的预测试任务?【英文标题】:Pre-test tasks using pytest markers? 【发布时间】:2021-04-02 16:51:31 【问题描述】:

我有一个使用 pytest 的 Python 应用程序。对于我的几个测试,有对 Elasticsearch 的 API 调用(使用 elasticsearch-dsl-py)会减慢我想要的测试:

    除非使用 Pytest 标记,否则应避免。 如果使用标记,我希望该标记在测试运行之前执行一些代码。就像使用 yield 时夹具的工作方式一样。

这主要是受到 pytest-django 的启发,您必须使用 django_db 标记才能连接到数据库(但如果您尝试连接到数据库,它们会抛出错误,而我只是不这样做'一开始就不想打电话,仅此而已)。

例如:

def test_unintentionally_using_es():
    """I don't want a call going to Elasticsearch. But they just happen. Is there a way to "mock" the call? Or even just prevent the call from happening?"""

@pytest.mark.elastic
def test_intentionally_using_es():
    """I would like for this marker to perform some tasks beforehand (i.e. clear the indices)"""

# To replicate that second test, I currently use a fixture:
@pytest.fixture
def elastic():
    # Pre-test tasks
    yield something

我认为这是标记的用例,对吧?主要受 pytest-django 启发。

【问题讨论】:

@pytest.mark.usefixtures("elastic")? 对于模拟弹性搜索,请查看例如elasticmock 感谢@hoefling 啊,我认为usefixtures 装饰器非常适合第二种情况,而不是标记。但是我如何在默认情况下阻止对 ES 的调用(如没有应用标记/夹具时)? 我想我现在明白你的意思了,很快就会添加答案。 【参考方案1】:

您最初使用夹具和自定义标记组合的方法是正确的;在下面的代码中,我从您的问题中提取了代码并填补了空白。

假设我们有一些使用官方elasticsearch 客户端的虚拟函数进行测试:

# lib.py

from datetime import datetime
from elasticsearch import Elasticsearch


def f():
    es = Elasticsearch()
    es.indices.create(index='my-index', ignore=400)
    return es.index(
        index="my-index",
        id=42,
        body="any": "data", "timestamp": datetime.now(),
    )

我们添加两个测试,一个没有标记elastic,应该在假客户端上操作,另一个标记,需要访问真实客户端:

# test_lib.py

from lib import f


def test_fake():
    resp = f()
    assert resp["_id"] == "42"


@pytest.mark.elastic
def test_real():
    resp = f()
    assert resp["_id"] == "42"

现在让我们编写 elastic() 夹具,它将根据是否设置了 elastic 标记来模拟 Elasticsearch 类:

from unittest.mock import MagicMock, patch
import pytest


@pytest.fixture(autouse=True)
def elastic(request):
    should_mock = request.node.get_closest_marker("elastic") is None
    if should_mock:
        patcher = patch('lib.Elasticsearch')
        fake_es = patcher.start()
        # this is just a mock example
        fake_es.return_value.index.return_value.__getitem__.return_value = "42"
    else:
        ...  # e.g. start the real server here etc
    yield
    if should_mock:
        patcher.stop()

注意autouse=True 的用法:fixture 将在每次测试调用时执行,但只有在测试标记时才进行修补。通过request.node.get_closest_marker("elastic") is None 检查标记的存在。如果您现在运行这两个测试,test_fake 将通过,因为elastic 模拟了Elasticsearch.index() 响应,而test_real 将失败,假设您没有在端口 9200 上运行服务器。

【讨论】:

对这个答案超级满意!!!我从来没有学会如何正确使用模拟和补丁。我认为这是一个足够好的答案,可以站稳脚跟,因此被接受。我想我唯一担心的是你在嘲笑一个特定的退货案例。我希望有一种方法来阻止调用,但我认为 Elasticsearch 模拟库可能会这样做。非常感谢! 是的,如果我错了,请纠正我——我想这更像是对自己的一个说明——但使用 Elasticmock 库可能意味着不使用他们的装饰器,而是使用_get_elasticmock 进行修补。 github.com/vrcmarcos/elasticmock/blob/… @acw 你也可以只做patcher = patch('lib.Elasticsearch', FakeElasticsearch); _get_elasticmock 只是一种缓存假客户端以供重用的方法。我同意elasticmock 的公共 API 虽然非常简洁,但可以提供更多模拟可能性。

以上是关于使用 pytest 标记的预测试任务?的主要内容,如果未能解决你的问题,请参考以下文章

标记一个 Pytest 夹具而不是使用该夹具的所有测试

pytest 不跳过未标记的测试

Pytest基础使用教程

pytest之标记mark

10pytest -- skip和xfail标记

pytest文档74-参数化parametrize加marks标记(pytest.param)