目录

Django-Vue3-前后端分离技术实现自动化测试平台从零到有系列-第三章-之-基础架构搭建

Django + Vue3 前后端分离技术实现自动化测试平台从零到有系列 <第三章> 之 基础架构搭建

1、简介

本章第一部份是后端Django项目按照第二章的架构设计将平台架构目录搭建完成;架构直观的表示在代码目录结构的调整;第二部分是自动化测试功能模块的表设计与创建;这是核心,也会是优先开发的部分。
这两部分组合成了整个项目架构基础,有了他们,后续的开发便顺畅了。

后面所有 模块 == APP(django项目使用 python manage.py startapp 创建的APP),后面常描述 ‘APP’,也可能一时不查写成了’模块’,希望理解

2、开发前准备

2.1、本教程需要的基础:

Python代码编写精通
Django 会写Demo
Vue3 + Element 会写Demo

基础欠些也没有关系,本文会细致的讲解用到的代码,按照一步一步来,也能实现;不懂的可以留言与私聊。

2.2、开发环境准备:

一台安装了下面东西电脑
Mysql 8
Python 3.6 +
Nodejs
Django 4 +
Pycharm
Vscode (开发前端用)

2.3、数据库准备

mysql8新增一个数据库apiauto,备用
字符集:utf8mb4
排序规则:utf8mb4_general_ci

https://i-blog.csdnimg.cn/direct/007583387db94c0ea1f979d2d5ebe16a.png

2.4、前一章节的代码:

3、代码

3.1、回顾前文

下图为<第二章>内容,见图后端分为用户、自动化测试、系统管理三个大模块;项目架构按照三个模块进行模块化;在<第一章>已经新增了 users APP,还须再增加两个APP。

https://i-blog.csdnimg.cn/direct/6244a8b4fc2946f8a1b7d1755bd9368d.png

apiauto/                        # 项目根目录(包含 manage.py 和应用目录)
├─ manage.py                    # Django 管理脚本,项目入口,可运行迁移、启动服务等
├─ apiauto/                     # 项目配置目录(和项目同名)
│   ├─ __init__.py              # Python 包标识(空文件即可)
│   ├─ settings.py              # 全局配置文件(数据库、应用、REST、JWT 等)
│   ├─ urls.py                  # 项目 URL 路由入口,分发到各个 app 的 urls.py
│   ├─ wsgi.py                  # WSGI 启动文件,传统部署(Gunicorn/Uwsgi 用)
│   └─ asgi.py                  # ASGI 启动文件,异步部署(Daphne/Uvicorn 用,支持 WebSocket)
└─ users/                       # 自定义应用(用户模块)
    ├─ __init__.py              # Python 包标识
    ├─ admin.py                 # Django Admin 后台配置(可注册用户模型)
    ├─ apps.py                  # 应用配置,Django 自动识别 app
    ├─ models.py                # 数据模型定义(ORM 对应数据库表)
    ├─ serializers.py           # DRF 序列化器(定义注册、用户信息、修改密码等数据结构)
    ├─ views.py                 # 视图(API 逻辑处理,例如注册、登录、获取用户信息)
    ├─ urls.py                  # 当前 app 的路由(只负责本 app 的接口)
    └─ test.py                  # 单元测试文件(编写测试用例验证功能是否正确)  

明显的感觉他层级不明显,需要更容易明显的区分项目与APP的架构层级

3.2、新架构

在新增自动化测试、系统管理这两个APP前,

其中 users 是APP,与项目配置目录同一层级,为了区分与统一管理APP,在同级目录下增加一个apps目录,apps下放所有APP,users会移动过去。改动后目录如下:

