1. 程式人生 > >Django 中的 csrf_token 與單元測試報錯處理

Django 中的 csrf_token 與單元測試報錯處理

在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)

雖然並不建議大家使用這種方法,但在特殊情況下也可以參考一下。