Loading

使用flask开发测试平台01-模型设计

前言

​ 在去年的年中,我一时冲动写了一个基于flask开发的测试平台,然后把服务托管在了腾讯云上,本来想是写文章分享的怎么开发的,但是一直没有写。如今一年已经过去了,服务器也是到期了,演示网址也是无法访问了。
正好现在是国庆假期,一天好似虚度人生,就敲起键盘写一点我的这个测试平台。

​ 首先肯定有一个名称,因为我羡慕自由的鸟儿飞翔在天空,所以我给起的名字是flytest。好了,名称就是这个样子了。继续看吧。

技术栈

​ 本项目是一个前后端不分离的jinja2渲染形式的平台,所以前端页面我使用的是bootstrap4。别问我为啥不用VUE,问就是不会。

环境 用途
bootstrap4 页面显示
flask 后端使用的框架
celery 测试用例执行
MySQL 测试数据的存储
redis 缓存
APScheduler 定时任务开发
nginx+gunicorn+supervisor 环境部署

平台结构

​ 测试平台大概分为这么几个模块,每一个模块都有相关的作用,但是每个模块之间又有着很强的关联性。

模块 作用
项目管理 对不同项目进行管理
环境管理 管理测试环境和生产环境
测试管理 添加用例并进行测试
任务列表 测试的任务和任务结果显示
定时任务 定时任务列表
最新报告 显示最近的测试结果
问题管理 显示最新的问题
结果趋势 通过折线图查看最新的测试情况

平台截图

简陋的登录页
image-20210106165027396
注册页面
image
主界面
image-20210905212400769
测试管理页
image

流程设计

​ 以下是我对于这个测试平台的流程构想,其中人员管理部分是采用邮箱注册并激活的形式进行处理。

测试平台操作流程设计图

数据库设计

在这个模型文件中使用了两个flask第三方库,分别是flask-loginflask-avatars

# 需要做这样的导入
from flask_login import UserMixin, current_user
from flask_avatars import Identicon
用户表
字段名称 类型 解释 关系
id Integer 主键
email Char(128) 邮箱地址
username Char(128) 用户名称
password_hash Char(128) 密码
avatar_s Char(64) 头像
avatar_m Char(64) 头像
avatar_l Char(64) 头像
is_acticed Boolean 是否激活
product 关联项目表
apitest 关联测试表
产品管理表
字段名称 类型 解释 关系
name Char(128) 产品名称
desc Char(512) 产品描述
tag Char 产品类型
user_id Integer 产品负责人
测试地址管理表
字段名称 类型 解释 关系
name Char(128) 地址名称
url Char(512) 地址
product_id Integer 产品ID
测试表
字段名称 类型 解释 关系
name Char(128) 地址名称
results Integer 测试结果
task_id Char(256) 任务ID
user_id Integer 用户ID
测试步骤
字段名称 类型 解释 关系
name Char(128) 请求名称
method Char(16) 请求方法
route Char(512) 请求路径
headers Text 请求头
request_data Text 请求数据
expected_result Char(512) 预期值
expected_regular Char(512) 预期正则
request_extract Char(512) 请求验证
response_extract Char(512) 返回验证
results Text(2048) 结果
报告
字段名称 类型 解释 关系
types Integer 1普通任务2定时任务
task_id Char(256) 任务ID
name Char(256) 名称
result Char(2048) 结果
问题库
字段名称 类型 解释 关系
task_id Char(256) 任务ID
casename Char(256) 用例名称
stepname Char(512) 步骤名称
request Text 请求
detail Text 详情
level Char(10) 级别
任务结果
字段名称 类型 解释 关系
task_id Char(256) 任务ID
name Char(512) 任务名称
hostname Char(512) 主机名称
params Char(512) 任务参数
result Text 结果
traceback Text 异常详情
定时任务
字段名称 类型 解释 关系
task_id Char(256) 任务ID
test_name Char(128) 任务名称
func_name Char(256) 任务执行函数名
trigger Char(64) 执行类型
args Char(128) 参数
kwargs Char(128) 关键字参数
max_instances
times
misfire_grace_time
next_run_time Datetime 下次运行时间
start_date Date 开始日期
is_active Boolean 是否激活
product_id Integer 项目ID

​ 以上我们把表结构大概设计好了,看看实际的sqlalchemy模型文件吧:

from datetime import datetime

from werkzeug.security import generate_password_hash, check_password_hash
from flask import session, current_app
from flask_login import UserMixin, current_user
from flask_avatars import Identicon
from app.extensions import db, cache


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(128), unique=True)
    username = db.Column(db.String(128))
    password_hash = db.Column(db.String(128))
    avatar_s = db.Column(db.String(64))
    avatar_m = db.Column(db.String(64))
    avatar_l = db.Column(db.String(64))
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)
    is_active = db.Column(db.Boolean, default=False, nullable=False)

    products = db.relationship('Product', back_populates='user')
    apitests = db.relationship('Apitest', back_populates='user')

    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)
        self.generate_avatar()

    def generate_avatar(self):
        avatar = Identicon()
        filenames = avatar.generate(text=self.email)
        self.avatar_s = filenames[0]
        self.avatar_m = filenames[1]
        self.avatar_l = filenames[2]
        db.session.commit()

    @property
    def password(self):
        return AttributeError("password is only readable")

    @password.setter
    def password(self, pwd):
        self.password_hash = generate_password_hash(pwd)

    def verify_password(self, pwd):
        return check_password_hash(self.password_hash, pwd)

    def __repr__(self):
        return self.username

    def __str__(self) -> str:
        return self.username