apiauto/                        # 项目根目录(包含 manage.py 和应用目录)
├─ manage.py                    # Django 管理脚本,项目入口,可运行迁移、启动服务等
├─ apiauto/                     # 项目配置目录(和项目同名)
│   ├─ __init__.py              # Python 包标识(空文件即可)
│   ├─ settings.py              # 全局配置文件(数据库、应用、REST、JWT 等)
│   ├─ urls.py                  # 项目 URL 路由入口,分发到各个 app 的 urls.py
│   ├─ wsgi.py                  # WSGI 启动文件,传统部署(Gunicorn/Uwsgi 用)
│   └─ asgi.py                  # ASGI 启动文件,异步部署(Daphne/Uvicorn 用,支持 WebSocket)
└─ apps/                        # APPS集中管理目录   
    ├─ users/                   # 用户模块APP
    │   ├─ __init__.py          # Python 包标识(空文件即可)
	│   ├─ settings.py          # 全局配置文件(数据库、应用、REST、JWT 等)
	│   ├─ urls.py              # 项目 URL 路由入口,分发到各个 app 的 urls.py
	│   ├─ wsgi.py              # WSGI 启动文件,传统部署(Gunicorn/Uwsgi 用)
	│   └─ asgi.py              # ASGI 启动文件,异步部署(Daphne/Uvicorn 用,支持 WebSocket)
    ├─ auto/                    # 自动化模块APP
    ├─ sysmanages/              # 系统管理模块APP
    └─ ....                   

3.2、调整操作

1、在apiauto目录下新增apps目录

apiauto右击—-New—-Directory

https://i-blog.csdnimg.cn/direct/d25bc2c6a0204e4d8afdd43755d30676.png

弹出框中输入:apps,

https://i-blog.csdnimg.cn/direct/05fac2b25a1e463ca50c5079c83897d0.png

按下 回车,创建apps目录如下图

https://i-blog.csdnimg.cn/direct/a24e2accf682453baad2894ed3f2ba6b.png

将users拖过去apps目录,确认即可
https://i-blog.csdnimg.cn/direct/7d16216b34af4fc7abe40bbd2a0b8e7b.png

3.2、Django识别调整后的APP

这里users APP移动了,已经在apps目录下了,项目运行仍会找原因来位置的users,它不存在原位置了,所以运行会开始报错,说:No module named ‘users’
https://i-blog.csdnimg.cn/direct/1434d528d6c747308ce00a9360a62f9f.png
因为APP移动,django找不到这个APP

解决方案很简单,在apiauto目录下的setting.py里增加一行代码:

import os, sys
sys.path.insert(0,  os.path.join(BASE_DIR, 'apps'))

增加到 “BASE_DIR = Path(file).resolve().parent.parent” 这行代码后面
https://i-blog.csdnimg.cn/direct/43b0bf4b006f4315a7abc489c9725b47.png
添加这行后,项目运行成功了。
https://i-blog.csdnimg.cn/direct/b573b7bcd48347848b2636caf40f51e5.png

4、新增APP

4.1、自动化测试APP

现在增加APP需要进入apps目录下增加了
打开Terminal 编辑进,进入apps目录下
https://i-blog.csdnimg.cn/direct/0dd2a94fe66c4dfc935843f0ee150192.png

输入命令:

python ../manage.py startapp auto

https://i-blog.csdnimg.cn/direct/820cc4e03319473fa8ec471d44b8a619.png

auto是自动化测试模块的APP,接下来新增系统管理APP

4.2、系统管理APP

输入命令

python ../manage.py startapp sysmanage

https://i-blog.csdnimg.cn/direct/bb49fe4a09854b309c5dc63075f469d8.png

到这里,整个平台的架构已经初步形成,

users:用户登录与注册、密码修改、三方登录、用户信息等相关的功能;
sysmanage:整个系统的管理,权限、日志、三方接入配置等;
auto: 核心功能,自动化测试的功能,主要有环境、接口、用例、套件、执行计划等的功能;

https://i-blog.csdnimg.cn/direct/d6ffeee7a3f54787af2baf63710a15fd.png
架构设计目录只是一部分,表设计不可或缺。
users模块的表继承了django的用户相的表,不需要设计
sysmanage,它可能会管到各个模块的表,自己未必有表,暂时搁置
下面便开始设计auto模块的表;

5、数据

5.1、表设计

一个完整的接口自动化测试平台,需要以下这些表

