本章主要讲述需求介绍及后端框架搭建
整体环境:
注意:这个平台只是简单打来练手的,告诉大家有一个简单的搭建思路并简单实现功能,因为正常情况下,公司的基础架构都早已搭建好了,并有很完善的功能,并且会直接在别人的开源项目下进行二次开发,不会像我们这样自己着手去打一个平台(又费时/又费力,打出来的效果还没别人开源的好)我们只需要在原公司基础架构上去实现自己的功能即可。
本次实战的整体功能,后端API实现简单来说:
1、实现【测试用例】的增删改查
2、实现【测试计划】的查询/新增
3、实现【构建记录】的查询/新增
可能稍微复杂点的逻辑就是【生成计划】这个操作
--> 不仅需要同步生成一条测试任务,将测试任务和测试用例关联上
--> 还要能同步调用jenkins,生成一条构建记录,并且把jenkins生成的测试报告给返回回来
Flask==2.0.3
Flask_Cors==3.0.10
flask_restx==0.5.1
Flask_SQLAlchemy==2.5.1
jenkinsapi==0.3.11
PyYAML==6.0
SQLAlchemy==1.4.39
"""
启动类
"""
import yaml
from flask import Flask
from flask_restx import Api, Namespace
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import Session
from flask_cors import CORS
app = Flask(__name__)
api = Api(app)
# 用例的命名空间,CORS解决跨域问题
CORS(app, supports_credentials=True)
# 数据库配置
# 读取数据库配置
with open("../config/data.yml", encoding='utf-8') as f:
result = yaml.safe_load(f)
username = result.get("database").get('username')
password = result.get("database").get('password')
server = result.get("database").get('server')
database = result.get("database").get('database')
app.config['SQLALCHEMY_DATABASE_URI'] = \
f"mysql+pymysql://{username}:{password}@{server}/{database}?charset=utf8"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# SQLALchemy 绑定app
db = SQLAlchemy(app)
db_session: Session = db.session
def add_router():
from controller.testcase_controller import case_ns
# 添加api的命名空间,解决swagger不展示内容的问题
api.add_namespace(case_ns, "/testcase")
if __name__ == '__main__':
add_router()
app.run(debug=True, host="0.0.0.0", port=8888)
## 进行数据库的配置
database:
username: root
password: yy1998123
server: localhost
database: test_platform
实体类字段和数据库表字段是一一对应的,而我们在做需求的时候肯定是先把整个需求的逻辑理清楚,再去做,首先要做的就是把数据库表给设计出来,这里表就设计的很简单。
测试计划和测试用例之间:多对多关系
为什么要用中间表呢?
如果说当表结构逻辑很复杂的话,那添加的外键就会越来越多,到时候表结构里面会有一堆和业务没那么有关系的外键
测试计划和构建记录之间:一对多关系
"""
测试用例实体类
"""
from sqlalchemy import *
from app import db
class TestCaseEntity(db.Model):
# 表名
__tablename__ = "testcase"
# 用例ID 用例的唯 一标识
id = db.Column(Integer, primary_key=True)
# 用例的标题 或者文件名,限定 80个字符 ,不为空,并且唯一
case_title = db.Column(String(80), nullable=False, unique=True)
# 备注
remark = db.Column(String(120))
def as_dict(self):
return {"id": self.id, "case_title": self.case_title, "remark": self.remark}
"""
测试计划实体类
"""
from sqlalchemy import *
from sqlalchemy.orm import relationship
from do.testcase_plan_rel import testcase_plan_rel
from app import db
class PlanEntity(db.Model):
# 表名
__tablename__ = "plan"
# 用例ID 用例的唯 一标识
id = db.Column(Integer, primary_key=True)
# 用例的标题 或者文件名,限定 80个字符 ,不为空,并且唯一
name = db.Column(String(80), nullable=False, unique=True)
testcases = relationship("TestCaseEntity",
secondary=testcase_plan_rel)
def as_dict(self):
# [TestcaseEntity<1>] => test_demo.py testcase_2.py
return {"id": self.id, "name": self.name,
# 遍历拿到测试用例名称,并且将名称转为字符串格式
"testcase_info": " ".join([testcase.case_title for testcase in self.testcases])}
"""
测试用例_测试计划_中间表 实体类
"""
from sqlalchemy import *
from app import db
testcase_plan_rel = db.Table(
'testcase_plan_rel',
Column('testcase_id', Integer,
ForeignKey('testcase.id'),
primary_key=True),
Column('plan_id', Integer,
ForeignKey('plan.id'),
primary_key=True))
"""
测试计划实体类
"""
from sqlalchemy import *
from sqlalchemy.orm import relationship
from do.testcase_plan_rel import testcase_plan_rel
from app import db
class PlanEntity(db.Model):
# 表名
__tablename__ = "plan"
# 用例ID 用例的唯 一标识
id = db.Column(Integer, primary_key=True)
# 用例的标题 或者文件名,限定 80个字符 ,不为空,并且唯一
name = db.Column(String(80), nullable=False, unique=True)
testcases = relationship("TestCaseEntity",
secondary=testcase_plan_rel)
def as_dict(self):
# [TestcaseEntity<1>] => test_demo.py testcase_2.py
return {"id": self.id, "name": self.name,
# 遍历拿到测试用例名称,并且将名称转为字符串格式
"testcase_info": " ".join([testcase.case_title for testcase in self.testcases])}
大家可以自己在mysql客户端去添加表,也可以直接运行文件,我这里采用运行文件的方法
注意 :创建一次后,代码就可以注释掉,避免之后每次启动项目都创建
"""
数据库表创建
注意:创建一次后,就可以注释掉,避免之后每次启动项目都创建
"""
from do.build_entity import BuildEntity
from do.plan_enetity import PlanEntity
from do.testcase_plan_rel import testcase_plan_rel
from app import db
from do.testcase_entity import TestCaseEntity
if __name__ == '__main__':
db.create_all()
"""
该类为日志封装类
"""
import logging
import os
from logging.handlers import RotatingFileHandler
# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)
# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接当前要输出日志的路径
log_dir_path = os.sep.join([root_path, f'/logs'])
if not os.path.isdir(log_dir_path):
os.mkdir(log_dir_path)
# 创建日志记录器,指明日志保存路径,每个日志的大小,保存日志的上限
file_log_handler = RotatingFileHandler(os.sep.join([log_dir_path, 'log.log']), maxBytes=1024 * 1024, backupCount=10)
# 设置日志的格式
date_string = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] [%(filename)s]/[line: %(lineno)d]/[%(funcName)s] %(message)s ', date_string)
# 日志输出到控制台的句柄
stream_handler = logging.StreamHandler()
# 将日志记录器指定日志的格式
file_log_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# 为全局的日志工具对象添加日志记录器
# 绑定绑定句柄到logger对象
logger.addHandler(stream_handler)
logger.addHandler(file_log_handler)
# 设置日志输出级别
logger.setLevel(level=logging.DEBUG)