Django 2.0 学习(08):Django 自动化测试
编写我们的第一个测试
确定bug
幸运的是,在polls应用中存在一个小小的bug急需修复:无论Question的发布日期是最近(最后)的日期,还是将来很多天的日期,Question.was_published_recently()方法都会返回True。使用下面的代码对其进行验证:
>>> import datetime
>>>
>>> from django.utils import timezone
>>> from polls.models import Question
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> future_question.was_published_recently()
True
然而,将来的事情不可能是最近(最后)的,这是明显的错误。
创建测试显示bug
我们刚刚在shell中完成的测试问题正是我们在自动化测试中要做的,因此让我们转入自动化测试。按照惯例,对应用的测试就是在应用的tests.py文件中,测试系统会自动查找以test开始的任意文件。在polls应用中编辑tests.py文件,代码如下:
import datetime
from django.utils import timezone
from django.test import TestCase
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
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。
运行测试
在终端中,运行测试代码:
python manage.py test polls
我们将会看到下面的输出内容:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "E:\mysite\polls\tests.py", line 12, in test_was_published_recently_with_future_question
self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Destroying test database for alias 'default'...
我们来分析下,发生了什么:
- python manage.py test polls命令在polls应用中查找测试;
- 查找到django.test.TestCase子类;
- 为目标测试创建一个专门的数据库;
- 查找一个名字以test开始的方法;
- 在test_was_published_recently_with_future_question方法中,创建一个Question实例,该实例的pub_date字段是将来的30天;
- 最后,assertIs()方法发现was_published_recently()返回True,而我们想要的返回是False;
测试通知我们哪个测试失败了,并且失败产生在代码的哪行。
修复bug
我们已经知道问题是:Question.was_published_recently()在其参数pub_date是将来的日期时会返回False。修改models.py文件中的方法,让它在日期是过去的情况下也能返回True,其代码如下:
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
再次运行测试命令,会看到如下所示:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Destroying test database for alias 'default'...
更全面的测试
在这里,我们可以进一步约束was_published_recently()方法;实际上,在修复bug时引进其他bug是非常尴尬的事情。在相同的类中,我们增加两个方法来全面的测试方法的行为,编辑文件polls/tests.py代码如下:
import datetime
from django.utils import timezone
from django.test import TestCase
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
def test_was_published_recently_with_old_question(self):
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):
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)
现在有三个测试方法来证实Question.was_published_recently()对过去、现在和将来的返回值。
测试视图
Django测试客户端
Django提供了一个测试客户端,用来模仿用户在视图级别与我们的代码互动。我们可以在test.py中使用它,也可以在shell中使用。我们将在shell中来进行测试,首先启动shell并在其中设置测试环境:
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
setup_test_environment()安装一个模板渲染器,它可以检查响应的某些附加属性,比如:response.context;否则附加属性将不可用,但是该方法不会设置测试数据库。
接下来,我们需要导入测试客户端类:
>>> from django.test import Client
>>> client = 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>\n <a href="/polls/1/">What is up old?</a>\n
</li>\n \n </ul>\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What is up old?>]>
改进视图函数
打开polls/views.py文件,添加代码如下:
from django.utils import timezone
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
Question.objects.filter(pub_date__lte=timezone.now()) 返回一个Questions查询集合,该集合是pub_date小于或等于timezone.now的数据。
现在我们基于test.py文件进行新的测试,打开polls/test.py文件,添加如下代码:
from django.urls import reverse
def create_question(question_text, days):
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):
response = self.client.generic(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):
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_future_question(self):
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):
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_two_past_questions(self):
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_no_question没有创建任何问题,只是检查信息No polls are available"",并且确认latest_question_list是否为空。django.test.TestCase类提供了额外的断言方法,上面的例子中就用到asserContains()和assertQuerysetEqual()这两个方法;
- 在test_past_question中,我们创建了一个问题并且确认该问题会出现在问题列表中;
- 在test_future_question中,我们创建了一个发布日期是将来的问题。对于每个测试方法,数据库都会被重置,所以第一个问题不会存在在数据库中,并且在index中不会由任何问题;
测试DetailView
我们做的已经很好了,尽管将来的问题没有显示在index页面中,但是如果用户知道正确的URL或者猜到它们,用户依然能够访问到它们。所以,我们需要对DetailView视图添加相似的约束,依然打开polls/views.py文件,修改代码如下:
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now())
同理,我们将会添加一些测试,去检查每个问题:如果发布日期是过去的,就显示;如果发布日期是将来的,就不显示;打开polls/tests.py文件,添加下面代码:
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
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):
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)
好的测试规则应该遵循:
- 对于每个模型或者视图,编写独立的(测试类)TestClass;
- 对于每个测试条件编写独立的测试方法;
- 可以通过测试方法的名字理解其功能;
Django 2.0 学习(08):Django 自动化测试的更多相关文章
- Django 2.0 学习(07):Django 视图(进阶-续)
接Django 2.0 学习(06):Django 视图(进阶),我们将聚焦在使用简单的表单进行处理和精简代码. 编写简单表单 我们将用下面的代码,来替换之前的detail模板("polls ...
- Django 2.0 学习(04):Django数据库
数据库设置/配置 打开mysite/settings.py,我们会发现Django是用的是默认的数据库SQLite,如下图所示: Django也是支持其它数据库的,比如PostgreSQL.MySQL ...
- Django 2.0 学习
Django django是基于MTV结构的WEB框架 Model 数据库操作 Template 模版文件 View 业务处理 在Python中安装django 2.0 1 直接安装 pip inst ...
- Django 2.0 学习(12):Django 模板语法
Django 模板语法 一.模板 只要是在html里面有模板语法就不是html文件了,这样的文件就叫做模板. 二.模板语法 模板语法变量:{{ }} 在Django模板中遍历复杂数据结构的关键是句点字 ...
- Django 2.0 学习(19):Django 分页器
Django 分页器 要使用Django实现分页功能,必须从Django中导入Paginator模块(painator - 分页器) views.py from django.shortcuts im ...
- Django 2.0 学习(06):Django 视图(进阶)
概述 Django中的特方法,该方法代表了Django的Web页面,并且视图具有特定的模板.以博客应用为例进行说明,在博客应用中应该包含下面的视图: 博客主页:显示最近的一些记录: 详细页面:单个详细 ...
- Django 2.0 学习(03):Django视图和URL(下)
接上篇博文,继续分析Django基本流程. 编写第一个(view)视图函数 1.打开文件polls/views.py,输入下面的Python代码: from django.http import Ht ...
- Django 2.0 学习(01):Django初识与安装
Django(Python Web框架) Django是一个开放源代码的Web框架,用Python写的.采用了MTV的框架模式,即模型M,模板T和视图V.它最初被开发是用来管理以新闻内容为主的网站,即 ...
- Django 2.0 学习(13):Django模板继承和静态文件
Django模板继承和静态文件 模板继承(extend) Django模板引擎中最强大也是最复杂的部分就是模板继承了,模板继承可以让我们创建一个基本的"骨架"模板,它可以包含网页中 ...
随机推荐
- 在线接口文档工具——ShowDoc
ShowDoc:https://www.showdoc.cc/ --待更.
- 【机器学习笔记】EM算法及其应用
极大似然估计 考虑一个高斯分布\(p(\mathbf{x}\mid{\theta})\),其中\(\theta=(\mu,\Sigma)\).样本集\(X=\{x_1,...,x_N\}\)中每个样本 ...
- day 5 模块发布安装
1.模块的位置 现在当前路径查找,再到系统路径/usr/lib/python3.5/查找,再到其他系统路径查找 2.模块发布 1)模块目录结构 Msg ├── __init__.py ├── recv ...
- 在Win10中通过命令行打开UWP应用
近期由于需要在WinX菜单中添加几个UWP应用,但发现很难找到相应的命令行,Universal Apps 的快捷方式属性里也没有. 于是到网上搜了很久才找到一个E文的页面,试了一下确实可行,分享给大家 ...
- VueJs 学习笔记
VueJs学习笔记 参考资料:https://cn.vuejs.org/ 特效库:TweenJS(补间动画库) VelocityJS(轻量级JS动画库) Animate.css(CSS预设动画库) ...
- 「日常训练&知识学习」单调栈
这几天的知识学习比较多,因为时间不够了.加油吧,为了梦想. 这里写几条简单的单调栈作为题解记录,因为单调栈的用法很简单,可是想到并转化成用这个需要一些题目的积淀. 相关博客参见:https://blo ...
- 那些年我们不爱学的mysql单词
MySQL 一种关系型数据库 database 数据库,简称DB databases 数据库的复数,代表多个数据库 net 网络/服务 start 启动 stop 停止 root MySQL数据库中的 ...
- lintcode112 删除排序链表中的重复元素
删除排序链表中的重复元素 给定一个排序链表,删除所有重复的元素每个元素只留下一个. 您在真实的面试中是否遇到过这个题? Yes 样例 给出 1->1->2->null,返回 1- ...
- OSS文件上传及OSS与ODPS之间数据连通
场景描述 有这样一种场景,用户在自建服务器上存有一定数量级的CSV格式业务数据,某一天用户了解到阿里云的OSS服务存储性价比高(嘿嘿,颜值高),于是想将CSV数据迁移到云上OSS中,并且 ...
- Django创建App报错
在django下创建APP项目时遇到的坑 python manage.py startapp app01 报错内容如下: 解决:找到报错中的文件夹151行删除items(),)中的逗号即可 在命令行下 ...