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

 

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

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

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

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

 

【未完待续】

本文版权归舍得学苑所有,欢迎转载,转载请注明作者和出处。谢谢!

作者:舍得

首发:舍得学苑@博客园

实战Django:官方实例Part5的更多相关文章

  1. 实战Django:官方实例Part1

    [写在前面] 撰写这个实战系列的Django文章,是很久之前就有的想法,问题是手头实例太少,一旦开讲,恐有"无米下锅"之忧. 随着对Django学习的深入,渐渐有了些心得,把这些心 ...

  2. 实战Django:官方实例Part2

    我们接着Part1部分往下讲.我们在part1中启动服务器后,并没有在管理页面中发现新添加的Polls应用,怎么办捏? 7.在管理界面中显示Question 只要注册一下这个应用就可以了.编辑poll ...

  3. 实战Django:官方实例Part6

    我们终于迎来了官方实例的最后一个Part.在这一节中,舍得要向大家介绍Django的静态文件管理. 现在,我们要往这个投票应用里面添加一个CSS样式表和一张图片. 一个完整的网页文件,除了html文档 ...

  4. 微软官方实例 RazorPagesMovie 在 asp.net core 2.1 版本下的实战

    微软官方实例 RazorPagesMovie 在 asp.net core 2.1 版本下的实战 友情提示: 操作系统: MacOS 10.13.5 dotnet core: version 2.1. ...

  5. 实战Django:简易博客Part1

    舍得学习新技能的时候,通常不喜欢傻读书--捧着一本阐述该项技能的书籍,然后傻看,一路看下来,脑子里塞满了新的概念.知识点,头是越来越大,但技能却几乎没掌握半分. 多年来,舍得养成了用做实例来学习新技能 ...

  6. 对《[Unity官方实例教程 秘密行动] Unity官方教程《秘密行动》(十二) 角色移动》的一些笔记和个人补充,解决角色在地形上移动时穿透问题。

    这里素材全是网上找的. 教程看这里: [Unity官方实例教程 秘密行动] Unity官方教程<秘密行动>(九) 角色初始设定 一.模型设置: 1.首先设置模型的动作无限循环. 不设置的话 ...

  7. Android实例-Delphi开发蓝牙官方实例解析(XE10+小米2+小米5)

    相关资料:1.http://blog.csdn.net/laorenshen/article/details/411498032.http://www.cnblogs.com/findumars/p/ ...

  8. 微信应用号开发知识贮备之altjs官方实例初探

    天地会珠海分舵注:随着微信应用号的呼之欲出,相信新一轮的APP变革即将发生.从获得微信应用号邀请的业内人士发出来的一张开发工具源码截图可以看到,reacjs及其相应的FLUX框架altjs很有可能会成 ...

  9. 源于《Unity官方实例教程 “Space Shooter”》思路分析及相应扩展

    教程来源于:Unity官方实例教程 Space Shooter(一)-(五)       http://www.jianshu.com/p/8cc3a2109d3b 一.经验总结 教程中步骤清晰,并且 ...

随机推荐

  1. Haar特征

    转自:http://blog.csdn.net/carson2005/article/details/8094699 Haar-like特征,即很多人常说的Haar特征,是计算机视觉领域一种常用的特征 ...

  2. Testlink中分析结果的图表显示乱码

    1.下载 tahoma.ttf 字体: 2.下载好后,我们将其放置到,testlink的安装目录的以下文件夹中: testlink/third_party/pchart/Fonts/ 接下来,修改配置 ...

  3. python-appium手机自动化测试(仅需安装包)前期准备(pydev-eclipse编辑器)

    1.jdk安装与环境变量配置教程http://jingyan.baidu.com/article/6dad5075d1dc40a123e36ea3.html 我本机安装的是1.6.043 2.sdk下 ...

  4. [WinForm]平均切割图片AvgCutImage

    昨天晚上下班前有朋友问我有的人的QQ空间相册的那种多个图片拼接成一张完整的图片的是怎么做到的比如像这样效果: 嘛,反正我是1000%不会使用这样封面来做网络相册的封面,因为用户体验实在是太差了.完全不 ...

  5. Android开发-API指南-设备兼容性

    Device Compatibility 英文原文:http://developer.android.com/guide/practices/compatibility.html 采集日期:2014- ...

  6. 业务gis 搭建一个skyline 的js模板 (一)

    刚刚我们说的是二维的系统,如果要展示三维,我们是不是也需要这样,答案是必须的,是一定要,如果你是基于skyline做三维开发,业务开发人员要去搞那套api估计要吐血,所以我们必须得封装起来,这里不介绍 ...

  7. 未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序的处理方式

    今天客户向我反映一个问题,当他们在用我们的系统导出excel表格时,报错:未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序 经过找资料终于得到解决方法,记录一下. 在对应 ...

  8. UIBlurEffect实现模糊效果

    //使用图片初始化背景 Pattern 图案,模式 self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageN ...

  9. 在java 中,数组与 List<T> 类型的相互转换

    在java中,数组与List<T> 之前进行互相转换,转换方法可总结为以下几种: 一. 将 数组转换成List<T> 1. 使用 Collections 的addAll 方法 ...

  10. 查询数据库中表或视图或存储过程的数量 sql 语句

    如果一个数据库中表的数量较多的话,那么在统计数据库中表的数量或视图或存储过程的数量时,如果还有一个一个去数,那就太麻烦了,其实可以通过 sql 语句来查询的,sql 语句的查询方法如下: sql se ...