Django 系列官方教程[5]测试

app已经建立完毕,现在我们开始测试

一、什么是测试:

什么是自动测试?


测试是检查代码运行情况的例程。


测试在不同的层面上进行。一些测试可能适用于一个微小的细节(特定的模型方法是否会像预期的那样返回值?)而其他人则检查软件的整体操作(站点上的一系列用户输入是否产生了预期的结果?)。这与您在教程2前面所做的测试没有什么不同,使用shell来检查方法的行为,或者运行应用程序并输入数据来检查其行为。


自动化测试的不同之处在于,测试工作是由系统为您完成的。您只需创建一组测试,然后在对应用程序进行更改时,就可以检查您的代码是否仍能按预期工作,而无需执行耗时的手动测试。

二、为什么需要测试

你可能会觉得,光是学习Python/Django就已经足够了,还有另一件事要学习和做,这可能会让你觉得力不从心,而且可能没有必要。毕竟,我们的民意调查应用程序现在运行得很好;经历创建自动化测试的麻烦不会让它工作得更好。如果创建polls应用程序是Django编程的最后一步,那么确实,您不需要知道如何创建自动测试。但是,如果不是这样的话,现在是学习的好时机。

测试将节省您的时间¨


在一定程度上,“检查它是否有效”将是一个令人满意的测试。在更复杂的应用程序中,组件之间可能有几十种复杂的交互。


这些组件中任何一个的更改都可能对应用程序的行为产生意想不到的后果。检查它是否仍然“似乎有效”可能意味着使用20种不同的测试数据来运行代码的功能,以确保没有破坏某些东西——这不是很好地利用时间。


当自动测试可以在几秒钟内完成这项工作时,这一点尤其正确。如果出现问题,测试还将帮助识别导致意外行为的代码。


有时,要从高效、创造性的编程工作中解脱出来,去面对枯燥乏味的测试工作,这似乎是一件烦人的事情,尤其是当你知道自己的代码工作正常时。


然而,编写测试的任务要比花几个小时手动测试应用程序或试图找出新引入的问题的原因更有成就感。

测试不仅能发现问题,还能防止问题的发生


把测试仅仅看作开发的一个消极方面是错误的。


如果没有测试,应用程序的目的或预期行为可能相当不透明。即使它是你自己的代码,你有时也会发现自己在里面摸索,试图找出它到底在做什么。


测试改变了这一点;它们从内部点亮你的代码,当出现问题时,它们会将灯光聚焦在出了问题的部分上——即使你甚至没有意识到它出了问题。

测试会让你的代码更有吸引力


您可能已经创建了一个出色的软件,但您会发现许多其他开发人员会拒绝查看它,因为它缺乏测试;没有测试,他们不会相信它。Jacob Kaplan Moss是Django最初的开发者之一,他说“没有测试的代码会被设计破坏。”


其他开发人员希望在认真对待软件之前看到软件中的测试,这是您开始编写测试的另一个原因。

测试有助于团队合作¨


以上几点是从单个开发人员维护应用程序的角度编写的。复杂的应用程序将由团队维护。测试保证同事不会无意中破坏您的代码(并且您不会在不知情的情况下破坏他们的代码)。如果你想以Django程序员为生,你必须擅长编写测试!

三、基本的测试策略

写作测试有很多方法。


一些程序员遵循一种叫做“测试驱动开发”的规则;他们实际上是在编写代码之前编写测试。这似乎有悖常理,但实际上与大多数人通常会做的事情类似:他们描述一个问题,然后创建一些代码来解决它。测试驱动的开发在Python测试用例中形式化了这个问题。


更多情况下,测试新手会创建一些代码,然后决定应该进行一些测试。也许更早地编写一些测试会更好,但现在开始永远都不晚。


有时很难找到从哪里开始写测试。如果您已经编写了数千行Python,那么选择要测试的内容可能并不容易。在这种情况下,下一次进行更改时编写第一个测试是富有成效的,无论是在添加新特性还是修复bug时。


我们现在就开始吧。

四、编写测试

发现bug

之前的投票小程序有个明显的bug,Question.was_published_recently()虽然返回的值是正确的,但如果发布日期放到将来了怎么办。

首先我们用shell来测试:

>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
TRue

现在我们来编写测试程序来查看问题在那

打开polls/tests.py,修改为:

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

from .models import Question


class QuestionModelTests(TestCase):

    def test_was_published_recently_with_future_question(self):
        """
        was_published_recently() returns False for questions whose pub_date
        is in the future.
        """
        time = timezone.now() + datetime.timedelta(days=30)
        future_question = Question(pub_date=time)
        self.assertIs(future_question.was_published_recently(), False)
