Django项目实战——用户投票系统(五)

Django项目实战——用户投票系统五

  • Django项目实战——用户投票系统(五)
    • 前期概要
    • 正文开始
      • 自动化测试概述
      • 本案例的第一个测试
      • 开始全面测试
      • 测试视图
      • 更多的测试思路
    • 未完待续

Django项目实战——用户投票系统(五)

前期概要

​ 在Django(2-4)部分,我们通过Django轻量级服务器的运用,编辑了几个html视图,导入了视图的接口之后,成功做到了主界面,以及投票界面的搭建,相信正如《道德经》所言:

一生二,二生三,三生万物。通过举一反三我们可以通过本次的投票系统改进成其他平台项目的运用。

但这并不是该项目的尾声,一个项目在可视化完成之后,必不可少需要测试,在接下来的部分,我们开始创建一些自动化测试,来检验该项目的鲁棒性等一系列性能。

正文开始

我们已经建立了一个网络投票应用程序,现在我们将为它创建一些自动化测试。

同样的,链接附上:

编写你的第一个 Django 应用,第 5 部分 | Django 文档 | Django (djangoproject.com)

自动化测试概述

  1. 简介:

    首先是测试:是用来检查你的代码能否正常运行的程序

    ​ 测试在不同的层次中都存在。有些测试只关注某个很小的细节(某个模型的某个方法的返回值是否满足预期?),而另一些测试可能检查对某个软件的一系列操作(某一用户输入序列是否造成了预期的结果?)。

    一般使用shell来测试某一方面的功能,或者运行某一程序并输入数据来判断是否正常运行。

    其次是自动化:自动化 测试是由某个系统帮你自动完成的。当你创建好了一系列测试,每次修改应用代码后,就可以自动检查出修改后的代码是否还像你曾经预期的那样正常工作。你不需要花费大量时间来进行手动测试。

  2. 写测试的原因:

    • 自动化测试节约了手动测试的时间

      在某种程度上,能够「判断出代码是否正常工作」的测试,就称得上是个令人满意的了。在更复杂的应用程序中,组件之间可能会有数十个复杂的交互。

      对其中某一组件的改变,也有可能会造成意想不到的结果。判断「代码是否正常工作」意味着你需要用大量的数据来完整的测试全部代码的功能,以确保你的小修改没有对应用整体造成破坏——这太费时间了。

      尤其是当你发现自动化测试能在几秒钟之内帮你完成这件事时,就更会觉得手动测试实在是太浪费时间了。当某人写出错误的代码时,自动化测试还能帮助你定位错误代码的位置。

      有时候你会觉得,和富有创造性和生产力的业务代码比起来,编写枯燥的测试代码实在是太无聊了,特别是当你知道你的代码完全没有问题的时候。

      然而,编写测试还是要比花费几个小时手动测试你的应用,或者为了找到某个小错误而胡乱翻看代码要有意义的多

    • 测试不仅可以发现错误,也可以预防错误

      测试的出现改变了这种情况(整个应用的行为意图会变得更加的不清晰)。测试就好像是从内部仔细检查你的代码,当有些地方出错时,这些地方将会变得很显眼——就算你自己没有意识到那里写错了

    • 测试使代码更具有吸引力

      有测试的代码不值得信任。 Django 最初开发者之一的 Jacob Kaplan-Moss 说过:“项目规划时没有包含测试是不科学的。”

      其他的开发者希望在正式使用你的代码前看到它通过了测试,这是你需要写测试的另一个重要原因。

    • 测试有利于团队协作

      复杂的应用可能由团队维护。测试的存在保证了协作者不会不小心破坏了了你的代码(也保证你不会不小心弄坏他们的)

  3. 基础测试策略:

    • 一些开发者遵循 “[测试驱动]” 的开发原则,他们在写代码之前先写测试。这种方法看起来有点反直觉,但事实上,这和大多数人日常的做法是相吻合的。我们会先描述一个问题,然后写代码来解决它。「测试驱动」的开发方法只是将问题的描述抽象为了 Python 的测试样例。
    • 一个刚接触自动化测试的新手更倾向于先写代码,然后再写测试。虽然提前写测试可能更好,但是晚点写起码也比没有强。
    • 有时候很难决定从哪里开始下手写测试。如果你才写了几千行 Python 代码,选择从哪里开始写测试确实不怎么简单。如果是这种情况,那么在你下次修改代码(比如加新功能,或者修复 Bug)之前写个测试是比较合理且有效的

