测试用例sql实体
对应cases 实体 目前就先这么定义 与之还有bug、part(用例模块)俩实体关系定义
用例属于对应产品版本下、多对一关系 目前未定义公共用例
用例对于bug 属于一对一关系
对于模块未设定关系、没有设置键
对于步骤使用了json
class Cases(Base):
__tablename__ = "cases"
part = db.Column(db.String(20), comment="模块")
title = db.Column(db.String(20), unique=True, nullable=False, comment="用例名称")
desc = db.Column(db.String(100), nullable=False, comment="用例描述")
creator = db.Column(db.INTEGER, nullable=False, comment="创建人")
steps = db.Column(db.JSON, nullable=False, comment="用例步骤")
status = db.Column(db.Enum("QUEUE", "TESTING", "BLOCK", "SKIP", "PASS", "FAIL", "CLOSE"), server_default="QUEUE",
comment="状态")
platform = db.Column(db.Enum("IOS", "ANDROID", "WEB", "PC", "APP"), server_default="IOS", comment="所属平台")
case_level = db.Column(db.Enum('P1', 'P2', 'P3', 'P4'), server_default='P1', comment="用例等级")
case_type = db.Column(db.Enum('功能', '接口', '性能'), server_default='功能', comment="用例类型")
prd = db.Column(db.String(200), nullable=False, comment="需求链接")
updater = db.Column(db.INTEGER, nullable=True, comment="修改人")
mark = db.Column(db.String(100), nullable=True, comment="用例备注")
versionID = db.Column(db.INTEGER, db.ForeignKey('version.id'), comment="所属版本")
productID = db.Column(db.INTEGER, db.ForeignKey("product.id"), nullable=True, comment="所属产品")
from .bugs import Bug
bug = db.relationship("Bug", backref="bugs", lazy="dynamic")
对于用例的步骤 实体类里封装静态方法校验内容格式
@staticmethod
def __verify_steps(steps: List[Dict]) -> json:
"""
校验steps格式 并按照 step 排序
[
{
"step":1, *
"setup": "im setup" | null,
"do": "to do ...", *
"exp": "exp ....", *
},
{
"step":2,
"setup": "im setup" | null,
"do": "to do ...",
"exp": "exp ....",
}
]
:param steps: ↑
:return:
:raise: ParamErr
"""
for step in steps:
if not step.get("step"):
raise ParamException(ResponseMsg.miss("step"))
if not step.get("setup"):
step.setdefault('setup', None)
if not step.get("do"):
raise ParamException(ResponseMsg.miss("do"))
if not step.get("exp"):
raise ParamException(ResponseMsg.miss("exp"))
steps.sort(key=lambda s: s['step'])
return steps
用例模块 属于产品下 所以是多对一的关系 使用
ForeignKey
class CasePart(Base):
__tablename__ = 'case_part'
partName = db.Column(db.String(20), comment="用例模块")
productID = db.Column(db.INTEGER, db.ForeignKey("product.id"), nullable=True, comment="所属产品")
CASE 定义接口
增删该接口
比较简单不多说
class NewCaseController(Resource):
@auth.login_required
def post(self) -> MyResponse:
"""
新增用例
:return: MyResponse
"""
parse = MyRequestParseUtil()
parse.add(name="part", type=str, required=True)
parse.add(name="title", type=str, required=True, unique=Cases)
parse.add(name="desc", type=str, required=True)
parse.add(name="case_level", type=str, choices=["P1", "P2", "P3", "P4"], required=True)
parse.add(name="status", type=str, choices=["QUEUE", "TESTING", "BLOCK", "SKIP", "PASS", "FAIL", "CLOSE"],
required=True)
parse.add(name="platform", type=str, choices=["IOS", "ANDROID", "WEB", "PC", "APP"], required=True)
parse.add(name="case_type", type=str, choices=["功能", "接口", "性能"], required=False)
parse.add(name="prd", type=str, required=True)
parse.add(name="productID", type=int, isExist=Product, required=True)
parse.add(name="versionID", type=int, isExist=Version, required=True)
parse.add(name="steps", type=list, required=True)
Cases(**parse.parse_args()).save()
return MyResponse.success()
@auth.login_required
def put(self) -> MyResponse:
"""
用例修改
:return: MyResponse
"""
parse = MyRequestParseUtil()
parse.add(name="id", type=int, required=True, isExist=Cases)
parse.add(name="part", type=str, required=False)
parse.add(name="title", type=str, required=False)
parse.add(name="desc", type=str, required=False)
parse.add(name="case_level", type=str, choices=["P1", "P2", "P3", "P4"], required=False)
parse.add(name="case_type", type=str, choices=["功能", "接口", "性能"], required=False)
parse.add(name="platform", type=str, choices=["IOS", "ANDROID", "WEB", "PC", "APP"], required=False)
parse.add(name="prd", type=str, required=False)
parse.add(name="productID", type=int, isExist=Product, required=False)
parse.add(name="versionID", type=int, isExist=Version, required=False)
parse.add(name="steps", type=list, required=False)
Cases.update(**parse.parse_args())
return MyResponse.success()
@auth.login_required
def delete(self) -> MyResponse:
"""
通过id删除
:return: MyResponse
"""
parse = MyRequestParseUtil()
parse.add(name="id", type=int, required=True, isExist=Cases)
Cases.delete_by_id(parse.parse_args().get("id"))
return MyResponse.success()
查用例
通过id查用例
通过产品ID 查用用例 并分页
class FindCase(Resource):
@auth.login_required
def get(self, caseID: AnyStr) -> MyResponse:
"""
通过ID
:param caseID: caseID
:return: MyResponse
"""
return MyResponse.success(Cases.get(int(caseID), "caseID"))
class QueryCaseController(Resource):
@auth.login_required
def get(self, productID: AnyStr) -> MyResponse:
"""
通过MyResponse 分页查询
:param productID: 产品ID
:return: MyResponse
"""
parse = MyRequestParseUtil("values")
parse.add(name="page", default="1")
parse.add(name="limit", default="20")
res = Product.get(productID, "productID").page_case(**parse.parse_args())
return MyResponse.success(res)
page_case
方法 在Product
实体中实现
@simpleCase
def page_case(self, page: AnyStr, limit: AnyStr) -> Pagination:
"""
查询用例分页
:param limit: limit
:param page: page
:return:Pagination
"""
limit = int(limit)
page = int(page)
items = self.cases.limit(limit).offset((page - 1) * limit).all()
total = self.cases.order_by(None).count()
return Pagination(self, page, limit, total, items)
自定了一个装饰器、自定义返回用例信息、主要是没找到好的方法
def simpleCase(func):
"""
指定CASE字段返回
"""
@wraps(func)
def d(cls, page, limit, *args):
info = func(cls, page, limit, *args)
results = {
"items": [{
"id": i.id,
"title": i.title,
"desc": i.desc,
"creator": i.creator,
"case_level": i.case_level,
"create_time": i.create_time
} for i in info.items],
"pageInfo": {
"total": info.total,
"pages": info.pages,
"page": info.page,
"limit": info.per_page
}
}
return results
return d
excel 用例文档上传
使用了
pyexcel
做依赖
class MyExcel:
def __init__(self, file_path: AnyStr, sheetName: AnyStr = None):
self.file_path = file_path
self.wb = load_workbook(self.file_path)
self.sheetName = sheetName
def save(self, **kwargs):
"""
遍历sheet 读取info
:return:
"""
if self.sheetName:
return self.sheetReader(self.wb[self.sheetName], **kwargs)
else:
return [self.sheetReader(sheet, **kwargs) for sheet in self.sheets]
def sheetReader(self, sheet: Worksheet, **kwargs):
"""
[{title:xx,desc:xx,prd:xx,case_level:xx,status:xx,steps:"steps": [{step:1,do:xx,exp:xx}..]
:param sheet:
:return:
"""
MAX_ROW = sheet.max_row
MIN_ROW = sheet.min_row
MIN_COL = sheet.min_column
MAX_COL = sheet.max_column
with create_app().app_context():
for j in range(MIN_ROW + 1, MAX_ROW + 1): # 第二行开始
d = {'part': None, 'title': None, 'desc': None, 'prd': None, 'case_level': None, 'status': None,
'steps': [], "exp": None, "platform": None}
body = [sheet.cell(j, i).value for i in range(MIN_COL, MAX_COL + 1)]
case = dict(zip(d, body))
case['steps'] = self.__steps(case.get("steps"), case.get("exp"))
case['productID'] = kwargs.get("productID")
case['versionID'] = kwargs.get("versionID")
case['creator'] = kwargs.get('creator')
case.pop("exp")
Cases(**case).save()
def __steps(self, steps: AnyStr, exp: AnyStr) -> List[Dict]:
"""
:param steps: {1.xxx,2.xxx,3.xxx}
:param exp: {1.xxx,2.xxx,3.xxx}
:return:[{step:1,do:xxx,exp:xxx},{}]
"""
_steps = []
_st = steps.split("\n")
_ex = exp.split("\n")
for i in range(len(_st)):
d = ["step", "do", "exp"]
s = re.findall(r'(\d).(.*)', _st[i])[0] # ("1","xx")
r = re.findall(r'(\d).(.*)', _ex[i])[0]
z = dict(zip(d, [int(s[0].strip()), s[1].strip()] + [r[1].strip()]))
_steps.append(z)
return _steps
@property
def sheets(self) -> List[Worksheet]:
"""
获取所有sheet
:return: List[<Worksheet>,<Worksheet>]
"""
return self.wb.worksheets
格式要提前定义好、且顺序不能变 、否则会读取错误
接口定义
class ExcelPut(Resource):
@auth.login_required
def post(self):
from werkzeug.utils import secure_filename
from faker import Faker
from Utils.myPath import getExcelPath
from Utils.myExcel import MyExcel
f = Faker()
file = request.files.get("file")
parse = MyRequestParseUtil("values")
parse.add(name="productID", required=True, isExist=Product)
parse.add(name="versionID", required=True, isExist=Version)
parse.parse_args().setdefault('creator', g.user.id)
fileName = f.pystr() + '_' + secure_filename(file.filename) # excel名称
filePath = getExcelPath(fileName) # excel路径
file.save(filePath) #
try:
MyExcel(filePath).save(**parse.parse_args())
return MyResponse.success()
except Exception as e:
return ParamError.error(ResponseMsg.ERROR_EXCEL)
请求需要传递给版本ID 与 产品ID 放到
params
里 放到body
里会解析报错、我也不知道为啥
后面会想办法 把这个接口改成异步请求、以及优化excel流程
写道最后
目前进度就是这样、有的模块没有录入 flask 也是边写边学、有好的想法的大佬可以留言。thank u
附上github github