1. 模型编写
根据项目需求分析和数据库设计,我们创建项目应用,测试计划应用,测试报告应用,bug应用:
django-admin startapp projects
django-admin startapp testplans
django-admin startapp reports
django-admin startapp bugs
注意在配置文件中注册应用。然后分别创建对应的模型如下:
(1)项目应用模型
from django.db import models
from utils.models import BaseModel
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)
def __str__(self):
return self.name
class Meta:
db_table = 'project'
verbose_name = "项目表"
verbose_name_plural = verbose_name
class TestEnv(models.Model):
"""测试环境表"""
name = models.CharField(max_length=150, help_text='环境名称', verbose_name='环境名称')
# 一个项目有多个测试环境,一个环境只属于一个项目
# models.CASCADE 删除项目的时候,就把相关环境同步删除。
project = models.ForeignKey(Project, on_delete=models.CASCADE, help_text='项目id', verbose_name='项目id',
related_name='test_envs') # 反向字段名
global_variable = models.JSONField(help_text='全局变量', verbose_name='全局变量', default=dict, null=True,
blank=True)
debug_global_variable = models.JSONField(help_text='debug模式全局变量', verbose_name='debug模式全局变量',
default=dict, null=True, blank=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, 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_test_env'
verbose_name = "测试环境表"
verbose_name_plural = verbose_name
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
(2)bug应用模型
from django.db import models
class Bug(models.Model):
"""Bug模型"""
CHOICES = [
('未处理', '未处理'),
('处理中', '处理中'),
('处理完', '处理完'),
('无效bug', '无效bug'),
]
# 项目
project = models.ForeignKey('projects.Project', on_delete=models.CASCADE, help_text='项目id', verbose_name='项目id')
# 接口
interface = models.ForeignKey('projects.Interface', on_delete=models.CASCADE, verbose_name='接口', help_text='接口')
# bug描述
desc = models.TextField(verbose_name='bug描述', help_text='bug描述', max_length=3000, blank=True)
# bug基本信息 (请求头 请求体 请求方式 响应结果)
info = models.JSONField(verbose_name='bug信息', help_text='bug信息', default=dict, blank=True)
# bug状态 (待提交,已提交,处理中,关闭,无效bug)
status = models.CharField(verbose_name='bug状态', help_text='bug状态', max_length=40, choices=CHOICES,
default='未处理')
# bug提交者
user = models.CharField(verbose_name='提交者', help_text='提交者', max_length=40, default='', blank=True)
create_time = models.DateTimeField(verbose_name='创建时间', help_text='创建时间', auto_now_add=True)
class Meta:
db_table = 'tb_bug'
verbose_name = 'bug表'
verbose_name_plural = verbose_name
def __str__(self):
return self.id
class BugHandle(models.Model):
"""bug操作记录表"""
bug = models.ForeignKey('Bug', on_delete=models.CASCADE, verbose_name='bug_id', help_text='bug_id')
create_time = models.DateTimeField(verbose_name='创建时间', help_text='创建时间', auto_now_add=True)
handle = models.TextField(verbose_name='处理操作', help_text='处理操作', blank=True)
update_user = models.CharField(verbose_name='更新用户', help_text='更新用户', max_length=32, blank=True)
class Meta:
db_table = 'tb_bug_handle'
verbose_name = 'bug操作记录表'
verbose_name_plural = verbose_name
(3)测试报告应用模型
from django.db import models
class Record(models.Model):
"""运行记录表"""
create_time = models.DateTimeField(verbose_name='创建时间', help_text='创建时间', auto_now_add=True)
plan = models.ForeignKey('testplans.TestPlan', help_text='执行计划', verbose_name='执行计划',
on_delete=models.PROTECT, related_name='records')
all = models.IntegerField(help_text='用例总数', verbose_name='用例总数', blank=True, default=0)
success = models.IntegerField(help_text='成功用例', verbose_name='成功用例', blank=True, default=0)
fail = models.IntegerField(help_text='失败用例', verbose_name='失败用例', blank=True, default=0)
error = models.IntegerField(help_text='错误用例', verbose_name='错误用例', blank=True, default=0)
pass_rate = models.CharField(help_text='执行通过率', verbose_name='执行通过率', max_length=100, blank=True,
default=0)
tester = models.CharField(help_text='执行者', verbose_name='执行者', max_length=100, blank=True)
test_env = models.ForeignKey('projects.TestEnv', help_text='测试环境', verbose_name='测试环境',
on_delete=models.PROTECT)
statue = models.CharField(help_text='执行状态', verbose_name='执行状态', max_length=100)
def __str__(self):
return str(self.id)
class Meta:
db_table = 'tb_record'
verbose_name = '运行记录表'
verbose_name_plural = verbose_name
class Report(models.Model):
"""测试报告"""
info = models.JSONField(help_text='测试报告', verbose_name='测试报告', default=dict, blank=True)
record = models.OneToOneField('Record', help_text='测试记录', verbose_name='测试记录 ', on_delete=models.PROTECT)
def __str__(self):
return str(self.id)
class Meta:
db_table = 'tb_report'
verbose_name = '报告表'
verbose_name_plural = verbose_name
(4)测试计划应用模型
from django.db import models
class TestPlan(models.Model):
"""测试计划"""
create_time = models.DateTimeField(verbose_name='创建时间', help_text='创建时间', auto_now_add=True)
name = models.CharField(verbose_name='计划名', help_text='计划名', max_length=150)
project = models.ForeignKey('projects.Project', verbose_name='项目id', help_text='项目id', on_delete=models.CASCADE,
related_name='test_plan')
scenes = models.ManyToManyField('TestScene', help_text='包含的测试场景', verbose_name='包含的测试场景', blank=True)
def __str__(self):
return self.name
class Meta:
db_table = 'tb_test_plan'
verbose_name = '测试计划表'
verbose_name_plural = verbose_name
class TestScene(models.Model):
"""测试场景/测试套件"""
# 一系列有顺序的测试用例
project = models.ForeignKey('projects.Project', help_text='所属项目', verbose_name='项目名称',
on_delete=models.PROTECT, related_name='test_scenes')
name = models.CharField(max_length=50, help_text='测试场景名', verbose_name='测试场景名')
def __str__(self):
return self.name
class Meta:
db_table = 'tb_test_scene'
verbose_name = "测试场景"
verbose_name_plural = verbose_name
class SceneData(models.Model):
"""场景/套件数据"""
step = models.ForeignKey('TestStep', help_text='步骤', verbose_name='步骤',
on_delete=models.PROTECT)
scene = models.ForeignKey(TestScene, help_text='场景', verbose_name='场景', on_delete=models.PROTECT)
sort = models.IntegerField(help_text='执行顺序', verbose_name='执行顺序',
blank=True)
def __str__(self):
return str(self.id)
class Meta:
db_table = 'tb_scene_data'
verbose_name = "场景步骤"
verbose_name_plural = verbose_name
setup_script = """
# 前置脚本(python):
# global_tools:全局工具函数
# data:用例数据
# env: 局部环境
# ENV: 全局环境
# db: 数据库操作对象
"""
teardown_script = """
# 后置脚本(python):
# global_tools:全局工具函数
# data:用例数据
# response:响应对象response
# env: 局部环境
# ENV: 全局环境
# db: 数据库操作对象
"""
class TestStep(models.Model):
"""用例表"""
title = models.CharField(max_length=50, help_text='用例名称', verbose_name='用例名')
interface = models.ForeignKey('projects.Interface', on_delete=models.CASCADE, help_text='接口', verbose_name='接口')
headers = models.JSONField(help_text='请求头', verbose_name='请求头', default=dict, blank=True)
request = models.JSONField(help_text='请求信息', verbose_name='请求信息', default=dict, blank=True)
file = models.JSONField(help_text='上传的文件参数', verbose_name='上传的文件', default=list, blank=True)
setup_script = models.TextField(help_text='前置脚本', verbose_name='前置脚本', default=setup_script, blank=True)
teardown_script = models.TextField(help_text='后置脚本', verbose_name='用例后置脚本 ', default=teardown_script,
blank=True)
def __str__(self):
return self.title
class Meta:
db_table = 'tb_test_step'
verbose_name = "测试步骤表"
verbose_name_plural = verbose_name
二、项目管理接口设计
(1)项目创建
接口名称: /projects/
请求方式: POST
参数格式: JSON
请求参数:
参数 | 变量名 | 类型 | 说明 | 是否必传 |
项目名称 | name | 字符串 | 项目名称 | 是 |
项目负责人 | leader | 字符串 | 项目负责人 | 是 |
请求示例:
json格式参数
{
"name": "前程贷",
"leader": "心蓝"
}
返回示例
响应状态码:201 响应数据:
{
"id": 1,
"create_time": "2022-07-12T21:59:57.606147+08:00",
"name": "ck12接口自动化",
"leader": "心蓝"
}
(2)删除项目
接口名称: /projects/项目ID/
请求方式: DELETE
参数格式: 路径参数
请求参数:无
返回示例
响应状态码:204
响应数据:无
(3)修改项目
接口名称: /projects/项目ID/
请求方式: PUT/PATCH
参数格式: JSON
请求参数:
参数 | 变量名 | 类型 | 说明 | 是否必传 |
项目名称 | name | 字符串 | 项目名称 | PUT请求必传 |
项目负责人 | leader | 字符串 | 项目负责人 | PUT请求必传 |
请求示例:
json格式参数
{
"name": "前程贷",
"leader": "心蓝"
}
返回示例
响应状态码:200 响应数据:
{
"id": 1,
"create_time": "2022-07-12T21:59:57.606147+08:00",
"name": "ck12接口自动化",
"leader": "心蓝"
}
(4)查看项目列表
接口名称:
请求方式: GET
参数格式: 无
请求参数: 无
返回示例
响应状态码:200 响应数据:
[{
id": 1,
"create_time": "2022-07-12T21:59:57.606147+08:00",
"name": "ck12接口自动化",
"leader": "心蓝"
}]
(5)查看项目
接口名称:
请求方式: GET
参数格式: 路径参数
请求参数: 无
返回示例
响应状态码:200 响应数据:
{
"id": 1,
"create_time": "2022-07-12T21:59:57.606147+08:00",
"name": "ck12接口自动化",
"leader": "心蓝",
"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
}
]
(6)后端代码
① 新增模型方法
因为在获取项目详情时需要统计对应的数据,所以我们再项目模型中新增如下方法:
class Project(models.Model):
"""项目表"""
name = models.CharField(verbose_name="项目名", help_text="项目名称", max_length=50)
leader = models.CharField(verbose_name="负责人", help_text="项目负责人", max_length=50, default='')
create_time = models.DateTimeField(verbose_name="创建时间", help_text="", auto_now_add=True)
def __str__(self):
return self.name
class Meta:
db_table = "project"
verbose_name = "项目表"
verbose_name_plural = verbose_name
def info(self):
"""返回项目的统计信息"""
return [
{'name': '执行环境', 'value': self.test_envs.count()},
{'name': '测试场景', 'value': self.test_scenes.count()},
{'name': '测试计划', 'value': self.test_plan.count()},
{'name': '接口数量', 'value': self.interfaces.count()},
{'name': '定时任务', 'value': 0},
{'name': '执行记录', 'value': Record.objects.filter(plan__name=self.name).count()},
]
def bugs(self):
"""返回bugs的统计信息"""
return [
{'name': '未处理', 'value': self.bug_set.filter(status='未处理').count()},
{'name': '处理中', 'value': self.bug_set.filter(status='处理中').count()},
{'name': '处理完', 'value': self.bug_set.filter(status='处理完').count()},
{'name': '无效bug', 'value': self.bug_set.filter(status='无效bug').count()},
]
② 序列化器
在projects应用中创建serializers.py 模块,编写代码如下:
class ProjectSerializer(serializers.ModelSerializer):
"""项目序列化器"""
# 注意info和bugs是对象方法
class Meta:
model = Project
fields = ['id', 'create_time', 'name', 'leader', 'info', 'bugs']
③ 视图
class ProjectViewSet(ModelViewSet):
"""项目视图集"""
serializer_class = ProjectSerializer
queryset = Project.objects.all()
permission_classes = [IsAuthenticated, IsLeaderOrReadOnly]
# 还可以过滤一下查询集,实现特定的人群看到特定的项目
def get_queryset(self):
return Project.objects.filter(leader=self.request.user)
④ 路由
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register('projects', views.ProjectViewSet)
urlpatterns = router.urls