找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2586

积分

0

好友

366

主题
发表于 昨天 22:37 | 查看: 2| 回复: 0

本章将正式启动平台后端核心部分的搭建。内容分为两大板块:一是依据第二章的架构设计,调整并完成 Django 项目的目录结构,使其模块化更清晰;二是完成自动化测试功能模块的数据库表设计与创建,这是后续功能开发的核心基础。当这两部分工作完成后,整个项目的骨架便已成型,后续开发将顺畅许多。

术语说明:后文中,“模块”与“APP”同义,均指通过 python manage.py startapp 创建的 Django 应用。

开发前准备

必备基础

为更好地跟随本教程,建议你具备以下基础:

  • Python:熟练掌握。
  • Django:能够编写基本 Demo。
  • Vue3 + Element:能够编写基本 Demo。

当然,即使基础稍有欠缺也不必担心,本文将详细讲解每一个步骤与代码,跟随操作即可完成搭建。

环境准备

请确保你的开发电脑已安装以下软件:

  • MySQL 8
  • Python 3.6+
  • Node.js
  • Django 4+
  • Pycharm 或 Vscode(用于前端开发)

数据库准备

  1. 确保 MySQL 8 服务已启动。
  2. 新建一个名为 apiauto 的数据库。
  3. 数据库字符集请设置为 utf8mb4,排序规则为 utf8mb4_general_ci

新建apiauto数据库设置字符集

代码架构调整

回顾前文

根据第二章的设计,我们的后端主要分为三个核心模块:用户自动化测试系统管理。项目架构将按照这三个模块进行划分。

在第一章中,我们已创建了 users APP,现在需要新增另外两个 APP。

平台架构图:前端、接口层、控制层、模型层、数据库

让我们先回顾第一章结束时的项目目录结构:

apiauto/                        # 项目根目录
├─ manage.py                    # Django 管理脚本
│
├─ apiauto/                     # 项目配置目录
│   ├─ __init__.py
│   ├─ settings.py
│   ├─ urls.py
│   ├─ wsgi.py
│   └─ asgi.py
│
└─ users/                       # 自定义应用(用户模块)
    ├─ __init__.py
    ├─ admin.py
    ├─ apps.py
    ├─ models.py
    ├─ serializers.py
    ├─ views.py
    ├─ urls.py
    └─ test.py

不难发现,users APP 与项目配置目录 apiauto 处于同一层级。为了更清晰地区分项目配置与业务应用,便于统一管理,我们需要创建一个专门的 apps 目录来存放所有 APP。

新架构目录

计划在同级目录下创建 apps 文件夹,并将 users APP 移入其中。调整后的理想目录结构如下:

apiauto/                        # 项目根目录
├─ manage.py                    # Django 管理脚本
│
├─ apiauto/                     # 项目配置目录
│   ├─ __init__.py
│   ├─ settings.py
│   ├─ urls.py
│   ├─ wsgi.py
│   └─ asgi.py
│
└─ apps/                        # 应用集中管理目录
    ├─ users/                   # 用户模块APP
    ├─ auto/                    # 自动化测试模块APP(待创建)
    └─ sysmanages/              # 系统管理模块APP(待创建)

调整操作步骤

1. 在项目根目录下创建 apps 目录

在 IDE 的项目文件树中,右键点击 apiauto(项目根目录),选择 New -> Directory

在IDE中新建目录

在弹出的对话框中输入 apps,然后按回车确认。

输入目录名apps

创建成功后,目录结构如下图所示:

apps目录创建成功

2. 将 users APP 移动到 apps 目录下

在 IDE 中,直接将 users 文件夹拖动到 apps 文件夹上,或使用 Move 功能。

移动users目录到apps

系统可能会提示搜索引用,稍等片刻即可完成移动。

移动目录进行中

移动完成后,目录结构应如下图所示:

users已移动到apps下

让 Django 识别新的 APP 路径

移动 users APP 后,Django 默认的模块查找路径中已不存在原来的 users,因此运行项目会报错:ModuleNotFoundError: No module named 'users'

模块导入错误提示

解决方法很简单,我们需要修改 apiauto/settings.py 文件,将 apps 目录添加到 Python 的系统路径中。

settings.py 文件的开头附近,找到 BASE_DIR 定义行,在其后添加以下代码:

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

在settings.py中添加sys.path配置

添加这行代码后,项目即可正常运行。

Django开发服务器启动成功

新增功能模块 APP

创建自动化测试模块 APP

现在我们需要在 apps 目录下创建新的 APP。
首先,打开终端,并确保当前工作目录在 apps 下。

在终端中进入apps目录

输入以下命令创建名为 auto 的 APP(对应自动化测试模块):

python ../manage.py startapp auto

执行命令创建auto应用

创建系统管理模块 APP

