仅对 Pytest 修补函数的第一次调用产生副作用

问题描述 投票:0回答:1

我有一个渲染 HTML 模板的函数。如果在渲染给定模板期间抛出异常,将渲染默认模板(下面函数的简化版本):

def main(path_to_template):
    try:
        return render_template(path_to_template)
    except Exception:
        return render_template('default.html')

我想测试异常块。 在我的测试函数中,我想修补

render_template
,以便它具有
Exception
副作用。 然而,这必须仅限于第一个调用,因为我希望第二个调用返回
render_template
正在执行的任何操作。

是否可以创建这样的测试?

我尝试了以下解决方案,但没有任何效果:

def test_main():
    mocker.patch("path.to.render_template", side_effect=Exception("mock exception"))
    main("some_path")

上面是第一种方法,它会在两次调用中引发

Exception
,因此不测试我想测试的内容。

def test_main():
    mocker.patch("path.to.render_template", side_effect=[Exception("mock exception"), None])
    main("some_path")

上面的方法也不起作用,因为

render_template
的返回不应该是
None

def custom_side_effect(*args):
    if args[0] == "some_path":
        raise Exception("mock exception")
    else:
        return render_template(args[0])

def test_main():
    mocker.patch("path.to.render_template", side_effect=custom_side_effect)
    main("some_path")

最后一个导致抛出循环异常,我必须终止测试运行。

这个问题似乎是一个标准问题,有一个简单的解决方案。 有没有人遇到过类似的问题并可以建议解决方法?

编辑1: 原始测试是使用 Selenium 的功能测试,我想测试服务器错误的场景,该场景应导致显示 HTTP 500 错误页面。 下面是被测试的原始函数:

@login_required(login_url="/login/")
def pages(request):
    context = {}
    try:
        # remove trailing slash to determine a template
        path = request.path.rstrip("/")
        load_template = path.split("/")[-1]
        if load_template == "admin":
            return HttpResponseRedirect(reverse("admin:index"))
        context["segment"] = load_template
        html_template = loader.get_template("home/" + load_template)
    except template.TemplateDoesNotExist as e:
        html_template = loader.get_template("home/page-404.html")
    except Exception as e:
        html_template = loader.get_template("home/page-500.html")
    return HttpResponse(html_template.render(context, request))

编辑2: 作为临时解决方案,我决定进行小型重构。

def load_home_page(path_to_template):
    return loader.get_template(path_to_template)


def load_404_page():
    return loader.get_template("home/page-404.html")


def load_500_page():
    return loader.get_template("home/page-500.html")

然后我只修补

load_home_page
功能。

我认为这不是最好的解决方案,所以很高兴看到更干净的东西。

python unit-testing mocking pytest monkeypatching
1个回答
0
投票

在答案中我不使用

pytest
,而只使用标准包
unittest
。我希望这对您来说不是问题。在答案中,您可以找到
side_effect
的定义,如果您愿意,可以将我的代码调整为
pytest

我在我的系统中创建了 3 个文件。第一个是文件

main.py
如下:

from other_module import render_template

def main(path_to_template):
    try:
        return render_template(path_to_template)
    except Exception:
        return render_template('default.html')

它完全包含您的功能

main()
。在这个文件中,我插入了指令
from other_module import render_template

要在我的系统中执行代码,我必须创建一个名为
other_module.py
的模块,并在其中定义一个函数
render_template()
,如下所示:

# content of the file 'other_module.py'

def render_template(path_to_template):
    print(f"Called real render_template with arg = '{path_to_template}'")
    return path_to_template

该函数每次执行时都会打印一条消息并返回传递给它的参数。

第三个(最重要的)文件是

test_main.py
,定义如下:

import unittest
from unittest import mock

from other_module import render_template
from main import main

def custom_side_effect(path_to_template):
    print(f"Called custom_side_effect with arg '{path_to_template}'")
    return render_template(path_to_template)

class TestRenderTemplate(unittest.TestCase):
    def test_main(self):
        other_path = "other_path"
        with mock.patch('main.render_template') as mock_render_template:
            mock_render_template.side_effect = [Exception, custom_side_effect('default.html'), custom_side_effect(other_path)]
            # the call of main() causes the call of render_tamplate() for 2 times (the second time it is called in the 'except' block)
            self.assertEqual('default.html', main("some_path"))
            # the second call to main() causes the third called to render_template()
            self.assertEqual(other_path, main(other_path))

if __name__ == '__main__':
    unittest.main()

测试的执行会产生以下输出:

Called custom_side_effect with arg 'default.html'
Called real render_template with arg = 'default.html'
Called custom_side_effect with arg 'other_path'
Called real render_template with arg = 'other_path'

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

输出显示:

  • 第一次由
    render_template()
    调用
    main()
    时,会引发由 side_effect 的定义引起的异常:
mock_render_template.side_effect = [Exception, custom_side_effect('default.html'), custom_side_effect(other_path)]
  • render_template()
    custom_side_effect()
    调用 2 次;这可以从以下事实看出:消息
    Called real render_template with arg ...
    紧随消息
    Called custom_side_effect with arg
© www.soinside.com 2019 - 2024. All rights reserved.