项目表:公司不止一个项目,不同项目之间的数据需要隔离
环境配置表:每个项目不止一个环境,一般有SIT、UAT、性能、线上环境
接口树表:树结构可以让人按功能、模块管理对应接口,以Sawwage作参考
用例树表:树结构可以让人按功能、模块管理对应用例
接口表:接口数据,加上mock自段
用例表:用例数据、检查点、执行状态等
前置后置执行步骤表:关联用例表,是用例执行前或后需要执行的脚本方法(关键字),功能强大;
套件表:用例执行集合
执行计划表:可以是套件的集合、定时、统计数据、
用例执行记录表:测试报告
套件执行记录表:统计报告
计划执行记录表:统计报告
前置后置执行记录表:测试报告
自定义脚本表:存放平台用户编写的脚本,编写完成后会生成关键字在用例中选择到

表的ER图设计如下:
https://i-blog.csdnimg.cn/direct/e2b092ac056e4ce6b3ca9239a64b7ec2.jpeg#pic_center

5.2、代码

5.2.1、树递归

通过ER图可以看出,有两个树模型:接口树、用例树。
他们是自已外键自已,等于递归模型;
所以要先做了一个表数据自我递归小工具代码。

问递归是啥?各位如果递归不懂的,可以去力扣或其他的教程中找找答案。

https://i-blog.csdnimg.cn/direct/e2387485e0be4a17966d8fc8389f4973.png
https://i-blog.csdnimg.cn/direct/ed58edafa46440e6b58f9b29ee95c96a.png
在项目根目录下新增一个目录utils
项下添加个mixins.py文件里面增加代码

class TreeMixin:
    """
    给自关联模型提供树形结构功能
    """

    def to_dict(self):
        """
        转换为 dict 节点
        """
        return {
            "id": self.id,
            "name": self.name,
            "children": [child.to_dict() for child in self.children.filter(is_deleted=False)]
        }

    @classmethod
    def build_tree(cls, queryset, parent=None):
        """
        构建树形结构 (JSON 风格),自动过滤 is_deleted=True 的节点
        """
        nodes = queryset.filter(parent=parent, is_deleted=False)
        return [node.to_dict() for node in nodes]

    @classmethod
    def print_tree(cls, queryset, parent=None, indent=0):
        """
        打印缩进树,调试用,自动过滤 is_deleted=True
        """
        nodes = queryset.filter(parent=parent, is_deleted=False)
        for node in nodes:
            print(" " * indent + f"- {node.name}")
            cls.print_tree(queryset, node, indent + 2)

https://i-blog.csdnimg.cn/direct/db7d89ca213b4d2ca3828eb22f69c470.png

这个工具写完了后,后面大用,很多递归模型可以继承他。然后完成模型数据的自我递归,输出序列数据。
下面便开始对ER图的表进行Model化了。

5.2.2、models.py编写

各位可以回看一下ER图,其中每一个表,都有is_delete、created_at 、updated_at 、created_by 等字段;我们可以将这几个字段抽取出来成一个BaseModel,使用abstract = True标识这个Model不会生成表。

models.py文件做什么的?大家可以了解一下Django的ORM相关的知识。 死记住理论后,照搬下面代码生成数据库表进行理解。
死记理论,只记他的运行逻辑顺序便好,不是记ORM有众多关键字哈,关键字了解一下即可。

代码比较长,请诸位结合ER图喝着茶细品~

from django.db import models
# Create your models here.

from django.contrib.auth.models import User
# 引用工具树
from utils.mixins import TreeMixin



class BaseModel(models.Model):
    """
    抽象基类:给所有表提供通用字段
    """
    is_delete = models.BooleanField(default=False, verbose_name="是否删除")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="创建人"
    )

    class Meta:
        abstract = True  # 不会生成表,仅供继承


class Project(BaseModel):
    """
    项目
    """
    pro_name = models.CharField(max_length=100, null=True, unique=True, verbose_name='项目名称,唯一校验')
    description = models.TextField(null=True,verbose_name="项目描述")


    class Meta:
        managed = True
        db_table = 'auto_project'

    def __str__(self):
        return '{id:%d, projectname:%s, description:%s}' \
               % (self.id, str(self.projectname), str(self.description))