本案例的第一个测试

  1. Bug发现

    polls 应用有一个小 bug 需要被修复:

    我们的要求是如果 Question 是在一天之内发布的, Question.was_published_recently() 方法将会返回 True ,然而现在这个方法在 Questionpub_date 字段比当前时间还晚时也会返回 True(这是个 Bug)。

    现在用Python的shell的命令行来确认一下:

    ...\>py manage.py shell
    

    image-20220405152115540

    打开输入行后输入以下代码:

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

    但这时将来发生的,故不为True

  2. 创建一个测试

    将刚才在shell做出的测试变成自动化测试

    Django 应用的测试应该写在应用的 tests.py 文件里。测试系统会自动的在所有以 tests 开头的文件里寻找并执行测试代码。

    polls/tests.py

    import datetime
    
    from django.test import TestCase
    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)
    

    我们创建了一个 django.test.TestCase 的子类,并添加了一个方法,此方法创建一个 pub_date 时未来某天的 Question 实例。然后检查它的 was_published_recently() 方法的返回值——它 应该 是 False。

  3. 运用测试

    在终端中,我们通过输入以下代码运行测试:

    ...\> py manage.py test polls
    

    正常情况会出现以下界面:
    Django项目实战——用户投票系统(五)_第1张图片

    自动化的步骤为:

    • python manage.py test polls 将会寻找 polls 应用里的测试代码
    • 它找到了 [django.test.TestCase]的一个子类
    • 它创建一个特殊的数据库供测试使用
    • 它在类中寻找测试方法——以 test 开头的方法。
    • test_was_published_recently_with_future_question 方法中,它创建了一个 pub_date 值为 30 天后的 Question 实例。
    • 接着使用 assertls() 方法,发现 was_published_recently() 返回了 True,而我们期望它返回 False
  4. 修复bug

    pub_date 为未来某天时, Question.was_published_recently() 应该返回 False。我们修改 models.py 里的方法,让它只在日期是过去式的时候才返回 True

    polls/models.py

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

    于是会有如下界面

    Django项目实战——用户投票系统(五)_第2张图片

开始全面测试

现在可以考虑全面的测试 was_published_recently() 这个方法以确定它的安全性,然后就可以把这个方法稳定下来了,在上次写的类里再增加两个测试,来更全面的测试这个方法

polls/tests.py

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测试工具之Client

Django 提供了一个供测试使用的 [Client] 来模拟用户和视图层代码的交互。我们能在 tests.py 甚至是 [shell]中使用它。

我们依照惯例从 [shell]开始,首先我们要做一些在 tests.py 里不是必须的准备工作。第一步是在 [shell]中配置测试环境:

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

然后我们需要导入 [django.test.TestCase] 类(在后续 tests.py 的实例中我们将会使用 [django.test.TestCase] 类,这个类里包含了自己的 client 实例,所以不需要这一步):

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

测试开始:

>>> # get a response from '/'
>>> response = client.get('/')
Not Found: /
>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n    \n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

更多的测试思路

我们还可以从各个方面改进投票应用,但是测试会一直伴随我们。比方说,在目录页上显示一个没有选项 Choices 的投票问题就没什么意义。我们可以检查并排除这样的投票题。测试可以创建一个没有选项的投票,然后检查它是否被显示在目录上。当然也要创建一个有选项的投票,然后确认它确实被显示了。

恩,也许你想让管理员能在目录上看见未被发布的那些投票,但是普通用户看不到。不管怎么说,如果你想要增加一个新功能,那么同时一定要为它编写测试。不过你是先写代码还是先写测试那就随你了。

在未来的某个时刻,你一定会去查看测试代码,然后开始怀疑:「这么多的测试不会使代码越来越复杂吗?」。别着急,我们马上就会谈到这一点。

如果你对测试有个整体规划,那么它们就几乎不会变得混乱。下面有几条好的建议:

  • 对于每个模型和视图都建立单独的 TestClass
  • 每个测试方法只测试一个功能
  • 给每个测试方法起个能描述其功能的名字

未完待续

现在的页面虽然已经可以开始基础的目标功能,但我们还缺少美观,所以在下一步,我们将开始静态文件的封装,通常需要一些额外的文件——比如图片,脚本和样式表——来帮助渲染网络页面。在 Django 中,我们把这些文件统称为“静态文件”。……敬请期待。

你可能感兴趣的:(Django,python,django,前端框架)