我有一个渲染 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
功能。
我认为这不是最好的解决方案,所以很高兴看到更干净的东西。
在答案中我不使用
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