在Harry J.W. Percival 所著的<Python Web开发:测试驱动方法>中的第五章,在单元测试部分存在一个bug。
即,在高版本的Django(>1.7)中,在渲染模板时,Django 会把这个模板标签替换成一个<input type="hidden">
元素,其值是CSRF 令牌。
from django.test import TestCase
from django.core.urlresolvers import resolve
from django.http import HttpRequest
from django.template.loader import render_to_string
from .views import home_page
class HomePageTest(TestCase):
def test_root_url_resolves_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func, home_page)
def test_home_page_returns_correct_html(self):
request = HttpRequest()
response = home_page(request)
expected_html = render_to_string('home.html')
self.assertEqual(response.content.decode(), expected_html)
所以在执行单元测试时,
通过视图函数
home_page()
渲染得到的响应包含csrf转换的
<input>
元素,而
render_to_string()
则未生成该部分,所以导致测试失败。
会产生如下错误消息。
$ python3 manage.py test lists
Creating test database for alias 'default'...
F.
======================================================================
FAIL: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/panzeyan/PycharmProjects/TDD/superlists/lists/tests.py", line 20, in test_home_page_returns_correct_html
self.assertEqual(response.content.decode(), expected_html)
AssertionError: '<htm[240 chars] <input type=\'hidden\' name=\'csrfmiddlew[184 chars]l>\n' != '<htm[240 chars] \n </form>\n\n <table id="i[87 chars]l>\n'
----------------------------------------------------------------------
Ran 2 tests in 0.256s
FAILED (failures=1)
Destroying test database for alias 'default'...
在博客http://www.cnblogs.com/panzeyan/p/5819373.html中,给出了1.8.x~1.9.x的解决方案,即在调用
render_to_string()
时的末尾添加
request=request
但这种解决方案没有办法在笔者的1.10.x版本正常使用。因为视图中产生的令牌值与调用render_to_string()返回的令牌值不同。
在经过多次查询后可知,这个问题暂时没有办法以正规途径解决,甚至原文作者也建议大家使用1.8.7版本,因为1.8.x是长期支持版本。
https://groups.google.com/forum/#!topic/obey-the-testing-goat-book/fwY7ifEWKMU
但为了不影响自己的使用并且不降级django的版本,笔者决定采用讨巧的方法,利用正则表达式删除相关的标签。代码如下:
csrf_regex = r'<input[^>]+csrfmiddlewaretoken[^>]+>'
print('expected_html\n',expected_html)
observed_html = re.sub(csrf_regex, '', response.content.decode())
expected_html = re.sub(csrf_regex, '', expected_html)
虽然并不建议大家使用这种方法,但在特殊情况下也可以参考一下。