django应用的测试
本文章默认用户使用win10系统,并且已经安装pycharm、git、django2.2.5及配套第三方库(python3.6.0及以上版本,且为anaconda环境)
前言
其实在上一期django文章中就有测试的出现,我们使用shell测试数据库的功能,但这属于手动测试
在这篇文章中,我们要介绍的是自动化测试,即当你创建好了一系列测试,每次修改应用代码后,就可以自动检查出修改后的代码是否还像你曾经预期的那样正常工作,而不需要花费大量时间来进行手动测试简直懒人福音
对于任何一个项目来说,编写自动化测试都是十分重要的
测试驱动
一般我们采取先写测试后写代码的原则
自动化测试
发现漏洞
我们在上篇文章的结尾说到,我们的投票系统存在一个bug: Question.was_published_recently() 方法其实并不能正常判断该问题是否在刚才成功创建,在这里我们可以手动测试
在进入虚拟环境与交互式命令台后,依次输入以下命令
>>> 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
暴露漏洞
我们在外层’polls’文件夹中修改tests.py来完成自动化测试,代码如下:
1 |
from django.test import TestCase |
2 |
from django.utils import timezone |
3 |
from .models import Question |
4 |
|
5 |
|
6 |
import datetime |
7 |
|
8 |
|
9 |
class (TestCase): |
10 |
|
11 |
def test_was_published_recently_with_future_question(self): |
12 |
time = timezone.now() + datetime.timedelta(days=30) |
13 |
future_question = Question(pub_date=time) |
14 |
self.assertIs(future_question.was_published_recently(), False) |
在上述代码中,我们通过创建test_was_published_recently_with_future_question() 方法用于检查was_published_recently() 函数的返回值是否正确
我们在该方法中给出了一个未来的问题实例future_question,并告知tests.py文件其应该返回False
测试
在外层’mysite’文件夹中输入
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 "C:UsersAdministratorPycharmProjectsmy_djangopollstests.py", line 18, 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.002s
FAILED (failures=1)
Destroying test database for alias 'default'...
在输出中会给出错误函数、错误位置、错误原因、错误样例等一系列信息,有利于编码人员快速定位bug所在
在这里,自动化测试流程为:
- python manage.py test polls 将会寻找 polls 应用里的测试代码
- 它找到了 django.test.TestCase 类
- 它创建一个特殊的数据库供测试使用
- 它在查找到的类中寻找测试方法——以 test 开头的方法
- 它找到 test_was_published_recently_with_future_question 方法,并创建了一个 pub_date 值为 30 天后的 Question 实例
- 接着使用 assertIs() 方法,发现 was_published_recently() 返回了 True,而我们期望它返回 False
修复漏洞
我们该如何修复这个bug呢?
我们首先知道,这个bug是因为函数无法识别问题的真实创建时间,将未来的问题默认为刚才创建的了
所以,针对这个问题,我们可以通过修改’polls’文件夹中的models.py文件里的方法,让出现bug的方法认为只有过去的问题才能返回True,代码如下:
1 |
import datetime |
2 |
|
3 |
from django.db import models |
4 |
from django.utils import timezone |
5 |
|
6 |
# Create your models here. |
7 |
class Question(models.Model): |
8 |
question_text = models.CharField(max_length=200) |
9 |
pub_date = models.DateTimeField('date published')
|
10 |
def __str__(self): |
11 |
return self.question_text |
12 |
|
13 |
def was_published_recently(self): |
14 |
now = timezone.now() |
15 |
return now - datetime.timedelta(days=1) <= self.pub_date <= now |
16 |
|
17 |
|
18 |
class Choice(models.Model): |
19 |
question = models.ForeignKey(Question, on_delete=models.CASCADE) |
20 |
choice_text = models.CharField(max_length=200) |
21 |
votes = models.IntegerField(default=0) |
22 |
def __str__(self): |
23 |
return self.choice_text |
该代码中的was_published_recently()方法确保了在一天前到现在所创建的问题都返回True,其余返回False
我们可以重新做一次测试
观察输出
我的输出:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
这说明针对该方法的bug修复成功,这也同样意味着,该方法不会再出现bug,可以放心调用
更全面的测试
有这样一种情况:在修复一个 bug 时不小心引入另一个 bug
为了避免这种情况的出现虽然往往无法避免,我们需要进行更加全面的测试
我们通过修改’polls’文件夹中的tests.py文件来完善,代码如下:
1 |
from django.test import TestCase |
2 |
from django.utils import timezone |
3 |
from .models import Question |
4 |
|
5 |
|
6 |
import datetime |
7 |
|
8 |
|
9 |
class (TestCase): |
10 |
|
11 |
def test_was_published_recently_with_future_question(self): |
12 |
time = timezone.now() + datetime.timedelta(days=30) |
13 |
future_question = Question(pub_date=time) |
14 |
self.assertIs(future_question.was_published_recently(), False) |
15 |
|
16 |
def test_was_published_recently_with_old_question(self): |
17 |
time = timezone.now() - datetime.timedelta(days=1, seconds=1) |
18 |
old_question = Question(pub_date=time) |
19 |
self.assertIs(old_question.was_published_recently(), False) |
20 |
|
21 |
def test_was_published_recently_with_recent_question(self): |
22 |
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) |
23 |
recent_question = Question(pub_date=time) |
24 |
self.assertIs(recent_question.was_published_recently(), True) |
根据修改后的was_published_recently()方法,我们更新了我们的测试方法,使其符合修改后的该方法的所有的返回值(针对本方法,即未来与过去返回False,刚才返回True)
我们需要明白的是进行过测试的那些方法的行为永远是符合预期的
测试视图
修复了上述bug后,视图方面也会产生一定的问题:系统会发布所有问题,也包括那些未来的问题。如果将pub_date设置为未来某天,该问题应该在所填写的时间点才被发布,而在此之前是不可见的。
测试工具
Django 提供了一个供测试使用的 Client 来模拟用户和视图层代码的交互
我们通过交互式命令台来使用它,代码及其对应的输出如下:
>>> from django.test.utils import setup_test_environment
>>> setup_test_environment()
>>> from django.test import 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/1/">What's up?</a></li>n n </ul>n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>
了解一定python爬虫requests库的同学应该能很快理解上述代码,我们可以将这里的client理解成requests
开始修复
我们通过修改’polls’文件夹中的views.py文件来修复视图,代码如下:
1 |
from django.shortcuts import get_object_or_404, render |
2 |
|
3 |
# Create your views here. |
4 |
from django.http import HttpResponseRedirect |
5 |
from django.views import generic |
6 |
from django.urls import reverse |
7 |
from django.utils import timezone |
8 |
|
9 |
from .models import Choice, Question |
10 |
|
11 |
class IndexView(generic.ListView): |
12 |
template_name = 'polls/index.html' |
13 |
context_object_name = 'latest_question_list' |
14 |
|
15 |
def get_queryset(self): |
16 |
return Question.objects.filter( pub_date__lte=timezone.now() ).order_by('-pub_date')[:5]
|
17 |
|
18 |
|
19 |
class DetailView(generic.DetailView): |
20 |
model = Question |
21 |
template_name = 'polls/detail.html' |
22 |
|
23 |
|
24 |
class ResultsView(generic.DetailView): |
25 |
model = Question |
26 |
template_name = 'polls/results.html' |
27 |
|
28 |
|
29 |
def vote(request, question_id): |
30 |
question = get_object_or_404(Question, pk=question_id) |
31 |
try: |
大专栏 django应用的测试gutter">
32 |
selected_choice = question.choice_set.get(pk=request.POST['choice']) |
33 |
except (KeyError, Choice.DoesNotExist): |
34 |
return render(request, 'polls/detail.html', {
|
35 |
'question': question, |
36 |
'error_message': "You didn't select a choice.", |
37 |
}) |
38 |
else: |
39 |
selected_choice.votes += 1 |
40 |
selected_choice.save() |
41 |
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
|
在上述代码中,我们在get_queryset()方法中修改了原有的返回值,它由Question 的 pub_data 属性与 timezone.now() 相比较来判断是否应该显示此问题
编写测试
我们通过’polls’文件夹中的tests.py文件来编写测试,代码如下:
1 |
from django.test import TestCase |
2 |
from django.utils import timezone |
3 |
from django.urls import reverse |
4 |
from .models import Question |
5 |
|
6 |
|
7 |
import datetime |
8 |
|
9 |
|
10 |
class (TestCase): |
11 |
|
12 |
def test_was_published_recently_with_future_question(self): |
13 |
time = timezone.now() + datetime.timedelta(days=30) |
14 |
future_question = Question(pub_date=time) |
15 |
self.assertIs(future_question.was_published_recently(), False) |
16 |
|
17 |
def test_was_published_recently_with_old_question(self): |
18 |
time = timezone.now() - datetime.timedelta(days=1, seconds=1) |
19 |
old_question = Question(pub_date=time) |
20 |
self.assertIs(old_question.was_published_recently(), False) |
21 |
|
22 |
def test_was_published_recently_with_recent_question(self): |
23 |
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) |
24 |
recent_question = Question(pub_date=time) |
25 |
self.assertIs(recent_question.was_published_recently(), True) |
26 |
|
27 |
|
28 |
def create_question(question_text, days): |
29 |
time = timezone.now() + datetime.timedelta(days=days) |
30 |
return Question.objects.create(question_text=question_text, pub_date=time) |
31 |
|
32 |
|
33 |
class QuestionIndexViewTests(TestCase): |
34 |
def test_no_questions(self): |
35 |
response = self.client.get(reverse('polls:index'))
|
36 |
self.assertEqual(response.status_code, 200) |
37 |
self.assertContains(response, "No polls are available.") |
38 |
self.assertQuerysetEqual(response.context['latest_question_list'], []) |
39 |
|
40 |
def test_past_question(self): |
41 |
create_question(question_text="Past question.", days=-30) |
42 |
response = self.client.get(reverse('polls:index'))
|
43 |
self.assertQuerysetEqual( |
44 |
response.context['latest_question_list'], |
45 |
['<Question: Past question.>'] |
46 |
) |
47 |
|
48 |
def test_future_question(self): |
49 |
create_question(question_text="Future question.", days=30) |
50 |
response = self.client.get(reverse('polls:index'))
|
51 |
self.assertContains(response, "No polls are available.") |
52 |
self.assertQuerysetEqual(response.context['latest_question_list'], []) |
53 |
|
54 |
def test_future_question_and_past_question(self): |
55 |
create_question(question_text="Past question.", days=-30) |
56 |
create_question(question_text="Future question.", days=30) |
57 |
response = self.client.get(reverse('polls:index'))
|
58 |
self.assertQuerysetEqual( |
59 |
response.context['latest_question_list'], |
60 |
['<Question: Past question.>'] |
61 |
) |
62 |
|
63 |
def test_two_past_questions(self): |
64 |
create_question(question_text="Past question 1.", days=-30) |
65 |
create_question(question_text="Past question 2.", days=-5) |
66 |
response = self.client.get(reverse('polls:index'))
|
67 |
self.assertQuerysetEqual( |
68 |
response.context['latest_question_list'], |
69 |
['<Question: Past question 2.>', '<Question: Past question 1.>'] |
70 |
) |
在上述代码中,create_question()方法封装了创建投票的流程,然后在QuestionIndexViewTests类中进行一系列的测试
本质上,测试就是假装一些管理员的输入,然后通过用户端的表现是否符合预期来判断新加入的改变是否破坏了原有的系统状态
优化测试
如果有人能通过规律猜测到未发布的问题的URL该怎么办?
我们通过对’polls’文件夹中的views.py文件中的DetailView类做一定的约束,代码如下:
1 |
class DetailView(generic.DetailView): |
2 |
model = Question |
3 |
template_name = 'polls/detail.html' |
4 |
|
5 |
def get_queryset(self): |
6 |
return Question.objects.filter(pub_date__lte=timezone.now()) |
并在tests.py文件中添加对应的测试,代码如下:
1 |
class QuestionDetailViewTests(TestCase): |
2 |
def test_future_question(self): |
3 |
future_question = create_question(question_text='Future question.', days=5) |
4 |
url = reverse('polls:detail', args=(future_question.id,))
|
5 |
response = self.client.get(url) |
6 |
self.assertEqual(response.status_code, 404) |
7 |
|
8 |
def test_past_question(self): |
9 |
past_question = create_question(question_text='Past Question.', days=-5) |
10 |
url = reverse('polls:detail', args=(past_question.id,))
|
11 |
response = self.client.get(url) |
12 |
self.assertContains(response, past_question.question_text) |
这些测试用来检验 pub_date 在过去的 Question 可以显示出来,而 pub_date 为未来的不可以显示
代码测试是一个较大的内容,一般我们对于每个模型和视图都建立单独的TestClass;每个测试方法只测试一个功能;给每个测试方法起个能描述其功能的名字
自定义
自定义页面
我们在外层’polls’文件夹中创建一个’static’的文件夹,django将会在此文件夹下查找页面对应的静态文件
我们在’static’文件夹下创建一个’polls’文件夹,在该文件夹中创建一个style.css文件,代码如下:
1 |
li a {
|
2 |
color: green; |
3 |
} |
随后我们修改index.html,使其能够调用.css,代码如下:
1 |
{% load static %}
|
2 |
|
3 |
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
|
4 |
|
5 |
{% if latest_question_list %}
|
6 |
<ul> |
7 |
{% for question in latest_question_list %}
|
8 |
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
|
9 |
{% endfor %}
|
10 |
</ul> |
11 |
{% else %}
|
12 |
<p>No polls are available.</p> |
13 |
{% endif %}
|
如果要添加图片,请在内层’polls’文件夹中创建’images’文件夹,用来存放图片,这属于静态前端内容,再此不多做介绍
自定义后台
自定义后台过程较为繁琐,将在以后的文章中专门介绍,感兴趣的同学可以参考
结尾
如果已经看完这两篇文章,你应该对django整体框架有一定的了解,我们将在后面几篇文章中介绍更详细的django,比如管理文件、缓存、打包等
以上是django学习第二弹,收工。
django应用的测试的更多相关文章
- Django单元测试(二)------测试工具
The test client test client是一个python类,来模拟一个简单的“哑”浏览器,允许你来测试你的view函数.你可以使用test client完成下列事情: 1.模拟&quo ...
- Django权限管理测试
测试内容:当我单击登录页面登录的时候页面会弹出当前用户的个人信息 当我点击提交的时候可以看到我当前用户的所有权限: 测试成功,接下来看一下后台的简单代码: class User(models.Mode ...
- 关于Django启动创建测试库的问题
最近项目迁移到别的机器上进行开发,启动Django的时候,有如下提示: Creating test database for alias 'default' 其实这个可能是在Django启动按钮的设置 ...
- dapi 基于Django的轻量级测试平台一 设计思想
GitHub:https://github.com/yjlch1016/dapi 一.项目命名: dapi:即Django+API测试的缩写 二.设计思想: 模拟性能测试工具JMeter的思路, 实现 ...
- 统计 Django 项目的测试覆盖率
作者:HelloGitHub-追梦人物 文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 我们完成了对 blog 应用和 comment 应用这两个核心 app 的测试.现在 ...
- Django 模版语法 测试环境 ORM单表查询
模版语法 传值 视图函数向前端html页面传值,基本上所有的数据类型都可以渲染在前端页面上. views.py from django.shortcuts import render, redirec ...
- django 网站项目测试
视图和 URL 配置: 在先前创建的 meishiweb目录下的 meishiweb 目录新建一个 view.py 文件,并输入代码: 此时在浏览器即可访问: 证明已经成功 我们也可以修改成以下的规则 ...
- dapi 基于Django的轻量级测试平台七 怎样部署到生产环境
QQ群: GitHub:https://github.com/yjlch1016/dapi Nginx+uWSGI 前置条件:以下所有操作均在root账号下面进行如果不是root用户请注意权限问题因为 ...
- dapi 基于Django的轻量级测试平台八 Docker部署
QQ群: GitHub:https://github.com/yjlch1016/dapi 采用Docker+Supervisor+Nginx+uWSGI+Django 一.Dockerfile文件: ...
随机推荐
- This inspection highlights chained comparisons that can be simplified.
https://stackoverflow.com/questions/26502775/pycharm-simplify-chained-comparison In Python you can & ...
- h5集成环信在线客服自定义窗口
自定义客服窗口从底部弹出 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- SVN提交时忽略不必提交的文件夹和文件,如node_modules
空白处右键>选中TortoiseSVN>设置(settings)>常规设置(General)>Subversion>编辑(edit)>在弹出的config文件中找g ...
- 题解 P2981 【[USACO10FEB]奶牛在冰Cows on Ice】
楼上的思路都是从一个石头找跳到另一个石头的路径,但其实这题可以对于上下左右四个方向分别做一个虚拟节点,然后只需要找虚拟节点左边的虚拟节点就行了 问题是:不会用set怎么办??? 其实可以发现用vect ...
- switch-case的用法
case 值1: 表达式的值和 值1匹配上了,需要执行的代码; break; case 值2: 表达式的值和 值2匹配上了,需要执行的代码; break; case 值3: 表达式的值和 值3匹配上了 ...
- C盘满了解决办法之pagefile.sys文件
pagefile.sys文件一般存在于C盘,只有点击了隐藏属性才能看见. 这个文件一般比较大,它是系统创建虚拟内存页面的文件.平时大家使用软件的时候对于产生大量的临时数据,这些数据需要占用大量内存,如 ...
- java添加后台缓存
public class Cache { private String key;//缓存ID private Object value;//缓存数据 private long timeOut;//更新 ...
- 长沙中考2019数学T25讲解
好久没更Blog了... 为了应付完成寒假作业,还是更一下(再不更都庚子年了) Upd:2020.1.22 题目 第一问 还是比较水友好的 给顶点就相当于多给了对称轴-\(\frac{b}{2a}\) ...
- Java集合详解(全)
Java的集合主要有List , Set, Map List , Set继承至Collection接口,Map为独立接口 List下有ArrayList,LinkedList,Vector Set下有 ...
- RDD(二)——创建
RDD的创建 1)从内存中创建 从集合中创建RDD,Spark主要提供了两种函数:parallelize和makeRDD val raw: RDD[Int] = sc.parallelize(1 to ...