测试平台系列(73) 设计测试计划功能

简介: 设计测试计划功能

大家好~我是米洛


我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持。


回顾


上一节我们简单介绍了下APScheduler,这一节我们来编写测试计划相关内容。

设计测试计划表


测试计划,其实也可以叫测试集合,它是我们一组用例的集合。并且有着对应的特征:

  • 定时执行
  • 执行完毕后通知方式
  • 通过率低于多少发送邮件/钉钉等通知
  • 优先级
  • 涵盖多少case
  • case失败重试间隔 等等
    这里可能比较的就是把测试计划+测试集合给耦合到一起了。
    基于上面的思路,我们就可以来设计测试计划表了, 具体字段的含义可以参照注释。
    (数据表的设计不一定完美,后续一般会根据业务需求扩展)


from sqlalchemy import Column, String, TEXT, UniqueConstraint, BOOLEAN, SMALLINT, INT
from sqlalchemy.dialects.mysql import TINYTEXT
from app.models.basic import PityBase
class PityTestPlan(PityBase):
    project_id = Column(INT, nullable=False)
    # 测试计划执行环境, 可以多选
    env = Column(String(64), nullable=False)
    # 测试计划名称
    name = Column(String(32), nullable=False)
    # 测试计划优先级
    priority = Column(String(3), nullable=False)
    # cron表达式
    cron = Column(String(12), nullable=False)
    # 用例列表
    case_list = Column(TEXT, nullable=False)
    # 并行/串行(是否顺序执行)
    ordered = Column(BOOLEAN, default=False)
    # 通过率低于这个数会自动发通知
    pass_rate = Column(SMALLINT, default=80)
    # 通知用户,目前只有邮箱,后续用户表可能要完善手机号字段,为了通知
    receiver = Column(TEXT)
    # 通知方式 0: 邮件 1: 钉钉 2: 企业微信 3: 飞书 支持多选
    msg_type = Column(TINYTEXT)
    # 单次case失败重试间隔,默认2分钟
    retry_minutes = Column(SMALLINT, default=2)
    # 测试计划是否正在执行中
    state = Column(SMALLINT, default=0, comment="0: 未开始 1: 运行中")
    __table_args__ = (
        UniqueConstraint('project_id', 'name', 'deleted_at'),
    )
    __tablename__ = "pity_test_plan"
    def __init__(self, project_id, env, case_list, name, priority, cron, ordered, pass_rate, receiver, msg_type,
                 retry_minutes, user, state=0, id=None):
        super().__init__(user, id)
        self.env = ",".join(map(str, env))
        self.case_list = ",".join(map(str, case_list))
        self.name = name
        self.project_id = project_id
        self.priority = priority
        self.ordered = ordered
        self.cron = cron
        self.pass_rate = pass_rate
        self.receiver = ",".join(map(str, receiver))
        self.msg_type = ",".join(map(str, msg_type))
        self.retry_minutes = retry_minutes
        self.state = state

这里值得注意的是,我们定义的FORM数据,环境列表env、接收人receiver、用例列表case_list都是数组,我们需要进行一波转换

编写CRUD方法


  • 抽离异步分页方法和where方法

42.jpg

DatabaseHelper

在DatabaseHelper类中添加pagination方法,接受page和size,session和sql参数,先读出sql匹配到的总数,如果为0则直接return,否则通过offset和limit获取到对应分页的数据。

where方法是用于改进我们平时的多条件查询,类似这种:

43.jpg

image

  • 编写新增测试计划方法

44.jpg

image

可以看到改造之后,我们只需要调用where方法,不需要写if name != "":这样的语句了。

因为我们不允许同一个项目里面出现同名的测试计划,所以条件是项目id+name不能重复。

  • 编写增改删方法