# Create your tests here.

现在运行测试

python manage.py test polls

这里会发生几件事,

1. manage.py去找polls里的test

2.发现了里面的测试类

3.创建专门的数据库用于测试

4.寻找测试方法

5.测试后返回结果。

五、修复bug

将polls/models.py进行修改后重新进行测试。

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

六、更多复杂的测试

这里我们通过修改代码,修复了bug,但是引入了新的bug。。所以我们再设计一个测试

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

def test_was_published_recently_with_recent_question(self):
    """
    was_published_recently() returns True for questions whose pub_date
    is within the last day.
    """
    time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
    recent_question = Question(pub_date=time)
    self.assertIs(recent_question.was_published_recently(), True)

现在我们有三个测试了,而且不管未来这个程序和多少程序交互,我们能够保证这个函数不会出错。

七、视图的测试

Django提供了一个测试客户机来模拟用户在视图级别与代码交互。我们可以在测试中使用它。

在这里我们需要做一些测试中不需要的事情。打开shell并输入:

 python manage.py shell
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()

setup_test_environment()安装一个模板呈现程序,允许我们检查响应上的一些附加属性,例如响应。否则就无法获得的上下文。请注意,此方法不设置测试数据库,因此将针对现有数据库运行以下内容,并且输出可能会略有不同,具体取决于您已经创建的问题。如果你的时区在设置中,你可能会得到意想不到的结果。
接下来,我们需要导入测试类(这不是必需的,稍后在tests.py中,我们将使用django.test.TestCase类):

>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()

准备完毕,接下来开始让client做以下工作。

>>> response = client.get('/')
Not Found: /
>>> response.status_code
404
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    <ul>\n    \n        <li><a href="/polls/2/">\xe5\x86\x8d\xe6\x9d\xa5\xe4\xb8\x80\xe4\xb8\xaa</a></li>\n    \n        <li><a href="/polls/1/">\xe6\xad\xa4\xe5\x88\xbb\xe7\x9a\x84\xe6\x83\xb3\xe6\xb3\x95\xe6\x98\xaf\xef\xbc\x9f</a></li>\n    \n    </ul>\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: 再来一个>, <Question: 此刻的想法是?>]>

提升视图

我们还应当设定未来发布的问题当前是不可见的。打开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()方法并让他和timezone.now()比较,因此导入

from django.utils import timezone

并将get_queryset方法修改为:

def get_queryset(self):
    """
    返回最近五个问题,不包含未来的问题.
    """
    return Question.objects.filter(
        pub_date__lte=timezone.now()
    ).order_by('-pub_date')[:5]

八、测试视图

现在我们开始修改text.py,自然还是先导入库

from django.urls import reverse
def create_question(question_text, days):
    """
    Create a question with the given `question_text` and 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 QuestionIndexViewTests(TestCase):
    def test_no_questions(self):
        """
        If no questions exist, an appropriate message is 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_past_question(self):
        """
        Questions with a pub_date in the past are displayed on the
        index page.
        """
        question = create_question(question_text="Past question.", days=-30)
        response = self.client.get(reverse('polls:index'))
        self.assertQuerysetEqual(
            response.context['latest_question_list'],
            [question],
        )

    def test_future_question(self):
        """
        Questions with a pub_date in the future aren't 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.")
        self.assertQuerysetEqual(response.context['latest_question_list'], [])

    def test_future_question_and_past_question(self):
        """
        Even if both past and future questions exist, only past questions
        are displayed.
        """
        question = 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],
        )

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

让我们更仔细地看看其中的一些。


首先是一个问题快捷功能,create_question,,在创建问题的过程中减少一些重复。


test_no_questions不会创建任何问题,但会检查消息:“没有可用的民意调查。”并验证最新的问题列表是否为空。请注意,django。测验TestCase类提供了一些额外的断言方法。在这些示例中,我们使用assertContains()和assertQuerysetEqual()。


在test_past_question中,我们创建一个问题并验证它是否出现在列表中。


在test_future_question中,我们创建了一个带有未来发布日期的问题。每个测试方法都会重置数据库,因此第一个问题不再存在,因此索引中也不应该有任何问题。


等等实际上,我们正在使用这些测试来讲述网站上的管理员输入和用户体验的故事,并检查在每个州以及系统状态的每一个新变化中,预期的结果是否会发布。

九、详情测试

尽管用户在首页的详情视图不存在未来问题的问题,但是我们还是要把限制加好,以免用户通过猜解目录


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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值