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,也就是说,链接是有效的。

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

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

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

    【未完待续】

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

  • 相关阅读:
    HCIA-IoT 华为认证物联网工程师
    [书目20210522]投资最重要的事
    [书目20210414]海龟交易法则
    [书目20210224]陆蓉 行为金融学讲义
    [书目20210207]肖星的财务思维课
    [转]昂克英文君 一张图告诉你英语该怎么学
    Cloud Native
    Aerospike-介绍
    Groovy使用场景
    javaStream与响应式流
  • 原文地址:https://www.cnblogs.com/emagic/p/4149487.html
Copyright © 2011-2022 走看看