from sqlalchemy import select
from app.models import async_session, DatabaseHelper
from app.models.schema.test_plan import PityTestPlanForm
from app.models.test_plan import PityTestPlan
from app.utils.logger import Log
class PityTestPlanDao(object):
    log = Log("PityTestPlanDao")
    @staticmethod
    async def list_test_plan(page: int, size: int, project_id: int = None, name: str = ''):
        try:
            async with async_session() as session:
                conditions = [PityTestPlan.deleted_at == 0]
                DatabaseHelper.where(project_id, PityTestPlan.project_id == project_id, conditions) \
                    .where(name, PityTestPlan.name.like(f"%{name}%"), conditions)
                sql = select(PityTestPlan).where(conditions)
                result, total = await DatabaseHelper.pagination(page, size, session, sql)
                return result, total
        except Exception as e:
            PityTestPlanDao.log.error(f"获取测试计划失败: {str(e)}")
            raise Exception(f"获取测试计划失败: {str(e)}")
    @staticmethod
    async def insert_test_plan(plan: PityTestPlanForm, user: int):
        try:
            async with async_session() as session:
                async with session.begin():
                    query = await session.execute(select(PityTestPlan).where(PityTestPlan.project_id == plan.project_id,
                                                                             PityTestPlan.name == plan.name,
                                                                             PityTestPlan.deleted_at == 0))
                    if query.scalars().first() is not None:
                        raise Exception("测试计划已存在")
                    plan = PityTestPlan(**plan.dict(), user=user)
                    await session.add(plan)
        except Exception as e:
            PityTestPlanDao.log.error(f"新增测试计划失败: {str(e)}")
            raise Exception(f"添加失败: {str(e)}")
    @staticmethod
    async def update_test_plan(plan: PityTestPlanForm, user: int):
        try:
            async with async_session() as session:
                async with session.begin():
                    query = await session.execute(
                        select(PityTestPlan).where(PityTestPlan.id == plan.id, PityTestPlan.deleted_at == 0))
                    data = query.scalars().first()
                    if data is None:
                        raise Exception("测试计划不存在")
                    DatabaseHelper.update_model(data, plan, user)
        except Exception as e:
            PityTestPlanDao.log.error(f"编辑测试计划失败: {str(e)}")
            raise Exception(f"编辑失败: {str(e)}")
    @staticmethod
    async def delete_test_plan(id: int, user: int):
        try:
            async with async_session() as session:
                async with session.begin():
                    query = await session.execute(
                        select(PityTestPlan).where(PityTestPlan.id == plan.id, PityTestPlan.deleted_at == 0))
                    data = query.scalars().first()
                    if data is None:
                        raise Exception("测试计划不存在")
                    DatabaseHelper.delete_model(data, user)
        except Exception as e:
            PityTestPlanDao.log.error(f"删除测试计划失败: {str(e)}")
            raise Exception(f"删除失败: {str(e)}")
    @staticmethod
    async def query_test_plan(id: int) -> PityTestPlan:
        try:
            async with async_session() as session:
                sql = select(PityTestPlan).where(PityTestPlan.deleted_at == 0, PityTestPlan.id == id)
                data = await session.execute(sql)
                return data.scalars().first()
        except Exception as e:
            PityTestPlanDao.log.error(f"获取测试计划失败: {str(e)}")
            raise Exception(f"获取测试计划失败: {str(e)}")

基本思路差不多,老CRUD了!这里就不多说了,对sqlalchemy的async内容不了解的可以去官网看看demo。


这个query方法,是给定时任务查询测试计划数据使用。由于做了软删除,会导致经常忘记带上deleted_at==0的条件

编写相关接口(app/routers/testcase/testplan.py)



from fastapi import Depends
from app.dao.test_case.TestPlan import PityTestPlanDao
from app.handler.fatcory import PityResponse
from app.models.schema.test_plan import PityTestPlanForm
from app.routers import Permission
from app.routers.testcase.testcase import router
from config import Config
@router.get("/plan/list")
async def list_test_plan(page: int, size: int, project_id: int = None, name: str = "", user_info=Depends(Permission())):
    try:
        data, total = await PityTestPlanDao.list_test_plan(page, size, project_id, name)
        return PityResponse.success_with_size(PityResponse.model_to_list(data), total=total)
    except Exception as e:
        return PityResponse.failed(str(e))
@router.get("/plan/insert")
async def insert_test_plan(form: PityTestPlanForm, user_info=Depends(Permission(Config.MANAGER))):
    try:
        await PityTestPlanDao.insert_test_plan(form, user_info['id'])
        return PityResponse.success()
    except Exception as e:
        return PityResponse.failed(str(e))
@router.get("/plan/update")
async def update_test_plan(form: PityTestPlanForm, user_info=Depends(Permission(Config.MANAGER))):
    try:
        await PityTestPlanDao.update_test_plan(form, user_info['id'])
        return PityResponse.success()
    except Exception as e:
        return PityResponse.failed(str(e))
@router.get("/plan/delete")
async def delete_test_plan(id: int, user_info=Depends(Permission(Config.MANAGER))):
    try:
        await PityTestPlanDao.delete_test_plan(id, user_info['id'])
        return PityResponse.success()
    except Exception as e:
        return PityResponse.failed(str(e))

这边我们把测试计划的增删改权限赋予MANAGER,但其实最好是能给对应的项目经理,不过那样会稍微复杂点。我们暂时先偷个懒,或许再完善


