Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/327.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python pytest monkeypatch装饰程序(不使用模拟/修补程序)_Python_Pytest_Monkeypatching - Fatal编程技术网

Python pytest monkeypatch装饰程序(不使用模拟/修补程序)

Python pytest monkeypatch装饰程序(不使用模拟/修补程序),python,pytest,monkeypatching,Python,Pytest,Monkeypatching,我正在使用带有monkeypatch夹具的pytest编写一些测试。按照这些规则,我从使用它们的模块中导入要模拟的类和方法,而不是从源代码中导入 我为之编写测试的应用程序是一个使用标准环境的googleappengine应用程序。因此,我必须使用Python2.7,我使用的实际版本是2.7.15-pytest版本是3.5.0 到目前为止,一切都很顺利,但我在尝试模拟装饰器函数时遇到了一个问题 从顶部开始。在一个名为decorators.py的py文件中,包含所有auth decorators,包

我正在使用带有monkeypatch夹具的pytest编写一些测试。按照这些规则,我从使用它们的模块中导入要模拟的类和方法,而不是从源代码中导入

我为之编写测试的应用程序是一个使用标准环境的googleappengine应用程序。因此,我必须使用Python2.7,我使用的实际版本是2.7.15-pytest版本是3.5.0

到目前为止,一切都很顺利,但我在尝试模拟装饰器函数时遇到了一个问题

从顶部开始。在一个名为decorators.py的py文件中,包含所有auth decorators,包括我想要模拟的decorator。所讨论的装饰器是一个模块函数,而不是类的一部分

def user_login_required(handler):
    def is_authenticated(self, *args, **kwargs):
        u = self.auth.get_user_by_session()
        if u.access == '' or u.access is None:
            # return the response
            self.redirect('/admin', permanent=True)
        else:
            return handler(self, *args, **kwargs)
    return is_authenticated
装饰器应用于web请求函数。名为handlers(handlers.UserDetails)文件夹中名为UserDetails.py的文件中的一个基本示例

在测试模块中,我设置测试如下:

from handlers.UserDetails import user_login_required

@pytest.mark.parametrize('params', get_params, ids=get_ids)
def test_post(self, params, monkeypatch):

    monkeypatch.setattr(user_login_required, mock_user_login_required_func)
import decorators

class UserDetailsHandler(BaseHandler):
    @decorators.user_login_required
    def get(self):
        # Do web stuff, return html, etc
问题是monkeypatch不允许我将单个函数作为目标。它希望目标是一个类,后跟要替换的方法名,然后是mock方法

monkeypatch.setattr(WouldBeClass, "user_login_required", mock_user_login_required_func)
我已尝试调整代码,看看是否可以通过更改decorator的导入和使用方式来绕过它,如下所示:

from handlers.UserDetails import user_login_required

@pytest.mark.parametrize('params', get_params, ids=get_ids)
def test_post(self, params, monkeypatch):

    monkeypatch.setattr(user_login_required, mock_user_login_required_func)
import decorators

class UserDetailsHandler(BaseHandler):
    @decorators.user_login_required
    def get(self):
        # Do web stuff, return html, etc
然后在测试中,我尝试像这样修补函数名

from handlers.UserDetails import decorators

@pytest.mark.parametrize('params', get_params, ids=get_ids)
def test_post(self, params, monkeypatch):

    monkeypatch.setattr(decorators, "user_login_required" , mock_user_login_required_func)
虽然这段代码没有抛出任何错误,但当我逐步完成测试时,代码从未进入mock\u user\u login\u required\u func。它总是进入现场装饰


我做错了什么?这是尝试使用monkeypatch decorators的一个问题,或者模块中的单个函数是否可以不进行修补?

这里的快速解决方法似乎就是移动处理程序导入,以便在修补程序之后进行导入。decorator和修饰函数必须位于单独的模块中,以便python在修补之前不会执行decorator

from decorators import user_login_required

@pytest.mark.parametrize('params', get_params, ids=get_ids)
def test_post(self, params, monkeypatch):

    monkeypatch.setattr(decorators, "user_login_required" , mock_user_login_required_func)
    from handlers.UserDetails import UserDetailsHandler