class Environment(BaseModel):
    """
    测试环境配置
    """
    name = models.CharField(max_length=50, unique=True, verbose_name="环境名称")
    pro = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="test_env", verbose_name="项目其下环境")
    base_url = models.URLField(verbose_name="基础 URL")
    headers = models.JSONField(blank=True, null=True, verbose_name="公共请求头")
    db_config = models.JSONField(blank=True, null=True, verbose_name="数据库配置")
    env_params = models.JSONField(blank=True, null=True, verbose_name="环境变量配置")

    class Meta:
        db_table = "auto_environment"  # 表名
        verbose_name = "测试环境"
        verbose_name_plural = "测试环境"
        indexes = [
            models.Index(fields=["name"]),  # 添加索引
        ]

    def __str__(self):
        return self.name



class Apinode(BaseModel, TreeMixin):
    """
    接口树,管理接口,此表自我递归,继承TreeMixin的方法
    """
    name = models.CharField(max_length=200, null=False, verbose_name='node名称')
    parent = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name="children")
    pro = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="api_node", verbose_name="项目下节点")


    class Meta:
        managed=True
        db_table = 'auto_api_node'

    def __str__(self):
        return self.name



class API(BaseModel):
    """
    接口信息表
    """
    name = models.CharField(max_length=100, verbose_name="接口名称")
    path = models.CharField(max_length=200, verbose_name="接口路径")
    method = models.CharField(
        max_length=10,
        choices=[("GET", "GET"), ("POST", "POST"), ("PUT", "PUT"), ("DELETE", "DELETE"), ("PATCH", "PATCH")],
        verbose_name="请求方法"
    )
    headers = models.JSONField(blank=True, null=True, verbose_name="请求头")
    params = models.JSONField(blank=True, null=True, verbose_name="Query 参数")
    body = models.JSONField(blank=True, null=True, verbose_name="请求体")
    response = models.JSONField(blank=True, null=True, verbose_name='返回结果')
    node = models.ForeignKey(Apinode, on_delete=models.DO_NOTHING, related_name="test_api", verbose_name="接口树管理接口")
    ismock = models.IntegerField(default=0, null=True, verbose_name='是否mock, 0为否,1为是')

    class Meta:
        db_table = "auto_api"
        verbose_name = "接口"
        verbose_name_plural = "接口"
        indexes = [
            models.Index(fields=["name"]),
            models.Index(fields=["method"]),
        ]

    def __str__(self):
        return f"{self.name} [{self.method}]"

class Casenode(BaseModel, TreeMixin):
    """
    用例树,管理用例,此表自我递归,继承TreeMixin的方法
    """
    name = models.CharField(max_length=200, null=False, verbose_name='node名称')
    parent = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name="children")
    pro = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="case_node", verbose_name="项目下节点")


    class Meta:
        managed=True
        db_table = 'auto_case_node'

    def __str__(self):
        return self.name


class TestCase(BaseModel):
    """
    测试用例
    """
    name = models.CharField(max_length=100, verbose_name="用例名称")
    node = models.ForeignKey(Casenode, on_delete=models.DO_NOTHING, related_name="test_case", verbose_name="接口树管理用例")
    description = models.TextField(blank=True, null=True, verbose_name="用例描述")
    path = models.CharField(max_length=200, verbose_name="接口路径")
    method = models.CharField(
        max_length=10,
        choices=[("GET", "GET"), ("POST", "POST"), ("PUT", "PUT"), ("DELETE", "DELETE"), ("PATCH", "PATCH")],
        verbose_name="请求方法"
    )
    headers = models.JSONField(blank=True, null=True, verbose_name="请求头")
    params = models.JSONField(blank=True, null=True, verbose_name="Query 参数")
    body = models.JSONField(blank=True, null=True, verbose_name="请求体")
    response = models.JSONField(blank=True, null=True, verbose_name='返回结果')
    api = models.ForeignKey(API, on_delete=models.DO_NOTHING, related_name="test_cases", verbose_name="关联接口")
    checkrestype = models.IntegerField(null=True, verbose_name='检查数据类型 0是返回头,1是返回数据,2是接口状态')
    checkmethod = models.CharField(max_length=50, null=True, verbose_name='返回检查方式')
    checkdata = models.CharField(max_length=200, null=True, verbose_name='检查期望')
    execsort = models.IntegerField(null=True, verbose_name='用例执行排序,在新增时通过获取三级节点下的数量自动生成')
    artificial = models.FloatField(null=True, verbose_name='人工执行用时')
    status = models.IntegerField(default=0, null=True, verbose_name='执行状态, 1执行中,2执行完成')

    class Meta:
        db_table = "auto_test_case"
        verbose_name = "测试用例"
        verbose_name_plural = "测试用例"
        indexes = [
            models.Index(fields=["name"]),
        ]

    def __str__(self):
        return self.name


