Django 单元测试
单元测试框架unittest
unittest是python自带的一个单元测试框架,无需安装,使用简单方便。
unittest中最核心的部分是:TestFixture、TestCase、TestSuite、TestRunner
unittest case的运行流程:
- 写好一个完整的TestCase
- 多个TestCase 由TestLoder被加载到TestSuite里面, TestSuite也可以嵌套TestSuite
- 由TextTestRunner来执行TestSuite,测试的结果保存在TextTestResult中
- TestFixture指的是环境准备和恢复
TestFixture
用于测试环境的准备和恢复还原, 一般用到下面几个函数:
- setUp():每个测试case运行之前运行
- tearDown():每个测试case运行完之后执行
- setUpClass():必须使用@classmethod 装饰器, 所有case运行之前只运行一次
- tearDownClass():必须使用@classmethod装饰器, 所有case运行完之后只运行一次
TestCase
- 执行结果:
成功是 .
,失败是 F,出错是 E,跳过是 S - 测试用例的执行跟方法的顺序没有关系, 默认按字母顺序
- 每个测试方法均以
test 开头
测试model文件:
# -*- coding: utf-8 -*-
import unittest
class TestMy(unittest.TestCase):
def setUp(self):
# 每个测试用例运行之前执行
print 'setUp'
def tearDown(self):
# 每个测试用例运行之后执行
print 'tearDown'
@classmethod
def setUpClass(cls):
# 所有测试用例运行之前执行
print 'setUpClass'
@classmethod
def tearDownClass(cls):
# 所有测试用例运行之后执行
print 'tearDownClass'
def run_test(self):
# 不是以test开头,不是测试用例
print 'run'
def test_run_a(self):
# 测试用例
self.assertEqual(1, 1)
def test_run_b(self):
# 测试用例
self.assertEqual(1, 2)
在单元测试运行完之后,成功会打印一个".",失败会显示断言失败的地方。
Django单元测试
django的单元测试模块是基于unittest编写。我们使用的类名为django.test.TestCase,继承于python的unittest.TestCase。所以它的整个流程跟unittest是完全一致的。
- 不同点:在Django.test模块中定义了Client类用于在测试工程中模拟多种请求的发送。
from django.test import TestCase
import json
class TestAlarm(TestCase):
def test_nic_type(self):
# 测试获取NIC类型接口
r = self.client.get('/api/home/nic_type/')
self.assertEqual(r.status_code, 200)
content = json.loads(r.content)
self.assertEqual(content['result'], True)
Django支持的请求类型
请求类型 | 调用方法 |
---|---|
Post请求 | post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, **extra) |
Get请求 | get(path, data=None, follow=False, secure=False, **extra) |
Put请求 | put(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) |
Patch请求 | patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) |
Delete请求 | delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, **extra) |
常用参数含义:
参数名 | 含义解释 |
---|---|
path | 发送请求使用url |
data | 发送请求时携带的数据 |
content_type | 携带数据的格式 |
secure | 客户端将模拟HTTPS请求 |
follow | 客户端将遵循任何重定向 |
其中:post、put、patch、delete请求方法中content_type为application/json时,如果data为dict,list或tuple类型,则需要使用json.dumps()进行序列化。
请求数据的接收与处理
通过Client对象发出的请求在经过被测试单元的一系列操作之后,返回的数据会以返回值的形式返回。一般情况下,后端返回的数据形式为json格式
,在接收数据后需要调用json方法
进行解析。
import json
def test_nic_type(self):
# 测试获取NIC类型接口
response = self.client.get('/api/home/nic_type/')
self.assertEqual(response.status_code, 200)
content = json.loads(response.content)
其中,response的常用属性有:
- status_code:http状态码
- content: reponse的body,json格式,用json方式进行解析
结果验证
- self.assertEqual(1, 2): 断言1==2
- self.assertTrue(isinstance(content, list)): 断言content的类型是否是list
- self.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False): 断言response是否与status_code和text内容相应。将html设为True会将text作为html处理。
- self.assertJSONEqual(raw, expected_data, msg=None):断言Json片段raw和expected_data是否相当。
mock数据
mock 是辅助单元测试的模块,用于测试不方便调用的别人的接口。
使用:
在python2.x中,mock是一个独立的模块,使用时需要先安装
pip install mock
在python3.3之后,mock已经合并到unittest模块中了,是unittest单元测试的一部分,直接导入过来就行
from unittest import mock
使用举例
需要测试的方法:
def get_os_account(cls, bk_biz_id):
params = {
'bk_biz_id': bk_biz_id
}
accounts = JobApi.get_os_account(params=params)
return accounts
其中JobApi.get_os_account为外部接口,单元测试时无法调用,所以需要使用mock数据,使用方法如下:
import mock
def test_get_os_account(self):
"""
测试方法get_os_account
:return:
"""
JobApi.get_os_account = mock.MagicMock(return_value=Mock_Os)
data = Rules.get_os_account(
bk_biz_id=2
)
self.assertTrue(isinstance(data, dict))
其中Mock_Os为:
Mock_Os = {
"result": True,
"code": 0,
"message": "success",
"data": [
{
"id": 2,
"account": "Administrator",
"creator": "system",
"os": "Windows",
"alias": "Administrator",
"bk_biz_id": 2,
"create_time": "2018-03-22 15:36:31"
}
]
}
数据库
测试是需要数据库的,django会为测试单独生成数据库。不管你的测试是否通过,当你所有的测试都执行过后,这个测试数据库就会被销毁。
- 注意要点: 在执行单元测试的过程中,会Django会生成临时的数据库, 因此,连接数据库的用户需要有创建和删除数据库的权限。
- 默认情况下,测试数据库的名字是test_DATABASE_NAME,DATABASE_NAME是你在settings.py里配置的数据库名;
- 如果你需要给测试数据库一个其他的名字,可以在settings.py中指定TEST_DATABASE_NAME的值
配置测试数据库,在setting.py文件DATABASES中增加TEST字段
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 默认用mysql
'NAME': 'hn_collector', # 数据库名 (默认与APP_ID相同)
'USER': 'root', # 你的数据库user
'PASSWORD': '123456', # 你的数据库password
'HOST': '127.0.0.1', # 开发的时候,使用localhost
'PORT': '3306', # 默认3306
'TEST': {
'NAME': 'data_collector_test',
'CHARSET': 'utf8',
'COLLATION': 'utf8_general_ci'
},
'OPTIONS': {'charset': 'utf8'},
},
}
推荐使用配置数据库,这个可以修改测试数据库的相关配置。否则会比较容易出问题:例如编码问题:中文无法入库等
keepdb
在编写单元测试时,每次重新创建MySQL数据库表都花费很长时间,于是在表结果不变的情况下,想使用keepdb参数
python manage.py test --keepdb
测试数据:fixtures
有一些项目是基于数据库来开发的,如果数据库里没有数据,那么对于一个基于数据库的网站来说,test case并无多大的用处.为了给测试数据库加入测试数据更方便,django提供了载入fixtures的方法.
fixture是一系列的数据集合,django知道如何将它导入数据库。
使用步骤
- 在myapp下创建fixtures文件夹,在文件夹下创建myapp.json文件
- 在json文件中配置
[{
"fields": {
"subnets": "1",
"update_time": "2020-03-13T16:48:15",
"name": "test",
"ch_operator": null,
"scan_time": "2020-03-13T18:31:30",
"edit_user": null,
"is_delete": 0,
"create_user": "wangying",
"create_time": "2020-03-13T16:48:15"
},
"model": "tasks.tasksconfig",
"pk": 3
}, {
"fields": {
"status": 1,
"edit_time": "2020-03-13T16:48:15",
"is_delete": 0,
"create_user": "wangying",
"create_time": "2020-03-13T16:48:15",
"frequency": 60,
"time": "02:00:00",
"day": 5,
"tasksconfig": 3
},
"model": "tasks.cycle",
"pk": 3
}]
- 如果实际数据库中有数据,可以将数据直接拷贝到myapp.json文件中,拷贝命令如下
python manage.py dumpdata myapp >myapp/fixtures/myapp.json
- setting.py中加入:FIXTURE_DIRS = ('/path/to/api/fixtures/',)
- 在测试文件中使用:
class TestHome(BaseTestCase):
fixtures = ['ip_address.json']
def setUp(self):
super(TestHome, self).setUp()
登陆验证
直接将登陆验证mock掉,可以绕过中间件鉴权(不推荐)
@patch('account.middlewares.LoginMiddleware',return_value={'user': 'wy'})
def test_all_sub(self, user):
直接cookie赋值
def setUp(self):
self.client.cookies['bk_token'] = 'bk_token'
Celery 单元测试
异步任务单元测试
简单例子:
@task()
def add():
print '33333'
def task_celery(self, request):
type = request.GET.get('type')
if int(type) == 1:
add.delay()
data = {
'status': 1
}
else:
data = {
'status': 2
}
return Response(data)
单元测试方法:使用CELERY_ALWAYS_EAGER=True
- 装饰器override_settings
from django.test.utils import override_settings
@override_settings(CELERY_ALWAYS_EAGER=True)
def test_add(self):
param = {
'type': 1
}
r = self.client.get('/api/task/task_celery/', param, content_type='application/json')
self.assertEqual(r.status_code, 200)
content = json.loads(r.content)
self.assertEqual(content['result'], True)
- 直接修改settings
from django.conf import settings
def setUp(self):
settings.CELERY_ALWAYS_EAGER = True
如果设置CELERY_ALWAYS_EAGER=True
,则下面两行代码没有区别
add.delay()
add()
文件上传:
只需提供文件字段名称作为键,并提供要上传的文件的文件句柄作为值。
def test_upload_file(self):
"""
测试方法 upload_file
:return:
"""
with open('apps/test_handlers/mock_data/upload_file/ydauto_tomcat.zip') as fb:
r = self.client.post('/api/scripts/', data={'file': fb})
content = json.loads(r.content)
self.assertEqual(r.status_code, 200)
file只是一个键值,可以自己定义
运行单元测试
- 测试整个工程
python manage.py test
- 测试某个应用
python manage.py test app_name
- 只测试一个Case
python manage.py test MyTestCase
- 只测试一个方法
python manage.py test test_func
测试通过:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」