Writing your first Django app, part 5--学习笔记

难道是又堕落了吗?一个Django的入门教程,看了这么久都没看完,罪过,罪过。努力克服自身的局限,跳出自己的视角看世界。加油!

一:写你的第一个测试脚本

我们可以看到新建一个Question对象,他的pub_date设置为一个月后,调用was_published_recently()却给出True,这明显是错误的。

In [11]: from django.utils import timezone
In [12]: from polls.models import Question
In [13]: future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
In [14]: future_question.was_published_recently()
Out[14]: True

创建一个脚本来暴露出这个bug,cat polls/tests.py:

import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question

class QuestionMethodTests(TestCase):
    def test_was_published_recently_with_future_question(self):
        """
        was_pulished_recently() should return False for questions whose
        pub_date is in the future.
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertEqual(future_question.was_published_recently(),False)

开始测试python manage.py test polls,提示的很明显future_question.was_published_recently()返回的是True,而我们想返回的是False。

Creating test database for alias 'default'...
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/budong/Downloads/code_test/poll_mysite/polls/tests.py", line 14, in test_was_published_recently_with_future_question
    self.assertEqual(future_question.was_published_recently(),False)
AssertionError: True != False

----------------------------------------------------------------------
Ran 1 test in 0.022s

FAILED (failures=1)
Destroying test database for alias 'default'...

修正这个bug,polls/models.py。

def was_published_recently(self):
    now = timezone.now()
    return now - datetime.timedelta(days=1) <= self.pub_date <= now

再一次的测试,python manage.py test polls,问题解决。

Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.031s

OK
Destroying test database for alias 'default'...

更全面的测试,发表时间在一天前的应该返回False,发表时间在一天内的应该返回True,polls/tests.py。

def test_was_published_recently_with_old_question(self):
    """
    was_published_recently() should return False for questions whose
    pub_date is older than 1 day.
    """
    time = timezone.now() - datetime.timedelta(days=30)
    old_question = Question(pub_date=time)
    self.assertEqual(old_question.was_published_recently(), False)

def test_was_published_recently_with_recent_question(self):
    """
    was_published_recently() should return True for questions whose
    pub_date is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=1)
    recent_question = Question(pub_date=time)
    self.assertEqual(recent_question.was_published_recently(), True)

二:测试一个视图函数

Django的测试客户端,挺有意思的东西,模拟用户浏览器测试。

In [1]: from django.test.utils import setup_test_environment
In [2]: setup_test_environment()
In [3]: from django.test import Client
In [4]: client = Client()
In [5]: response = client.get('/')
In [6]: response.status_code
Out[6]: 404
In [7]: from django.core.urlresolvers import reverse
In [8]: response = client.ge
client.generic  client.get
In [8]: response = client.get(reverse('polls:index'))
In [9]: response.status_code
Out[9]: 200
In [10]: response.content
Out[10]: '\n<ul>\n    \n    <li><a href="/polls/3/">who are you?</li>\n    \n    <li><a href="/polls/2/">where?</li>\n    \n    <li><a href="/polls/1/">What&#39;s up?</li>\n    \n    <li><a href="/polls/8/">d</li>\n    \n    <li><a href="/polls/7/">b</li>\n    \n</ul>\n\n'
In [11]: from polls.models import Question
In [12]: from django.utils import timezone
In [14]: q = Question(question_text="Who is your favorite Beatle?",pub_date=time.now())
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-14-8d84e8b32f83> in <module>()
----> 1 q = Question(question_text="Who is your favorite Beatle?",pub_date=time.now())

NameError: name 'time' is not defined
In [15]: q = Question(question_text="Who is your favorite Beatle?",pub_date=timezone.now())
In [16]: q.save()
In [17]: response = client.get('/polls/')
In [18]: response.content
Out[18]: '\n<ul>\n    \n    <li><a href="/polls/10/">Who is your favorite Beatle?</li>\n    \n    <li><a href="/polls/3/">who are you?</li>\n    \n    <li><a href="/polls/2/">where?</li>\n    \n    <li><a href="/polls/1/">What&#39;s up?</li>\n    \n    <li><a href="/polls/8/">d</li>\n    \n</ul>\n\n'
In [19]: response.context['latest_question_list']
Out[19]: [<Question: Who is your favorite Beatle?>, <Question: who are you?>, <Question: where?>, <Question: What's up?>, <Question: d>]

改进我们的视图函数,views.py

from django.utils import timezone
class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'
    def get_queryset(self):
        #return Question.objects.order_by("-pub_date")[:5]
        return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]

测试我们新的视图函数

def create_question(question_text,days):
    """
    Create a question with the given `question_text` publishd the given number of `days` offset to now (negative for questions published in the past,positive for questions that have yet to be published).
    """
    time = timezone.now() + datetime.timedelta(days=days)
    return Question.objects.create(question_text=question_text,pub_date=time)

class QuestionViewTests(TestCase):
    def test_index_view_with_no_questions(self):
        """
        If no questions exists,an appropriate message should be displayed.
        """
        response = self.client.get(reverse('polls:index'))
        self.assertEqual(response.status_code,200)
        self.assertContains(response,"No polls are available.")
        self.assertQuerysetEqual(response.context['latest_question_list'],[])

    def test_index_view_with_a_past_question(self):
        """
        Question with a pub_date in the past should be displayed on the index page.
        """
        create_question(question_text="Past question.",days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question.>'])

    def test_index_view_with_a_future_question(self):
        """
        Questions with a pub_date in the future should not be displayed on the index page.
        """
        create_question(question_text="Future question.",days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertContains(response,"No polls are available.",status_code=200)
        self.assertQuerysetEqual(response.context['latest_question_list'],[])

    def test_index_view_with_future_question_and_past_question(self):
        """
        Even if both past and future questions exist,only past questions should be displayed.
        """
        create_question(question_text="Past question",days=-30)
        create_question(question_text="Future question",days=30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question>'])

    def test_index_view_with_two_past_questions(self):
        """
        The quetions index page may display multiple questions
        """
        create_question(question_text="Past question 1.",days=-30)
        create_question(question_text="Past question 2.",days=-5)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(response.context['latest_question_list'],['<Question: Past question 2.>','<Question: Past question 1.>'])

测试DetailView,尽管在IndexView中pub_date为未来时间的Question已经没了,但是在DetailView中,还是有的,写测试,修bug

polls/views.py

class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'
    def get_queryset(self):
        """
        Excludes any questions that aren't published yet.
        """
        return Question.objects.filter(pub_date__lte=timezone.now())

polls/tests.py

class QuestionIndexDetailTests(TestCase):
    def test_detail_view_with_a_future_question(self):
        """
        The detail view of a question with a pub_date in the future should return a 404 not found.
        """
        future_question = create_question(question_text='Future question.',days=5)
        response = self.client.get(reverse('polls:detail',args=(future_question.id,)))
        self.assertEqual(response.status_code,404)

    def test_detail_view_with_a_past_question(self):
        """
        The detail view of a question with a pub_date in the past should display the question's text.
        """
        past_question = create_question(question_text='Past Question.',days=-5)
        response = self.client.get(reverse('polls:detail',args=(past_question.id,)))
        self.assertContains(response,past_question.question_text,status_code=200)

更多测试请看官方文档。


参考资料:

Writing your first Django app, part 5:https://docs.djangoproject.com/en/1.8/intro/tutorial05/