class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    desc = db.Column(db.Text)
    tag = db.Column(db.String(32))
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship('User', back_populates='products')

    apiurls = db.relationship('Apiurl', back_populates='product')
    apitests = db.relationship('Apitest', back_populates='product')
    reports = db.relationship('Report', back_populates='product')
    bugs = db.relationship('Bug', back_populates='product')
    works = db.relationship('Work', back_populates='product')

    def __repr__(self):
        return '<Product %s>' % self.name

    @staticmethod
    def get_product(pk):
        if pk is not None:
            session["current_product"] = pk
        pk = session.get("current_product", None)
        product = Product.query.filter_by(id=pk, user=current_user, is_deleted=False).one_or_none() or Product.query.filter_by(user=current_user, is_deleted=False).first()
        return product


class Apiurl(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    url = db.Column(db.String(512))
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)

    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    product = db.relationship('Product', back_populates="apiurls")
    apisteps = db.relationship('Apistep', back_populates='apiurl')

    def __repr__(self):
        return '<Url %s>' % self.name


class Apitest(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64))
    results = db.Column(db.Integer, default=-1)
    task_id = db.Column(db.String(255), index=True)
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship('User', back_populates='apitests')
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    product = db.relationship('Product', back_populates='apitests')
    apisteps = db.relationship('Apistep', back_populates='apitest')

    def __repr__(self):
        return '<ApiTest %s>' % self.name


class Apistep(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    method = db.Column(db.String(16))
    route = db.Column(db.String(512))
    headers = db.Column(db.Text)
    request_data = db.Column(db.Text, nullable=True)
    expected_result = db.Column(db.String(512))
    expected_regular = db.Column(db.String(512), nullable=True)
    request_extract = db.Column(db.String(512))
    response_extract = db.Column(db.String(512))
    status = db.Column(db.Integer, default=-1)
    results = db.Column(db.Text(2048), nullable=True)
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)

    apiurl_id = db.Column(db.Integer, db.ForeignKey('apiurl.id'))
    apiurl = db.relationship('Apiurl', back_populates='apisteps')
    apitest_id = db.Column(db.Integer, db.ForeignKey('apitest.id'))
    apitest = db.relationship('Apitest', back_populates='apisteps')
    report_id = db.Column(db.Integer, db.ForeignKey("report.id"))
    report = db.relationship('Report')

    def __repr__(self):
        return '<ApiStep %s>' % self.name


class Report(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    types = db.Column(db.Integer, default=1)  # 1是普通任务 2是定时任务
    task_id = db.Column(db.String(256), index=True)
    name = db.Column(db.String(256), default="NULL")
    result = db.Column(db.String(2048))
    status = db.Column(db.Integer, default=-1)
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)

    apistep = db.relationship('Apistep', uselist=False)
    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    product = db.relationship('Product', back_populates='reports')


class Bug(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    task_id = db.Column(db.String(256), index=True)
    casename = db.Column(db.String(256))
    stepname = db.Column(db.String(512))
    request = db.Column(db.Text)
    detail = db.Column(db.Text)
    status = db.Column(db.Integer)
    level = db.Column(db.String(10), default='一般')
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    updated = db.Column(db.DateTime, onupdate=datetime.utcnow,
                        default=datetime.utcnow)
    is_deleted = db.Column(db.Boolean, default=False, nullable=False)

    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    product = db.relationship('Product', back_populates='bugs')

    def __repr__(self):
        return '<BUG FOR %S>' % self.stepname


class Work(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    task_id = db.Column(db.String(256), index=True)
    name = db.Column(db.String(512))
    hostname = db.Column(db.String(512))
    params = db.Column(db.String(512))
    status = db.Column(db.Text)
    result = db.Column(db.Text)
    traceback = db.Column(db.Text)
    created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
    product = db.relationship('Product', back_populates='works')

    def __repr__(self):
        return self.task_id


class CronTabTask(db.Model):
    """定时任务"""
    id = db.Column(db.Integer, primary_key=True)
    task_id = db.Column(db.String(256), index=True)
    test_name = db.Column(db.String(128))
    func_name = db.Column(db.String(256))
    trigger = db.Column(db.String(64))
    args = db.Column(db.String(128))
    kwargs = db.Column(db.String(128))
    max_instances = db.Column(db.Integer)
    times = db.Column(db.String(128))
    misfire_grace_time = db.Column(db.Integer)
    next_run_time = db.Column(db.String(256))
    start_date = db.Column(db.String(256))

    is_active = db.Column(db.Boolean, default=True, nullable=False)
    product_id = db.Column(db.Integer)

以上就是模型文件的所有数据了,感觉第一篇应该讲一下怎么搭建环境,好了,先写这么多吧。

posted @ 2022-10-03 21:35  随风挥手  阅读(280)  评论(0编辑  收藏  举报
群1
299524235(满)
群2
362812191