class Casexkey(BaseModel):
    """
    前置、后置执行
    """
    case = models.ForeignKey(TestCase, on_delete=models.PROTECT, null=False, related_name="casex_key", verbose_name='用例的id,外键')
    beaft = models.IntegerField(null=True, verbose_name='前置或后置,0:前置,1:后置')
    method = models.CharField(max_length=200, null=False, verbose_name='执行关键字,执行时会映射到脚本 ')
    result = models.CharField(max_length=200, null=True, verbose_name='变量名称')
    pars = models.IntegerField(null=True, verbose_name='参数数量')
    params1 = models.TextField(null=True, verbose_name='参数1')
    params2 = models.TextField(null=True, verbose_name='参数2')
    params3 = models.TextField(null=True, verbose_name='参数3')
    params4 = models.TextField(null=True, verbose_name='参数4')
    params5 = models.TextField(null=True, verbose_name='参数5')
    params6 = models.TextField(null=True, verbose_name='参数6')
    type1 = models.CharField(max_length=10, null=True, verbose_name='参数一类型')
    type2 = models.CharField(max_length=10, null=True, verbose_name='参数二类型')
    type3 = models.CharField(max_length=10, null=True, verbose_name='参数三类型')
    type4 = models.CharField(max_length=10, null=True, verbose_name='参数四类型')
    type5 = models.CharField(max_length=10, null=True, verbose_name='参数五类型')
    type6 = models.CharField(max_length=10, null=True, verbose_name='参数六类型')



    class Meta:
        managed = True
        db_table = 'auto_casexec_key'

    def __str__(self):
        return '公共方法名称:%s\t' % (self.method)



class TestSuite(BaseModel):
    """
    测试套件:用例集合
    """
    name = models.CharField(max_length=100, verbose_name="套件名称")
    description = models.TextField(blank=True, null=True, verbose_name="套件描述")
    test_cases = models.ManyToManyField(TestCase, related_name="suites", verbose_name="包含用例")
    name = models.CharField(max_length=100, verbose_name="套件名称")
    pro= models.ForeignKey(Project, null=True, on_delete=models.DO_NOTHING, related_name="test_suite")
    is_driven = models.IntegerField(default=0, null=True, verbose_name='数据驱动判断')
    filename = models.CharField(max_length=200, null=True, verbose_name='文件原名称')
    savename = models.CharField(max_length=200, null=True, verbose_name='文件存储名称')
    artificial = models.FloatField(null=True, verbose_name='人工执行时间')
    status = models.IntegerField(default=0, null=True, verbose_name='执行状态,0待执行,1执行中')

    class Meta:
        db_table = "auto_test_suite"
        verbose_name = "测试套件"
        verbose_name_plural = "测试套件"

    def __str__(self):
        return self.name


