Django单元测试 相关知识
前言
本文,旨在说明python Django如何编写单元测试,从“背景”,“测试要求”,“代码编写”,“如何运行”,“检验测试覆盖度” 这几个方面来说明
附上django的官方文档单元测试章节=>这里
背景
python中主要的单元测试框架有以下几种:
unittest
标准库,最出名。django中原生自带的单元测试库就是对unittest对封装
点击这里=>最基本的unittest的属性讲解和编写思路
Django 的默认测试库是 unittest,使用它时,要写的样板文件比较多。
下面的两个库所需的样板文件较少:
这两个库分别是对 pytest 和 nose 库的封装。它们不仅能运行 unittest 式的测试用例,还能测试任何以 test_ 开头的函数(类/目录/模志)等。
剩下比较知名的有:
pytest:第三方模块,第二出名。是unittest的替代(编写更少代码完成相同事),
Doctest:通过在函数最开头,写上类似交互式的语句来测试
Unittest2:第三方模块,是unitest的升级版。对API进行了改善以及更好的诊断语法
测试要求
具体到django各个组件到具体要求:
- 视图: 数据的呈现、数据的修改、自定义的 CBV 方法。
- 数据模型: 数据模型的创建/更新/删除,数据模型的方法,模型管理器的方法。
- 表单:表单方法,clean()、自定义项。
- 验证器:对每一个自定义的验证器编写多个测试方法,确保这些验证器不会对网站数据造成破坏。
- 信号:由于它们的作用比较间接,不进行测试可能会引起困惑。
- 过滤器:由于它们本质是函数,故测试应该较容易。
- 模板 Tag:由于它们能完成任何功能,并且可以接受模板上下文对象,对它们进行测试非常有难度。这也意味着需要对它们进行测试,因为如果不测试可能会存在边界条件问题。
代码编写
推荐做法
- 目录结构
catalog/
/tests/
__init__.py
test_models.py
test_forms.py
test_views.py
- 不要写需要测试的测试代码,意思是测试代码必须简洁明了
- 尽可能测试所有自己编写的代码,不测试的部分是Django 核心包或者第三方包
- 每个测试函数只测试一样事情=>一个单元测试不应该对多个视图、数据模型、表单或者类中的多个方法的行为同时进行测试。它应该只对单个视图、数据模型、表单、函数或者方法进行测试
- 当需要类似但不同数据的测试方法时,可以对代码复制粘贴,然后传递不同的参数
单元测试不应该测试本函数或方法以外的事物。因此在测试过程中不应该访问外部的 API、接收邮件、调用挂钩等。但是要测试的函数很可能会包含外部 API,此时有两种可选方法:
- 一、将单元测试变成集成测试
- 二、使用 Mock 库来模拟外部 API 应答
- 对每个测试都要写明测试目的文档,即便是小小的 docstring 也能帮很大的忙
测试覆盖的基本原则
当新增功能和修复问题后,假设之前的覆盖率是 65%,那么修改后如果测试覆盖率低于 65%,代码就不要合并进来,从而保证的测试的覆盖率。
测试覆盖率缓慢地提高是好的,说明项目的质量一直在改善,绝不可跳跃式的提高。
Django unittest 基本情况
TestCase
大多数测试的最佳基类是 django.test.TestCase。
此测试类在运行测试之前,会单独新建一个测试数据库来进行数据库的操作方面的测试(默认在测试完成后销毁)。并在自己的事务中,运行每个测试函数。
该类还拥有一个测试客户端,您可以使用该客户端,模拟在视图级别与代码交互的用户。
TestCase默认情况(数据库相关)
- 如果用原生的unittest库进行,测试之前(setUp())要先创建测试数据库连接,测试之后将数据库摧毁连接。但是Django框架中的TestCase,它已经帮你实现的一些逻辑方便用于测试,所以我们不需要在setUp和tearDown函数中实现这些逻辑
- TestCase在测试开始时,判断当前连接的数据库是否支持事务特性,如支持,则开启事务操作;在测试结束时,同样判断是否支持事务特性,如支持,执行事务回滚,然后关闭所有链接
- 每次测试会单独新建一个测试数据库来进行数据库的操作方面的测试,默认在测试完成后销毁。
注意:若migrations的文档过多时,那么大量时间将消耗在数据库的创建。django有提供命令使用进行单元测试过后不删除数据库。 这个命令就是: --keepdb - 在测试方法中对数据库进行增删操作,最后都会被清除。也就是说,在test_add中插入的数据,在test_add测试结束后插入的数据会被清除。
- 测试数据库的默认字符集
#在创建测试数据库时,数据库的默认字符集也许不是我们想要的例如latin1。可以通过在数据库配置中指定TEST_CHARSET, TEST_COLLATION 参数,来指定字符集以及排序规则
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxxx',
'USER': 'xxxx',
'PASSWORD': '',
'HOST': 'localhost',
'PORT': '',
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': 'SET default_storage_engine=INNODB',
},
'TEST_CHARSET': 'utf8',
'TEST_COLLATION': 'utf8_general_ci',
},
}
setUp()和setUpTestData()
1.对于每一个测试方法都会将setUp()和tearDown()方法执行一遍。即setUp() 在每个测试函数之前被调用,以设置可能被测试修改的任何对象(每个测试函数,都将获得这些对象的 “新” 版本)2.setUpTestData()和tearDownTestData() 用于类级别设置,在测试运行开始的时侯,会调用一次。您可以使用它来创建一个我们将使用,但不在任何测试中修改的作者对象。
class AuthorModelTest(TestCase): @classmethod
def setUpTestData(cls):
Author.objects.create(first_name='Big', last_name='Bob')
提供适用于Django的功能更强的断言方法
TestCase子类的以test*开头的测试方法使用 Assert 断言来测试条件是真,假或相等(AssertTrue, AssertFalse, AssertEqual)。如果条件评估不如预期,则测试将失败,并将错误报告给控制台。
AssertTrue, AssertFalse, AssertEqual 是 unittest 提供的标准断言。框架中还有其他标准断言,还有 Django 特定的断言,来测试视图是否重定向(assertRedirects),或测试是否已使用特定模板(assertTemplateUsed)
以下是比较有用的断言方法:
- assertRaises()
- assertCountEqual()
- assertDictEqual()
- assertFormError()
- assertContains() 先检测状态 200,然后再检查 response.content
- assertHTMLEqual() 忽略空白符的不同
- assertJSONEqual()
例如:假如要比较两个列表,如果用 assertEqual,那么需要先对列表进行相同的排序操作,然后才能进行比较,而使用assertCountEqual() 则不用这么麻烦。
编码时一些常见场景
测试接口
这文章写得不错,点击这里
例如:通过指定接口测试一个对象的增删改查
# flavors/test_api.py
import json from django.core.urlresolvers import reverse
from django.test import TestCase from flavors.models import Flavor class DjangoRestFrameworkTests(TestCase): def setUp(self):
# 别误会,下面创建的两个对象是用于“查(集合,个体详细)”,“删”
# “增”操作要在测试函数里面通过调用指定url来创建测试
Flavor.objects.get_or_create(title="title1", slug="slug1")
Flavor.objects.get_or_create(title="title2", slug="slug2")
self.create_read_url = reverse("flavor_rest_api")
self.read_update_delete_url = \
reverse("flavor_rest_api", kwargs={"slug": "slug1"}) def test_list(self):
response = self.client.get(self.create_read_url)
self.assertContains(response, "title1")
self.assertContains(response, "title2") def test_detail(self):
response = self.client.get(self.read_update_delete_url)
data = json.loads(response.content)
content = {"id": 1, "title": "title1", "slug": "slug1",
"scoops_remaining": 0}
self.assertEquals(data, content) def test_create(self):
post = {"title": "title3", "slug": "slug3"}
response = self.client.post(self.create_read_url, post)
data = json.loads(response.content)
self.assertEquals(response.status_code, 201)
content = {"id": 3, "title": "title3", "slug": "slug3",
"scoops_remaining": 0}
self.assertEquals(data, content)
self.assertEquals(Flavor.objects.count(), 3) def test_delete(self):
response = self.client.delete(self.read_update_delete_url)
self.assertEquals(response.status_code, 204)
self.assertEquals(Flavor.objects.count(), 1)
settings变量的修改
注意:django单元测试时为了模拟生产环境,会修改settings中的变量,例如, 把DEBUG变量修改为True, 把ALLOWED_HOSTS修改为[*]
若干需要在单元测试时修改setting配置。例如,django在单元测试时会将settings.DEBUG 设置为True, 而我们需要将其设置为False
方式1: 直接在修改
class BaseApiTest(TestCase):
def setUp(self):
#testcase DEBUG = False
settings.DEBUG = False def test_b(self):
self.assertEqual(2, 1+1) def tearDown(self):
pass
方式2: 通过装饰器修改
from django.test.utils import override_settings class BaseTest(TestCase):
def setUp(self):
pass # 利用该装饰器,可以在但个测试函数内修改settings变量, 而不影响
@override_settings(DEBUG=False)
def test_b(self):
self.assertEqual(2, 1+1) def tearDown(self):
pass
Celery异步任务的测试
在代码中几乎肯定是会有celery异步任务,若想对异步任务进行单元测试。可以将CELERY_ALWAYS_EAGER=True, BROKER_BACKEND='memory'
from xxx.celery import app
@app.task(bind=True)
def add(self,x, y):
return x + y class TaskTest(TestCase):
def setUp(self):
settings.CELERY_ALWAYS_EAGER = True def test_add(self):
self.assertEqual(2, add.apply_async((1,1))) def tearDown(self):
pass
通过 Mock 使得单元测试不与外界交互
单元测试不应该测试本函数或方法以外的事物。因此在测试过程中不应该访问外部的 API、接收邮件、调用挂钩等。但是要测试的函数很可能会包含外部 API,此时有两种可选方法:
- 一、将单元测试变成集成测试
- 二、使用 Mock 库来模拟外部 API 应答
使用 Mock 库能非常容易地将某库的功能进行临时修改,从而使它们能返回我们想到的数据。这篇文章写得不错=>这里
如何运行
# 测试整一个工程
$ ./manage.py test # 只测试某个应用,并且指定不要销毁测试数据库
$ ./manage.py test app --keepdb # 只测试一个Case
$ ./manage.py test animals.tests.StudentTestCase # 只测试一个方法
$ ./manage.py test animals.tests.StudentTestCase.test_add #显示更多测试信息节
#如果您想获得有关测试运行的更多信息,可以更改详细程度。例如,要列出测试成功和失败(以及有关如何设置测试数据库的大量信息),您可以将详细程度设置为 “2”,如下所示:
python3 manage.py test --verbosity 2
# 允许的详细级别为 0, 1 ,2 和 3,默认值为 “1”
#注:pycharm中,TestCase类左边有个箭头,点击即可测试。
如果要运行测试的子集,可以通过指定包,模块,TestCase子类或方法的完整路径(包含点)来执行此操作
例如:
- 目录结构
catalog/
/tests/
__init__.py
test_models.py
test_forms.py
test_views.py
python3 manage.py test catalog.tests # Run the specified module
python3 manage.py test catalog.tests.test_models # Run the specified module
python3 manage.py test catalog.tests.test_models.YourTestClass # Run the specified class
python3 manage.py test catalog.tests.test_models.YourTestClass.test_one_plus_one_equals_two # Run the specified method
测试覆盖度工具
测试覆盖度即是对开发人员进行督促的工具,也是用来评估项目状态的度量。牢记我们只需对自己的代码进行测试,不对 Django 和第三方包进行测试。
使用coverage工具运行测试并生成覆盖报告,coverage工具使用=>这里
在 <project_root> 目录下,运行:
$ coverage run manage.py test --settings=twoscoops.settings.test
可能会返回:
Creating test database for alias "default"...
..
-----------------------------------------------
Ran 2 tests in 0.008s
OK
Destroying test database for alias "default"...
通过这种方式,我们只对自己的代码进行测试。
生成报告!
coverage.py 还能生成 HTML 格式的报告。在 <project_root> 目录下,运行:
$ coverage html --omit="admin.py"
之后在当前目录下会生成一个新的目录 htmlcov/,可以在目录下打开 index.html文件。点击里面的各模块列表,其中的红色部分是不好的。
集成测试
集成测试是将单独的模块组合成一个整体进行测试,它在单元测试之后进行。集成测试的例子有:
- 使用 Selenium 测试应用是否能在浏览器中正确运行
- 与第三方 API 进行真实测试
- 与 requestb.in 或 httpbin 交互来确认出站请求的有效性
- 使用 runscope.com 来确保 API 能正常运行
集成测试的缺点:
- 设置集成测试要花很多时间
- 运行速度非常慢。因此它是对整个系统进行测试
- 有错误抛出时,很难定位。例如,一个只对某类浏览器有影响的错误可能是由数据库层的 Unicode 转换引起的
- 相比单元测试更脆弱。某个组件中的一个小修改都可能会破坏它。
Django单元测试 相关知识的更多相关文章
- Django 开发相关知识 整理
前言 前端ajax HTTP请求头 ajax上传文件 jsonp跨域 URL 设计 分发 url参数编码 反向生成url 视图 request对象 POST url信息 视图返回值 HttpRespo ...
- 使用Nginx+uwsgi在亚马逊云服务器上部署python+django项目完整版(二)——部署配置及相关知识
---恢复内容开始--- 一.前提: 1.django项目文件已放置在云服务器上,配置好运行环境,可正常运行 2.云服务器可正常连接 二.相关知识 1.python manage.py runserv ...
- Django【第28篇】:Django Admin的相关知识
Django Admin的相关知识 一.面向对象复习 1.类的继承 class Base(object): def __init__(self,val): self.val = val def fun ...
- 【Python五篇慢慢弹(5)】类的继承案例解析,python相关知识延伸
类的继承案例解析,python相关知识延伸 作者:白宁超 2016年10月10日22:36:57 摘要:继<快速上手学python>一文之后,笔者又将python官方文档认真学习下.官方给 ...
- Django Models相关
Models的相关知识 1. AutoField:自增整数类型.根据 ID 自增长的 Int字段 2. IntegerField:整数类型 3. BigIntegerField:大整数类型.用于数值较 ...
- web聊天相关知识
http相关知识 http是无状态,请求,响应模式的通信模式,就是用户每次通过浏览器点击一下页面,都需要重新与web服务器建立一下连接,且发送自己的 session id 给服务器端以使服务器端验证此 ...
- Django 资源 与 知识 Django中自建脚本并使用Django环境 model中的save()方法说明 filter()用法
Django 资源 与 知识 Django中自建脚本并使用Django环境 model中的save()方法说明 filter()用法 2018/11/06 Chenxin 资料说明 Django基础入 ...
- Django 单元测试笔记
引言 关于单元测试的基本知识这里不再讲述,简单一句话:单元测试是用一段代码去测试另一段代码.最常用的框架是unittest,这是python的单元测试框架,而django单元测试框架test.Test ...
- 移动WEB像素相关知识
了解移动web像素的知识,主要是为了切图时心中有数.本文主要围绕一个问题:怎样根据设备厂商提供的屏幕尺寸和物理像素得到我们切图需要的逻辑像素?围绕这个问题以iphone5为例讲解涉及到的web像素相关 ...
随机推荐
- 关于Server2008 R2日志的查看
Server 2008 r2通过 系统事件查看器 分析日志: 查看 系统 事件: 事件ID号: 审计目录服务访问 4934 - Active Directory 对象的属性被复制 4935 -复制失败 ...
- React Native实战一
效果图如下所示: 展示列表页面,点击跳转至详情页面: /** * Sample React Native App * https://github.com/facebook/react-nat ...
- jquery中对地址中的中文进行encodeURI编码
传递参数:<script type="text/javascript"> var id= 'abc'; //字符串英文 var num = 998; ...
- VMware workstation安装Windows Server 2012 R2步骤详解(附下载链接)
话不多说,直接上链接.所需工具: 1.VMware workstation 14.0(版本无所谓) 附链接:https://pan.baidu.com/s/1CrH ...
- 【图像处理】FFmpeg解码H264及swscale缩放详解
http://blog.csdn.net/gubenpeiyuan/article/details/19548019 主题 FFmpeg 本文概要: 本文介绍著名开源音视频编解码库ffmpeg如何 ...
- Centos 7 忘记密码的情况下,修改root密码
应用场景 linux管理员忘记root密码,需要进行找回操作. 注意事项:本文基于centos7.4环境进行操作,由于centos的版本是有差异的,继续之前请确定好版本 操作步骤 一.重启系统,在开机 ...
- 某某网站PHP
在网站域名后输入:e/tool/gbook/?bid=1并回车,这样就打开了“帝国”CMS的留言功能.触发漏洞的步骤为: Step1.在“姓名”处输入:縗 Step2.在“联系邮箱”处输入:,1,1, ...
- Redis二进制反转算法分析
在 redis 源码中 dictScan 算法中用到了用到了非常经典的二进制反转算法,该算法对二进制的反转高效而实用,同时对于理解位运算也有非常大的帮助.先呈现源码: /* Function to r ...
- 从ftp服务器进行批量下载,处理文件名保存时重名的问题,更改重名文件名方式为给后面加1、2、3等数字,保持后缀不变
公司最近有一个从ftp批量下载文件的需求,但是文件名重复总会报错 没办法,自己下班后写了一个小算法 仿照桶排序的原理,实现了这个小功能,直接上代码: String[] test = {"ha ...
- [转帖]Zookeeper vs etcd vs Consul比较
Zookeeper vs etcd vs Consul比较 https://it.baiked.com/consul/2341.html 需要转型 加强学习. 如果使用预定义的端口,服务越多,发生冲突 ...