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模板引擎中最强大也是最复杂的部分就是模板继承了,模板继承可以让我们创建一个基本的"骨架"模板,它可以包含网页中 ...
随机推荐
- SQL 注入、XSS 攻击、CSRF 攻击
SQL 注入.XSS 攻击.CSRF 攻击 SQL 注入 什么是 SQL 注入 SQL 注入,顾名思义就是通过注入 SQL 命令来进行攻击,更确切地说攻击者把 SQL 命令插入到 web 表单或请求参 ...
- SpringBoot-02:SpringBoot中的POM文件详细解释
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 我把pom文件,以及它的详细解释发出来 <?xml version="1.0" en ...
- iOS 测试工具reveal可视化调试工具的使用
简单翻译一下reveal可视化图形工具插入项目的官方文档(官方英文版file:///Applications/Reveal.app/Contents/SharedSupport/Documentati ...
- Qml-Dialog不能隐藏标题栏和按钮自定义
在项目中,需要弹出一个对话框来完成用户输入的功能,为了考虑界面的同一,这里需要将原生自带的标题栏隐藏掉,换成自己写的 按照widget的写法,可以使用QDialog,但是qml与之对应的Dialog我 ...
- wpf基础使用_修改窗体图标
废话不多说,直接开始修改图标步骤: 当然直接使用绝对路径添加图标也是可以的,这种方式不可取,一旦图标移动位置或被删除,就会导致找不到图标文件报错,这里我们介绍的是另一个方式,使用资源文件的方式添加 1 ...
- Linux命令应用大词典-第26章 模块和内核管理
26.1 lsmod:显示内核中模块的状态 26.2 get_module:查看内核模块详细信息 26.3 modinfo:显示内核模块信息
- 【转】cocos2dx3.2学习笔记之Director(导演类)
转载:https://blog.csdn.net/u013435551/article/details/38579747 在Cocos2d-x中,把统筹游戏大局的类抽象为导演类(Director),D ...
- SQL语言重点学习
数据库的操作任务通常包括以下几个方面: 1.查询数据. 2.在表中插入,修改和删除记录. 3.建立,修改和删除数据对象. 4.控制对数据和数据对象的读写. 5.保证数据库一致性和完整性. SQL语言学 ...
- [JSON].value( keyPath )
语法:[JSON].value( keyPath ) 返回:[String | Null] 说明:获取指定键名路径值的字符串格式 示例: Set jsonObj = toJson("{bod ...
- 【转】VSstudio中的一些宏
说明 $(RemoteMachine) 设置为“调试”属性页上“远程计算机”属性的值.有关更多信息,请参见更改用于 C/C++ 调试配置的项目设置. $(References) 以分号分隔的引用列表被 ...