(四)flaskrestfu 测试用例管理平台 添加测试用例实例与对应接口以及excel上传

测试用例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

格式要提前定义好、且顺序不能变 、否则会读取错误

excel格式

接口定义

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值