class TestPlan(BaseModel):
    """
    执行计划
    """
    name = models.CharField(max_length=100, verbose_name="计划名称")
    environment = models.ForeignKey(Environment, on_delete=models.SET_NULL, null=True, related_name="test_plan", verbose_name="执行环境")
    description = models.TextField(blank=True, null=True, verbose_name="计划描述")
    suite = models.ForeignKey(TestSuite, on_delete=models.CASCADE, related_name="test_plan", verbose_name="关联套件")
    schedule = models.CharField(max_length=50, blank=True, null=True, verbose_name="定时任务表达式")
    qymsg = models.IntegerField(null=True, help_text='0,不发送消息, 1,发送消息')
    webhook = models.CharField(max_length=500, null=True, help_text='企业微信群机器人webhook')

    class Meta:
        db_table = "auto_test_plan"
        verbose_name = "执行计划"
        verbose_name_plural = "执行计划"

    def __str__(self):
        return self.name


class Script(BaseModel):
    """
    脚本(前置/后置、SQL/Python)
    """
    kw_name = models.CharField(max_length=50, null=True, unique=True, verbose_name='脚本文件名称')
    script_type = models.CharField(
        max_length=20,
        choices=[("PYTHON", "Python Script"), ("SQL", "SQL Script")],
        verbose_name="脚本类型"
    )
    methed_name = models.CharField(max_length=200, null=True, unique=True,
                                   verbose_name='脚本里面可以多个函数,取其中一个作入口函数名称,唯一')
    content = models.TextField(null=True, verbose_name='脚本内容 ')
    pars = models.IntegerField(null=True, verbose_name='参数数量')
    rules = models.TextField(null=True, verbose_name='规则描述,备注')
    public = models.IntegerField(default=0, verbose_name="以项目为维度,0:为私有,1:为公开")
    pro = models.ForeignKey(Project, null=True, on_delete=models.DO_NOTHING, related_name="script",)

    class Meta:
        db_table = "auto_script"
        verbose_name = "脚本"
        verbose_name_plural = "脚本"

    def __str__(self):
        return f"{self.kw_name} ({self.script_type})"




class TestPlanResult(BaseModel):
    """
    计划执行记录
    """
    plan = models.ForeignKey(TestPlan, null=True, on_delete=models.DO_NOTHING, related_name="plan_result", verbose_name='执行组ID')
    tp_name = models.CharField(max_length=255, null=True, verbose_name='执行计划名称')
    pro = models.ForeignKey(Project, null=True, on_delete=models.DO_NOTHING, related_name="plan_result", verbose_name='项目ID')

    class Meta:
        managed=True
        db_table = 'auto_test_plan_result'

    def __str__(self):
        return '名称%s' % (str(self.tp_name))

class TestSuiteResult(BaseModel):
    """
    测试套件执行报告
    """
    suite = models.ForeignKey(TestSuite, null=True, on_delete=models.DO_NOTHING, related_name="suites_result")
    excutnum = models.CharField(max_length=200, null=False, verbose_name='执行批次编号,按计划+用例生成数据,用此字段判断报告文件集,有多个相同编号的数据组成一封报告')
    planName = models.CharField(max_length=200, null=True, verbose_name='计划名称')
    cases = models.IntegerField(null=True, verbose_name='用例总数')
    passs = models.IntegerField(null=True, verbose_name='通过数量')
    fails = models.IntegerField(null=True, verbose_name='失败数量')
    totaltime = models.CharField(max_length=100, null=True, verbose_name='计划执行总用时')
    sCount = models.IntegerField(null=True, verbose_name='300ms<=s<700ms的数量')
    nCount = models.IntegerField(null=True, verbose_name='700ms<=s<1000ms的数量')
    cCount = models.IntegerField(null=True, verbose_name='1000ms<=s的数量')
    pro = models.ForeignKey(Project, null=True, on_delete=models.DO_NOTHING, verbose_name='项目id')
    tpr = models.ForeignKey(TestPlanResult, null=True, on_delete=models.DO_NOTHING, related_name="suites_result", verbose_name='套件的id')
    artificial = models.FloatField(null=True, verbose_name='计划执行时人工用时')
    savetime = models.FloatField(null=True, verbose_name='比人工省时')
    plancname = models.CharField(max_length=50, null=True, verbose_name='计划创建人名称')
    execuname = models.CharField(max_length=50, null=True, verbose_name='报告创建人名称')
    status = models.IntegerField(default=0, null=True, verbose_name='执行状态,0:执行完成 ,1:执行中')


    class Meta:
        managed=True
        db_table = 'auto_test_suite_result'

    def __str__(self):
        return '编号%s' % (str(self.excutnum))