使用内置的unittest.mock模块中的patch函数,您可能会发现这更容易实现

由于这里提到的导入/修改gotchas,我决定避免对这个特定的装饰器使用模拟

目前,我已经创建了一个装置来设置环境变量:

@pytest.fixture()
def enable_fake_auth():
    """ Sets the "enable_fake_auth"  then deletes after use"""
    import os
    os.environ["enable_fake_auth"] = "true"
    yield
    del os.environ["enable_fake_auth"]
然后在decorator中,我修改了is_authenticated方法:

def is_authenticated(self, *args, **kwargs):
    import os
    env = os.getenv('enable_fake_auth')
    if env:
        return handler(self, *args, **kwargs)
    else:
        # get user from session
        u = self.auth.get_user_by_session()
        if u:
            access = u.get("access", None)
            if access == '' or access is None:
                # return the response
                self.redirect('/admin', permanent=True)
            else:
                return handler(self, *args, **kwargs)
        else:
            self.redirect('/admin?returnPath=' + self.request.path, permanent=True)

return is_authenticated
它没有回答我最初提出的问题,但我已经把我的解决方案放在这里,以防它能帮助其他人。正如hoefling所指出的,像这样修改生产代码通常是一个坏主意,所以使用它的风险自负


在此之前,我的原始解决方案没有修改或模拟任何代码。它包括创建一个假的安全cookie,然后在测试请求的头中发送它。这将使对self.auth.get\u user\u by\u session()的调用返回具有访问集的有效对象。我可能会回到这一点。

我遇到了一个类似的问题,并通过在一个装置中使用补丁来修补装饰者遵从的代码,从而解决了这个问题。为了提供一些上下文,我有一个Django项目的视图,该项目在视图函数上使用了一个decorator来强制验证。有点像:

# myproject/myview.py

@user_authenticated("some_arg")
def my_view():
    ... normal view code ...
import pytest
from unittest.mock import patch

@pytest.fixture
def mock_auth():
    patcher = patch("myproject.auth")
    mock_auth = patcher.start()
    mock_auth.actual_auth_logic.return_value = ... a simulated "user is logged in" value
    yield
    patcher.stop()
user\u authenticated
的代码存在于单独的文件中:

# myproject/auth.py

def user_authenticated(argument):
    ... code for the decorator at some point had a call to:
    actual_auth_logic()
    
    
def actual_auth_logic():
    ... the actual logic around validating auth ...
为了测试,我写了如下内容:

# myproject/myview.py

@user_authenticated("some_arg")
def my_view():
    ... normal view code ...
import pytest
from unittest.mock import patch

@pytest.fixture
def mock_auth():
    patcher = patch("myproject.auth")
    mock_auth = patcher.start()
    mock_auth.actual_auth_logic.return_value = ... a simulated "user is logged in" value
    yield
    patcher.stop()
然后,任何想要有效跳过身份验证的视图测试(即假设用户已登录)都可以使用该装置:

def test_view(client, mock_auth):
    response = client.get('/some/request/path/to/my/view')

    assert response.content == "what I expect in the response content when user is logged in"
当我想测试未经身份验证的用户看不到经过身份验证的内容时,我只是省略了身份验证装置:

def test_view_when_user_is_unauthenticated(client):
    response = client.get('/some/request/path/to/my/view')

    assert response.content == "content when user is not logged in"

它有点脆弱,因为现在视图的测试与auth机制的内部联系在一起(即,如果
actual\u auth\u logic
方法被重命名/重构,这将是一个糟糕的时期),但至少它只与夹具隔离开来。

修补装饰器的典型问题是,一旦模块被导入,decorator将运行、修改函数,并且不再为该函数运行。事后修补无效。解释清楚,谢谢克劳斯。看起来我需要直接在decorator中执行一些操作,以检测是否正在运行测试。我有一个想法…将进一步调查。可能的副本提醒未来的读者:让生产代码在测试模式下表现不同是一个坏主意,也是一条非常危险的道路。请永远不要这样做。将此标记为可接受的答案,因为它解决了手头的问题。