这里写目录标题
一、项目应用
1、项目包含接口:
- 项目管理接口
- 接口管理接口
- 环境管理接口
2、创建子应用
python
python manage.py startapp projects
3、项目模块设计
a、模型类设计
python
from django.db import models
# Create your models here.
class Project(models.Model):
"""项目表"""
name = models.CharField(max_length=50, help_text='项目名称', verbose_name='项目名')
leader = models.CharField(max_length=50, help_text='负责人', verbose_name='负责人', default='')
create_time = models.DateTimeField(verbose_name='创建时间', help_text='创建时间', auto_now_add=True)
class Meta:
db_table = 'tb_project'
verbose_name = '项目表'
verbose_name_plural = verbose_name
def _str__(self):
return self.name
b、序列化器类设计
python
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ['id', 'name', 'leader', 'create_time']
c、视图类设计
python
class ProjectViewSet(ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
获取项目的详情数据
需要展示字段:
【基本信息】执行环境、执行场景、测试计划、接口数量、定时任务、执行记录
【bug统计】未处理bug、处理中bug、处理完bug、无效bug
【执行记录】执行时间、执行人、执行环境、测试计划、总用例、通过数、测试报告
方法一:需要重写retrieve方法
python
class ProjectViewSet(ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
# permission_classes = [IsAuthenticated]
def retrieve(self, request, *args, **kwargs):
response = super().retrieve(self, request, *args, **kwargs)
obj = self.get_object()
response.data['info'] = [
{'name': '执行环境', 'value': TestEnv.objects.filter(project_id=obj.id).count()},
{'name': '执行场景', 'value': TestScene.objects.filter(project_id=obj.id).count()},
{'name': '测试计划', 'value': TestPlan.objects.filter(project_id=obj.id).count()},
{'name': '接口数量', 'value': Interface.objects.filter(project_id=obj.id).count()},
{'name': '定时任务', 'value': 0}, # todo 定时任务未开发
{'name': '执行记录', 'value': Record.objects.filter(plan__project=obj).count()}
]
return response
方法二:模型类中定义新的字段(重点)
模型类
python
from django.db import models
# Create your models here.
from reports.models import Record
class Project(models.Model):
"""项目表"""
name = models.CharField(max_length=50, unique=True, help_text='项目名称', verbose_name='项目名')
leader = models.CharField(max_length=50, help_text='负责人', verbose_name='负责人', default='')
create_time = models.DateTimeField(verbose_name='创建时间', help_text='创建时间', auto_now_add=True)
class Meta:
db_table = 'tb_project'
verbose_name = '项目表'
verbose_name_plural = verbose_name
def _str__(self):
return self.name
# todo 重要
def info(self):
return [
{'name': '执行环境', 'value': self.test_envs.count()},
{'name': '执行场景', 'value': self.test_scenes.count()},
{'name': '测试计划', 'value': self.test_plans.count()},
{'name': '接口数量', 'value': self.interfaces.count()},
{'name': '定时任务', 'value': 0}, #todo 定时任务未开发
{'name': '执行记录', 'value': Record.objects.filter(plan__project=self).count()}
]
def bugs(self):
return [
{"name":"未处理bug",
"value":self.bug_set.filter(status='未处理').count()},
{"name": "处理中bug",
"value": self.bug_set.filter(status='处理中').count()},
{"name": "处理完bug",
"value": self.bug_set.filter(status='处理完').count()},
{"name": "无效bug",
"value": self.bug_set.filter(status='无效bug').count()},
]
序列化器设计
python
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ['id', 'name', 'leader', 'create_time', 'info', 'bugs']
视图设计
python
class ProjectViewSet(ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
d、项目的增删改查操作
新增项目(http://127.0.0.1:8000/projects/)
python
{
"id": 21,
"name": "872344567",
"leader": "65325346",
"create_time": "2023-08-06T12:18:26.752695+08:00",
"info": [
{
"name": "执行环境",
"value": 0
},
{
"name": "执行场景",
"value": 0
},
{
"name": "测试计划",
"value": 0
},
{
"name": "接口数量",
"value": 0
},
{
"name": "定时任务",
"value": 0
},
{
"name": "执行记录",
"value": 0
}
],
"bugs": [
{
"name": "未处理bug",
"value": 0
},
{
"name": "处理中bug",
"value": 0
},
{
"name": "处理完bug",
"value": 0
},
{
"name": "无效bug",
"value": 0
}
]
}
更新项目(http://127.0.0.1:8000/projects/18/)
删除项目(http://127.0.0.1:8000/projects/17/)
项目的详情信息(http://127.0.0.1:8000/projects/1/)
python
{
"id": 1,
"name": "20230730",
"leader": "巴特勒",
"create_time": "2023-07-30T21:33:50.016376+08:00",
"info": [
{
"name": "执行环境",
"value": 2
},
{
"name": "执行场景",
"value": 3
},
{
"name": "测试计划",
"value": 2
},
{
"name": "接口数量",
"value": 5
},
{
"name": "定时任务",
"value": 0
},
{
"name": "执行记录",
"value": 30
}
],
"bugs": [
{
"name": "未处理bug",
"value": 2
},
{
"name": "处理中bug",
"value": 1
},
{
"name": "处理完bug",
"value": 1
},
{
"name": "无效bug",
"value": 1
}
]
}
疑惑点:为什么extra_kwargs中定义的字段不生效?
extra_kwargs、read_only_fields等字段只对模型类中已经存在的字段生效
python
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ['id', 'name', 'leader', 'create_time', 'info', 'bugs']
# fields = ['id', 'name', 'leader', 'create_time']
extra_kwargs={
"info":{
"read_only":True
},
"bugs": {
"read_only": True
},
}
4、接口模块设计
a、模型类设计
python
class Interface(models.Model):
"""接口表"""
CHOICES = [
('1', '项目接口'),
('2', '外部接口')]
project = models.ForeignKey('Project',
on_delete=models.CASCADE,
help_text='项目id',
verbose_name='项目id',
related_name='interfaces')
name = models.CharField(max_length=50, help_text='接口名称', verbose_name='接口名称')
url = models.CharField(max_length=200, help_text='接口路径', verbose_name='接口路径')
method = models.CharField(max_length=50, help_text='请求方法', verbose_name='请求方法')
type = models.CharField(verbose_name='接口类型',
help_text='接口类型',
max_length=40,
choices=CHOICES,
default='1')
# 扩展,接口的说明,接口字段的参数
def __str__(self):
return self.url
class Meta:
db_table = 'tb_interface'
verbose_name = '接口表'
verbose_name_plural = verbose_name
b、序列化器类设计
python
class NestTestStepSerializer(ModelSerializer):
"""嵌套测试步骤序列化器"""
class Meta:
model = TestStep
fields = ['id', 'title']
class InterfaceSerializer(ModelSerializer):
steps = NestTestStepSerializer(many=True, read_only=True, source='teststep_set')
class Meta:
model = Interface
fields = '__all__'
def validate(self, attrs):
url = attrs.get('url')
type_ = attrs.get('type')
if type_ == '2':
if not url.startswith('http'):
raise serializers.ValidationError('外部接口的url需要完整的url,必须以http或者https开头')
return attrs
获取接口的列表信息时:还需要将接口的下用例信息获取出来
接口表与用例表的关系为一对多,关联关系字段为:interface。
用例数据获取所关联的接口数据用字段:interface
接口数据获取所关联的用例数据用字段:模型类名小写_set(teststep_set)
teststep_set = NestTestStepSerializer(many=True, read_only=True)。
当采用source时,又一种新的编写方法
source指定关联关系字段teststep_set;展示的字段可以随意定义
steps = NestTestStepSerializer(many=True, read_only=True, source='teststep_set')
c、视图类设计
python
class InterfaceViewSet(ModelViewSet):
queryset = Interface.objects.all()
serializer_class = InterfaceSerializer
filterset_fields = ['project', 'type']
d、接口的增删改查查操作
接口的增加
http://127.0.0.1:8000/interfaces/
接口的更新
http://127.0.0.1:8000/interfaces/7/
python
{
"id": 7,
"steps": [],
"name": "0806接口",
"url": "/interfaces/",
"method": "GET",
"type": "1",
"project": 1
}
接口的删除
http://127.0.0.1:8000/interfaces/9/
接口的查询
http://127.0.0.1:8000/interfaces/?project=1
过滤字段格式:?字段名=字段值
字段名必须是当前模型类中存在的字段
filterset_fields = ['project', 'type']
python
[
{
"id": 1,
"steps": [
{
"id": 1,
"title": "登录成功"
},
{
"id": 8,
"title": "登录成功1"
}
],
"name": "登录",
"url": "/users/login/",
"method": "POST",
"type": "1",
"project": 1
},
{
"id": 2,
"steps": [],
"name": "wai1",
"url": "https://www.baidu.com",
"method": "POST",
"type": "2",
"project": 1
},
{
"id": 3,
"steps": [
{
"id": 3,
"title": "查看项目列表信息"
}
],
"name": "查看项目列表",
"url": "/projects/",
"method": "GET",
"type": "1",
"project": 1
},
{
"id": 5,
"steps": [
{
"id": 7,
"title": "注册-正向用例"
}
],
"name": "注册",
"url": "/users/register/",
"method": "POST",
"type": "1",
"project": 1
},
{
"id": 6,
"steps": [
{
"id": 9,
"title": "创建项目正向用例"
}
],
"name": "创建项目",
"url": "/projects/",
"method": "POST",
"type": "1",
"project": 1
}
]
5、环境模块设计
a、模型类设计
python
class TestEnv(models.Model):
"""测试坏境表"""
name = models.CharField(max_length=150, help_text='环境名称', verbose_name='环境名称')
project = models.ForeignKey(Project,
on_delete=models.CASCADE,
help_text='项目id',
verbose_name='项目id')
global_variable = models.JSONField(help_text='全局变量',
verbose_name='全局变量',
default=dict,
null=True)
debug_global_variable = models.JSONField(help_text='debug模式全局变量',
verbose_name="debug模式全局变量",
default=dict,
null=True)
db = models.JSONField(help_text='数据库配置',
verbose_name='数据库配置',
default=list,
null=True,blank=True)
host = models.CharField(help_text='base_url地址',
verbose_name='base_url地址',
max_length=100,
null=True,blank=True)
headers = models.JSONField(help_text='请求头',
verbose_name='请求头',
default=dict,
null=True, blank=True)
global_func = models.TextField(help_text='全局工具函数',
verbose_name='全局工具函数',
default=open('./utils/global_func.py', 'r',
encoding='utf-8').read(),
null=True,blank=True)
def __str__(self):
return self.name
class Meta:
db_table = 'tb_testenv'
verbose_name = '接口表'
verbose_name_plural = verbose_name
b、序列化器类设计
python
class EnvSerializer(serializers.ModelSerializer):
class Meta:
model = TestEnv
fields = '__all__'
c、视图类设计
python
class EnvViewSet(ModelViewSet):
queryset = TestEnv.objects.all()
serializer_class = EnvSerializer
filterset_fields = ['project']
d、环境的增删改查操作
环境的增加
http://127.0.0.1:8000/test_envs/
python
{
"id": 4,
"name": "New Env",
"global_variable": {},
"debug_global_variable": {},
"db": [],
"host": null,
"headers": {},
"global_func": "\"\"\"\n自定义全局工具函数\n============================\n\"\"\"\nfrom apitestengine.core.tools import *\n",
"project": 1
}
环境的更新
http://127.0.0.1:8000/test_envs/3/
python
{
"id": 3,
"name": "项目1环境",
"global_variable": {
"a": 1
},
"debug_global_variable": {},
"db": [],
"host": "127.0.0.1:8000",
"headers": {
"authorization": "application/json"
},
"global_func": "\"\"\"\n自定义全局工具函数\n============================\n\"\"\"\nfrom apitestengine.core.tools import *\n\ndef test_name():\n return '666'",
"project": 1
}
环境的删除
http://127.0.0.1:8000/test_envs/2/
环境的查询
http://127.0.0.1:8000/test_envs/?project=1
过滤字段格式:?字段名=字段值
字段名必须是当前模型类中存在的字段
filterset_fields = ['project']
python
[
{
"id": 1,
"name": "env1",
"global_variable": {},
"debug_global_variable": {
"host": "http://127.0.0.1:8000",
"headers": {},
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImppbWkiLCJleHAiOjE2OTEzMjkwOTAsImVtYWlsIjoiMTFAcXEuY29tIn0.bm_b2Mj85JBvkAx0Ao02AFzFpVBizraZaC-_vC25IOA",
"username": "王帅"
},
"db": [
{
"name": "db1",
"type": "mysql",
"config": {
"user": "root",
"password": "root",
"host": "82.156.178.247",
"port": 8848,
"autocommit": true,
"database": "ck14_database"
}
}
],
"host": "http://127.0.0.1:8000",
"headers": {},
"global_func": "\"\"\"\n自定义全局工具函数\n\n\"\"\"\n\nfrom apitestengine.core.tools import *\n\n\n# def random_test_name():\n# return 'test_'+fk.pystr()\n\ndef random_date():\n \"\"\"随机生成一个日期\"\"\"\n return fk.date()\nprint(random_date())\n\n# print('random_test_name的值为:',random_test_name())\n\n# from faker import Faker\n# fk = Faker(locale='zh_CN')\n\ndef test_username():\n return fk.name()\n\n\n# def random_city()\n# return fk.city()\n \n# print(random_city())",
"project": 1
},
{
"id": 3,
"name": "项目1环境",
"global_variable": {
"a": 1
},
"debug_global_variable": {},
"db": [],
"host": "127.0.0.1:8000",
"headers": {
"authorization": "application/json"
},
"global_func": "\"\"\"\n自定义全局工具函数\n============================\n\"\"\"\nfrom apitestengine.core.tools import *\n\ndef test_name():\n return '666'",
"project": 1
},
{
"id": 4,
"name": "New Env",
"global_variable": {},
"debug_global_variable": {},
"db": [],
"host": null,
"headers": {},
"global_func": "\"\"\"\n自定义全局工具函数\n============================\n\"\"\"\nfrom apitestengine.core.tools import *\n",
"project": 1
}
]
6、DRF中的通用过滤
除了能够覆盖默认查询集之外,REST 框架还包括对通用过滤后端的支持,允许您轻松构建复杂的搜索和过滤器。
6.1、设置过滤器后端
可以使用设置全局设置默认过滤器后端DEFAULT_FILTER_BACKENDS。例如。
a、要使用DjangoFilterBackend,请先安装django-filter
python
pip install django-filter
注意djoango-filter仅支持:
● Python: 3.6,3.7,3.8
● Django: 2.2, 3.1,3.2
● DRF: 3.10+
b、注册
python
INSTALLED_APPS = [
...
'django_filters',
...
]
c、配置
python
REST_FRAMEWORK = {
...
...
# 过滤
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
}
d、使用
在后端类视图或者视图集中添加filterset_fields属性即可实现指定字段过滤,例如改写环境管理视图集
如下:
python
class EnvViewSet(ModelViewSet):
queryset = TestEnv.objects.all()
serializer_class = EnvSerializer
filterset_fields = ['name','project']
不需要在复写get_queryset方法手动过滤,就可以通过url/test_envs/?project=1访问过滤数据了。
注意:当使用外键字段过滤时,如果级联模式是删除,则使用不存在的数据过滤会返回400响应。例如上面的
视图如果使用不存在的项目id过滤返回结果如下:
python
http://127.0.0.1:8000/test_envs?project=2