class TestResult(BaseModel):
    """
    用例执行结果
    """
    casename = models.CharField(max_length=100, null=True, verbose_name="存放用例名称")
    excutnum = models.CharField(max_length=200, null=False,
                                verbose_name='执行批次编号,按计划+用例生成数据,用此字段判断报告文件集,有多个相同编号的数据组成一封报告')
    loopnum = models.CharField(max_length=200, null=True, verbose_name='执行循环的编号')
    test_plan = models.ForeignKey(TestPlan, null=True, on_delete=models.DO_NOTHING, related_name="case_result", verbose_name="关联执行计划,可为空,用例可单独执行")
    test_case = models.ForeignKey(TestCase, on_delete=models.DO_NOTHING, related_name="case_result", verbose_name="关联用例")
    status = models.CharField(
        max_length=20,
        choices=[("PASS", "Pass"), ("FAIL", "Fail")],
        verbose_name="执行结果"
    )
    response_data = models.JSONField(blank=True, null=True, verbose_name="请求信息与响应数据,这里有数据性能瓶颈,大家遇到再自行解决")
    duration = models.FloatField(verbose_name="耗时(秒)")
    driven = models.IntegerField(null=True, default=0, verbose_name='是否数据驱动,0:不驱动, 1:数据驱动')
    parentid = models.IntegerField(null=True, default=-1, verbose_name='驱动第一条数据id')
    exectime = models.FloatField(null=True, verbose_name='用例执行用时')
    savetime = models.FloatField(null=True, verbose_name='比人工省时')


    class Meta:
        db_table = "auto_test_result"
        verbose_name = "测试结果"
        verbose_name_plural = "测试结果"
        indexes = [
            models.Index(fields=["status"]),
        ]

    def __str__(self):
        return f"{self.test_case.name} - {self.status}"


class BeaftResult(BaseModel):
    """
    前置、后置执行结果
    """
    id = models.AutoField(primary_key=True)
    casename = models.CharField(max_length=100, null=True, verbose_name="存放用例名称")
    status = models.IntegerField(null=False, verbose_name='执行结果状态 ,1是成功,0是失败')
    elapsedt = models.CharField(max_length=100, null=True, verbose_name='请求响应时间')
    qutoe_case_type = models.IntegerField(null=True, default=0, verbose_name='0前置,1后置')
    qutoe_case_execorder = models.IntegerField(default=0, null=True, verbose_name='自定义方法前后执行:0 前, 1 后')
    tr = models.ForeignKey(TestResult, null=True, on_delete=models.DO_NOTHING, related_name="beaft_result", verbose_name='用例测试报告外键')

    class Meta:
        managed = True
        db_table = 'auto_beaft_result'

5.2.3、生成表

models.py文件里面class 除了BaseModel外,其他的完成生成表操作后,都会在mysql中生成对应的表。

运行命令(老演员了):

python manage.py makemigrations  #根据在 models.py 中定义或修改的模型(Model),初始化用户表,没有新增models,这里运行不会改为任何模型,默认生成在应用目录下的 migrations/ 文件夹里
python manage.py migrate    #Django 会读取 migrations 文件,然后执行对应的 SQL 语句。将数据更新到数据库;

生成表的部分各位先自行操作,后续我会补上截图
因为我虚拟机出了一点小问题,安装的mysql8容器崩了,导致无法生成表,不想重装,先去研究如何解决,然后再将解决方案分享出来。

6、总结&预告

本章节将后端代码架构与数据库设计完成,开头最难的部分算是做完了,接下来便是开始编写代码,下一章会进行两个页面代码编写,项目管理与环境管理页面;顺便会将平台前端页面的菜单做出来。
所以下一章正式进行代码编写阶段,期待~