zoukankan      html  css  js  c++  java
  • 实战Django:官方实例Part5

    俗话说,人非圣贤,孰能无过。在堆代码的过程中,即便是老攻城狮,也会写下一些错误的内容。俗话又说,过而能改,善莫大焉。要改,首先要知道哪里存在错误,这便是我们要对投票应用进行测试的原因。

    21.撰写第一个测试


    在我们这个项目中,还真有一个bug存在。这个bug位于Question.was_published_recently() 方法中。当Question提交的日期是正确的,那没问题,但若提交的日期是错误的——比如日期是几天之后,问题就来了。

    你可以在管理页面中增加一个投票,把日期设置在几天之后,你会发现你刚增加的投票被程序认为是“最近发布”的。

    我们可以编写一段测试程序来界定问题。

    编辑polls/tests.py 文件,添加下面的内容:

    polls/tests.py

    import datetime
    
    from django.utils import timezone
    from django.test import TestCase
    
    from polls.models import Question
    
    class QuestionMethodTests(TestCase):
    
        def test_was_published_recently_with_future_question(self):
            """
            如果question的发布日期是在将来,那么was_published_recently()应该
            返回一个False值。
            """
            time = timezone.now() + datetime.timedelta(days=30)
            future_question = Question(pub_date=time)
            self.assertEqual(future_question.was_published_recently(), False)

    我们来运行一下测试,在Dos命令提示符下(注意,检查一下是否位于项目文件夹mysite下,,就象我们在Part1中所做的那样),输入:

    python manage.py test polls

    你会看到象这样的运行结果:

    Creating test database for alias 'default'...
    F
    ======================================================================
    FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTests)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
        self.assertEqual(future_question.was_published_recently(), False)
    AssertionError: True != False
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    FAILED (failures=1)
    Destroying test database for alias 'default'...

    我们来看一下整个运行过程:

    • 执行python manage.py test polls后,程序会自动检索polls应用下面的tests.py文档。
    • 然后它会发现我们的测试类:QuestionMethodTests
    • 程序会根据测试类创建一个临时数据库;
    • 在test_was_published_recently_with_future_question中,程序会创建一个Question实例,它的发布日期在30天之后;
    • 最后它使用了assertEqual() 方法,它发现was_published_recently() 返回的值是True,而实际上我们希望它返回的是False。

    所以我们看到最终的结果是FAILED,说明我们的程序存在问题。

    22.修复Bug


    既然找到了问题所在,我们来修复它。

    编辑polls/models.py 文件,作如下改动:

    polls/models.py

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

    然后再运行一次测试,可看到如下结果:

    Creating test database for alias 'default'...
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK
    Destroying test database for alias 'default'...

    这回正常了。

    23.更多综合性的测试

    有时项目中不止一个bug,下面,我们再编写两个测试。

    编辑polls/tests.py 文件,在QuestionMethodTests类下添加下面的内容:

    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)

    现在,我们一共有三个测试来确认Question.was_published_recently() 在过去、最近、将来三个时间点上创建问题时返回正确的值。

    当然。投票应用还只是一个非常简单的例子,相应的bug也不会很多。但在以后我们开发项目的过程中,我们会碰到一些复杂的应用,这时,测试就变得更加重要了。

    24.测试视图

    前面我们只是对应用的内部业务逻辑进行测试,接下来,我们要模拟用户操作,来测试我们的视图。

    在Part4中,我们的Index视图使用了Django通用视图中的ListView,它的内容是这样的:

    polls/views.py

    class IndexView(generic.ListView):
        template_name = 'polls/index.html'
        context_object_name = 'latest_question_list'
    
        def get_queryset(self):
            """Return the last five published questions."""
            return Question.objects.order_by('-pub_date')[:5]

    我们需要修正get_queryset这个方法,让它在提取数据时检查发布时间,与当前时间进行比对。我们编辑polls/views.py,在文件头部先加入:

    polls/views.py:

    from django.utils import timezone

    然后修正get_queryset方法 :

    polls/views.py:

    def get_queryset(self):
        """
        Return the last five published questions (not including those set to be
        published in the future).
        """
        return Question.objects.filter(
            pub_date__lte=timezone.now()
        ).order_by('-pub_date')[:5]

    Question.objects.filter(pub_date__lte=timezone.now())可确保返回的结果集中的Question对象的发布日期早于或等于当前的时间。

    现在我们来测试一下这个新的视图。

    编辑polls/tests.py,先在文件头部加入:

    polls/tests.py:

    from django.core.urlresolvers import reverse

    然后再加上以下内容:

    polls/tests.py:
    def create_question(question_text, days):
        """
        Creates a question with the given `question_text` published 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 exist, 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):
            """
            Questions 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 questions 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.>']
            )

    我们来看一下这部分代码:

    • 首先我们使用了一个叫create_question的函数,它是用来快速创建问题的,因为随后的测试中都会用到,所以有这个函数,可减少一些重复劳动。
    • test_index_view_with_no_questions不创建任何问题,只是检查当没有任何投票问题的时候,首页是否能返回“No polls are available.”这个信息,同时检查latest_question_list是不是空的。
    • 在test_index_view_with_a_past_question中,我们创建了一个问题,把它的发布时间设在了30天前,然后检查它是不是出现在首页的列表中;
    • 在test_index_view_with_a_future_question中,我们创建了一个问题,把它的发布时间设在了30天后,这里的每一个测试方法在执行的时候,数据库都会重置。所以我们在上一步测试中创建的那个问题不再存在,这样,首页的列表就应该是空的;

    即使我们在首页视图中不再显示那些发布在将来时段的问题,但还会有用户通过合适的链接来访问到这些内容。这就意味着,我们要调整内容页的视图。

    编辑polls/views.py,在DetailView中加入get_queryset方法:

    polls/views.py:

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

    我们同样要编写一些测试来检查这个视图是否起作用了。

    编辑polls/tests.py,加入下列内容:

    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)

    我们来简单分析一下这段测试:

    • 在test_detail_view_with_a_future_question中,我们创建了一个问题,把它的发布时间设置在5天后,然后模拟用户去访问,如果我们新的DetailView起作用的话,这个链接应该是空的,换句话说,访问这个链接时,用户会得到一个404的状态码。
    • 在test_detail_view_with_a_past_question中,我们创建了一个问题,把它的发布时间设置在5天前,同样模拟用户去访问,这种情况下,用户会得到的状态码应该是200,也就是说,链接是有效的。

    我们还可以就更多的问题进行测试,同时根据测试优化我们的应用。

    举个例子,用户在使用这个应用的过程中,发布投票时没有带任何的投票项,此时,我们的视图需要具备相应的检测功能,来防止这类事情的发生。我们可以编写一个测试,创建一个问题,让它不再任何投票项,通过这样的测试来界定问题,并根据测试结果对视图进行调整。

    在测试中,我们奉行一个理念:测试越多越好。测试越多,说明我们的应用越可靠。或许有一天,我们的测试代码的数量甚至超过了正式代码,不必在意这些。测试只会让我们的代码愈来愈成熟。

    【未完待续】

    本文版权归舍得学苑所有,欢迎转载,转载请注明作者和出处。谢谢!
    作者:舍得
    首发:舍得学苑@博客园

  • 相关阅读:
    LeetCode 227. Basic Calculator II
    LeetCode 224. Basic Calculator
    LeetCode 103. Binary Tree Zigzag Level Order Traversal
    LeetCode 102. Binary Tree Level Order Traversal
    LeetCode 106. Construct Binary Tree from Inorder and Postorder Traversal
    LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal
    LeetCode 169. Majority Element
    LeetCode 145. Binary Tree Postorder Traversal
    LeetCode 94. Binary Tree Inorder Traversal
    LeetCode 144. Binary Tree Preorder Traversal
  • 原文地址:https://www.cnblogs.com/emagic/p/4149487.html
Copyright © 2011-2022 走看看