今天的内容就分享到这里,下节会介绍测试计划如何和APScheduler结合起来,之后便是编写测试计划的前端页面部分,完成定时任务功能。



相关文章
|
19天前
|
测试技术
自动化测试登录后的功能
在自动化测试的时候,往往许多功能需要登录以后才可以进行操作的,在这里我介绍一种方法,在登录以后将Cookies信息存入本地文件,在测试登录以后操作的时候再从本地文件把信息调出来存入Cookies
33 4
|
28天前
|
关系型数据库 MySQL 测试技术
【分享】AgileTC测试用例管理平台使用分享
AgileTC 是一个脑图样式测试用例管理平台,支持用例设计、执行与团队协作,帮助测试人员高效管理测试流程。
172 116
【分享】AgileTC测试用例管理平台使用分享
|
26天前
|
人工智能 数据可视化 测试技术
AI测试平台自动遍历:低代码也能玩转全链路测试
AI测试平台的自动遍历功能,通过低代码配置实现Web和App的自动化测试。用户只需提供入口链接或安装包及简单配置,即可自动完成页面结构识别、操作验证,并生成可视化报告,大幅提升测试效率,特别适用于高频迭代项目。
|
1月前
|
JSON 测试技术 API
Apipost与Apifox测试功能对决,谁更适合开发者?
在API开发中,调试工具的选择至关重要。本文对比了国产工具Apipost与Apifox的功能差异,涵盖调试能力、环境管理、团队协作、文档生成、自动化测试等方面。Apifox在细节处理、协作支持及生态集成上表现更优,适合复杂项目与团队开发;而Apipost则适合基础调试需求。通过全面评估,开发者可依据项目特点选择合适工具,提升开发效率与质量。
Apipost与Apifox测试功能对决,谁更适合开发者?
|
1月前
|
人工智能 测试技术 调度
写用例写到怀疑人生?AI 智能测试平台帮你一键生成!
霍格沃兹测试开发学社推出AI智能测试用例生成功能,结合需求文档一键生成高质量测试用例,大幅提升效率,减少重复劳动。支持自定义提示词、多文档分析与批量管理,助力测试人员高效完成测试设计,释放更多时间投入核心分析工作。平台已开放内测,欢迎体验!
|
1月前
|
人工智能 测试技术 项目管理
测试不再碎片化:AI智能体平台「项目资料套件」功能上线!
在实际项目中,需求文档分散、整理费时、测试遗漏等问题常困扰测试工作。霍格沃兹推出AI智能体测试平台全新功能——项目资料套件,可将多个关联文档打包管理,并一键生成测试用例,提升测试完整性与效率。支持套件创建、文档关联、编辑删除及用例生成,适用于复杂项目、版本迭代等场景,助力实现智能化测试协作,让测试更高效、更专业。
|
1月前
|
存储 人工智能 测试技术
用AI提升测试效率:智能体平台的「需求文档管理」功能上线啦!
霍格沃兹测试开发学社推出AI智能体测试平台,全新「需求文档管理」功能助力高效测试准备。集中管理需求文档,支持多种上传方式,智能生成测试用例,提升测试效率与准确性,助力迈向智能化测试新时代。
|
13天前
|
人工智能 自然语言处理 前端开发
深度解析Playwright MCP:功能、优势与挑战,AI如何提升测试效率与覆盖率
Playwright MCP通过AI与浏览器交互,实现自然语言驱动的自动化测试。它降低门槛、提升效率,助力测试工程师聚焦高价值工作,是探索性测试与快速验证的新利器。
|
28天前
|
人工智能 自然语言处理 测试技术
AI测试平台的用例管理实践:写得清晰,管得高效,执行更智能
在测试过程中,用例分散、步骤模糊、回归测试效率低等问题常困扰团队。霍格沃兹测试开发学社推出的AI测试平台,打通“用例编写—集中管理—智能执行”全流程,提升测试效率与覆盖率。平台支持标准化用例编写、统一管理操作及智能执行,助力测试团队高效协作,释放更多精力优化测试策略。目前平台已开放内测,欢迎试用体验!
|
4月前
|
Java 测试技术 容器
Jmeter工具使用:HTTP接口性能测试实战
希望这篇文章能够帮助你初步理解如何使用JMeter进行HTTP接口性能测试,有兴趣的话,你可以研究更多关于JMeter的内容。记住,只有理解并掌握了这些工具,你才能充分利用它们发挥其应有的价值。+
750 23

热门文章

最新文章