本文章默认用户使用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())

有关.object.filter()

并在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应用的测试的更多相关文章

  1. Django单元测试(二)------测试工具

    The test client test client是一个python类,来模拟一个简单的“哑”浏览器,允许你来测试你的view函数.你可以使用test client完成下列事情: 1.模拟&quo ...

  2. Django权限管理测试

    测试内容:当我单击登录页面登录的时候页面会弹出当前用户的个人信息 当我点击提交的时候可以看到我当前用户的所有权限: 测试成功,接下来看一下后台的简单代码: class User(models.Mode ...

  3. 关于Django启动创建测试库的问题

    最近项目迁移到别的机器上进行开发,启动Django的时候,有如下提示: Creating test database for alias 'default' 其实这个可能是在Django启动按钮的设置 ...

  4. dapi 基于Django的轻量级测试平台一 设计思想

    GitHub:https://github.com/yjlch1016/dapi 一.项目命名: dapi:即Django+API测试的缩写 二.设计思想: 模拟性能测试工具JMeter的思路, 实现 ...

  5. 统计 Django 项目的测试覆盖率

    作者:HelloGitHub-追梦人物 文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 我们完成了对 blog 应用和 comment 应用这两个核心 app 的测试.现在 ...

  6. Django 模版语法 测试环境 ORM单表查询

    模版语法 传值 视图函数向前端html页面传值,基本上所有的数据类型都可以渲染在前端页面上. views.py from django.shortcuts import render, redirec ...

  7. django 网站项目测试

    视图和 URL 配置: 在先前创建的 meishiweb目录下的 meishiweb 目录新建一个 view.py 文件,并输入代码: 此时在浏览器即可访问: 证明已经成功 我们也可以修改成以下的规则 ...

  8. dapi 基于Django的轻量级测试平台七 怎样部署到生产环境

    QQ群: GitHub:https://github.com/yjlch1016/dapi Nginx+uWSGI 前置条件:以下所有操作均在root账号下面进行如果不是root用户请注意权限问题因为 ...

  9. dapi 基于Django的轻量级测试平台八 Docker部署

    QQ群: GitHub:https://github.com/yjlch1016/dapi 采用Docker+Supervisor+Nginx+uWSGI+Django 一.Dockerfile文件: ...

随机推荐

  1. MyBatis从入门到精通(第9章):Spring集成MyBatis(上)

    MyBatis从入门到精通(第9章):Spring集成MyBatis(上) Spring是一个为了解决企业级Web应用开发过程中面临的复杂性,而被创建的一个非常流行的轻量级框架. mybatis-sp ...

  2. IT架构的本质--阅读笔记01

    万物都有其本质,也只有了解了事物的本质之后,才不至于出现在事物稍作改变时就难以应对的情况,作为软件工程专业的学生,我们应该对IT架构的本质有一定的了解.“老僧三十年前未参禅时,见山是山,见水是水.及至 ...

  3. Tarjan算法:求解无向连通图图的割点(关节点)与桥(割边)

    1. 割点与连通度 在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点(Articulation Point).一个没有关节点的 ...

  4. kaggle——分销商产品未来销售情况预测

    分销商产品未来销售情况预测 介绍 前面的几个实验中,都是根据提供的数据特征来构建模型,也就是说,数据集中会含有许多的特征列.本次将会介绍如何去处理另一种常见的数据,即时间序列数据.具体来说就是如何根据 ...

  5. CPA-计划

    平时周一到周五上班晚上8点到12点,周末6-8个小时,然后没有节假日,一次差不多可以3.4科 审计 看150页  3小时,看完,做题 2天时间,5门课程,12小时考试,没想到能完整地挺过来.感觉税法战 ...

  6. Xshell便捷设置实现linux复制粘贴

    说明:在window系统中,Ctrl+C是复制的快捷键,Ctrl+V是粘贴的快捷键,但在xshell中,Ctrl+C 代表着中断当前指令. 解决办法如下: 1.  使用xshell中的复制粘贴快捷键复 ...

  7. flash插件的安装——网页视频无法播放

    1.从官网下载Adobe flash player 安装包.官方网址:https://get.adobe.com/cn/flashplayer/ 或者从我的网盘下载:链接:https://pan.ba ...

  8. HDU-1828 Picture(扫描线 求矩形并的周长)

    http://acm.hdu.edu.cn/showproblem.php?pid=1828 Time Limit: 6000/2000 MS (Java/Others)    Memory Limi ...

  9. Linux常用指令(三)

    进入京东运维组实习,收到了很多同事的热心指导,自己也努力学习,按照他们给出的学习计划,真的很充实,学到了很多不只是开发方面的知识. 以下简单记录下自己的笔记,方便以后查阅. 1.文件系统 Linux系 ...

  10. 30)PHP,引用对象和克隆对象的区别

    复制文件.建立快捷方式的区别,克隆就是复制,引用就是快捷方式,引用的对象实际上同一个东西,修改任何一个变量,另外一个也会跟着变化.