本文章默认用户使用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. UVA 10801 多线程最短路

    题意:一栋摩天大楼从0层到K层,有N部电梯,每个电梯都有自己的运行速度,此外,对于某个电梯来说,并不是每一层都会停,允许在某一层进行电梯换乘,每次换乘固定消耗60秒,最终求从0层去K层的最短时间,如果 ...

  2. python函数中的参数(关键字参数,默认参数,位置参数,不定长参数)

    默认参数:定义函数的时候给定变量一个默认值. def num(age=1): 位置参数:调用函数的时候根据定义函数时的形参位置和实参位置进行引用. 关键字参数:如果定义的函数中含有关键字参数,调用函数 ...

  3. Unity3D事件函数的执行顺序

    In Unity scripting, there are a number of event functions that get executed in a predetermined order ...

  4. 进程同步multiprocess.Lock

    进程同步multiprocess.Lock 我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制.尽管并发编程让我们能更加充分的利用IO ...

  5. eureka学习之二:自我保护机制

    提供者和消费者:消费者通过注册服务名称,找rpc远程地址,调用提供者的接口 Eureka的自我保护机制:

  6. 计量经济与时间序列_ACF与PACF标准差(均标准误)的计算(含代码)

    1   我们对于acf和pacf值计算完毕之后,在需要计算两个数值的标准差. 2   acf和pacf的标准差计算略有不同.acf的标准差是一个移动过程,而pacf是一个相对固定过程. 3   我们继 ...

  7. [原]UEFI+GPT启动VHD

    1. 缘起 2. 创建VHD文件并写入系统镜像到VHD文件 2.1 制作VHD文件 2.1.1 纯界面创建 2.1.2 命令行创建 2.2 把系统镜像写入VHD文件 3. 添加VHD文件到系统引导 3 ...

  8. elasticsearch-hadoop 扩展定制 官方包以支持 update upsert doc

    官方源码地址https://github.com/elastic/elasticsearch-hadoop 相关文档 https://www.elastic.co/guide/en/elasticse ...

  9. Opencv笔记(十一)——图像模糊(平滑)

    学习目标: 使用自定义的滤波器对图像进行卷积(2D 卷积) 学习使用不同的低通滤波器对图像进行模糊 一.2D卷积 卷积不是很了解的可以看我上一篇博客,与语音信号一样,我们也可以对 2D 图像实施低通滤 ...

  10. Web 手工测试

    day 1 学习目标: 熟练搭建本地测试环境 掌握熟悉项目的步骤和内容 掌握项目基本的测试流程 基础环境介绍: 项目环境的组成部分: 操作系统 windows win7 win10 Linux Cen ...