继续在 apps 目录下,输入以下命令创建名为 sysmanage 的 APP:

python ../manage.py startapp sysmanage

执行命令创建sysmanage应用

至此,平台后端三个核心 APP 已全部创建完毕,项目架构初步形成:

  • users:负责用户登录、注册、信息维护等所有与用户身份相关的功能。
  • sysmanage:负责整个系统的后台管理,如权限控制、操作日志、系统配置等。
  • auto核心功能模块,负责自动化测试的主体功能,包括环境管理、接口管理、用例管理、测试套件、执行计划等。

完整的项目目录结构

有了清晰的架构目录,接下来不可或缺的一步就是数据库表设计。

  • users 模块的表结构基于 Django 自带的用户模型扩展,此处无需额外设计。
  • sysmanage 模块主要管理其他模块的数据,自身可能没有独立的业务表,暂时搁置。
  • 下面,我们将重点设计 auto 模块的数据库表。

数据库设计

表结构规划

一个完整的接口自动化测试平台,通常需要以下核心数据表:

  1. 项目表:用于区分不同项目,实现数据隔离。
  2. 环境配置表:为每个项目配置不同的测试环境(如 SIT、UAT、线上等)。
  3. 接口树表:以树形结构组织和管理接口,便于按功能模块分类。
  4. 用例树表:以树形结构组织和管理测试用例。
  5. 接口表:存储接口的详细定义,可包含 Mock 配置。
  6. 用例表:存储测试用例的具体步骤、检查点、执行状态等。
  7. 前置/后置步骤表:记录用例执行前后需要执行的脚本或关键字操作。
  8. 套件表:测试用例的集合。
  9. 执行计划表:可关联套件,支持定时任务,用于发起批量测试。
  10. 用例执行记录表:存储单次用例执行的详细报告。
  11. 套件执行记录表:存储套件执行的汇总报告。
  12. 计划执行记录表:存储计划执行的汇总报告。
  13. 自定义脚本表:存储用户编写的、可在前置后置步骤中调用的脚本。

这些表的实体关系(ER)设计图如下:

自动化测试平台数据库ER关系图

核心代码实现

1. 树形结构递归工具

从 ER 图可以看出,“接口树”和“用例树”都是自关联的递归模型。为了方便处理这类数据结构,我们先实现一个通用的树形结构工具类。

在项目根目录下(与 apps 同级)新建一个 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)

TreeMixin工具类代码

这个工具类将为后续所有自递归模型(如接口树、用例树)提供便捷的树形数据转换和展示方法。

2. 编写 models.py

接下来,我们依据 ER 图,在 apps/auto/models.py 中定义所有数据模型。

首先,我们观察到几乎所有表都包含 is_delete(逻辑删除)、created_atupdated_atcreated_by 等公共字段。我们可以将这些字段抽取到一个抽象的基类 BaseModel 中。

如果你对 Django 的 ORM 还不熟悉,可以先跟着代码实践,了解模型如何映射为数据库表。核心是掌握字段定义和模型间的关系(ForeignKey, ManyToManyField等)。

以下是完整的 models.py 代码,请结合 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'

3. 生成数据库表

完成 models.py 的编写后(除了抽象基类 BaseModel),我们需要通过 Django 的迁移命令,在 MySQL 数据库中创建对应的表。

在项目根目录下执行以下命令:

# 1. 创建迁移文件:根据 models.py 的改动生成迁移脚本
python manage.py makemigrations

# 2. 应用迁移:执行迁移脚本,在数据库中创建或修改表结构
python manage.py migrate

说明makemigrations 命令会检测模型变化并生成记录文件;migrate 命令则负责执行这些记录,将其同步到数据库。如果你是首次为 auto 应用生成表,makemigrations 会创建对应的初始迁移文件。

至此,一个功能完备的自动化测试平台所需的 软件测试 数据表就设计并创建完成了。

总结

本章完成了从零搭建自动化测试平台中颇具挑战性的两部分工作:清晰的后端代码架构划分详尽的业务数据库设计。我们调整了项目目录,将应用统一管理在 apps 下;并设计了涵盖项目、环境、接口、用例、计划、报告等全流程的核心数据模型。

骨架已然搭好,接下来的章节我们将正式进入代码编写阶段,开始实现项目管理、环境管理等前端页面,并逐步完善平台的各项功能。如果你在搭建过程中遇到问题,欢迎在技术社区交流探讨。真正的“造轮子”之旅,现在才正式开始!




上一篇:小肩膀逆向 JS逆向与POST四期实战:从入门到精通 易语言JS逆向、多线程、抓包与项目实战全解析
下一篇:小肩膀逆向 iOS逆向一期:C语言基础、密码学与Frida实战 一站式掌握iOS逆向核心技能与安全分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-27 02:53 , Processed in 0.249488 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表