Skip to content

128.改造请求参数提取方式(2)

大家好~我是米洛
我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持。
欢迎关注我的公众号米洛的测开日记,一起交流学习!

写在前面

本文代码其实很多,但文字更多,主要是想讲明白现在的情况。敲者莫盲目,要一起思考。如果只是观望,那就直接看点图片了解下功能即可。

回顾

上一节我们编写了后端部分代码,你可能发现了一些bug,不过没关系,虽然没有自测,但是要对自己的curd有信心,实在不行,前端调试的时候,还可以改嘛~

不过由于前端比较复杂(主要是我写的臭,我就不多展示了)。大致说一下注意事项吧:

  1. 我们需要一个可动态伸缩的表格组件,这种事件只能自己写,或者可以采用下面这种组件:

因为这样最容易实现,但是体验没那么好(需要在modal里面编写内容,操作更繁琐)

  1. 由于历史原因,我们的用例是存在添加态和编辑态的,添加态指的是啥都没有,我们把用例的全部数据一次性添加到数据库,其中就包括:

  2. 用例数据 (用例表)

  3. 断言数据(断言表)
  4. 测试数据(测试数据表)

等等其他的表。但是编辑态,是我们已经存在用例数据的基础上,对其他表进行的修改。比如我已经有一条用例,现在要修改它,比如给它添加一条新的断言,如果沿用添加态的话,我们会需要更新以上N个表,十分复杂。但其实我们只需要添加一条断言数据即可。

所以以上就是我们的历史现状,有了编辑态和添加态。在我之前设计的平台里,是把断言/前后置条件放到用例表的一个字段,所以不存在这样的问题。在熟悉了这2个模式之后,我们的出参提取接口也将变得困难。


其实主要就是以上2个原因吧,为了方便,我们打不算采用和之前的前后置/断言一样的模式(支持添加态和编辑态)。

为什么呢?因为添加态,添加或编辑一个断言(此时case还没有),我的逻辑是暂存断言数据,而编辑态,我们添加或编辑一个断言,需要立刻生效(入库)。为此我们增删改,大约有6套逻辑

有没有一种可能,去规避这些复杂逻辑,同时支持添加/编辑态呢?那就只有批量更新这一条路了。

所谓批量更新,即把所有出参数据暂存为一个数组,无论是添加/编辑态,我们只有最后保存case的时候,才发生变化。

说简单一点就是点击保存用例按钮的时候,才去改变数据库,其他时候数据都都放到浏览器本地内存。

但这里还有几个要注意的点:

  1. 历史数据的删除(比如我删除了一条旧的,添加了一条新的,我们批量更新的时候要对比)

对于这一点,简单的办法是:先删除case_id下的所有出参数据,接着全部重新添加,但这样会导致数据量越来越多(软删除),物理删除的话,会导致这个表id自增得飞快,不过影响不是很大。

  1. 需要用事务保证,我们必须保证所有数据都编辑完毕。

这样看起来,我们后端上次写的所谓CRUD,就没有太多作用了。因为我们可能只需要一个接口:

/parameters/update/bacth

改造后端

改造后的dao文件:

import time
from datetime import datetime
from typing import List

from sqlalchemy import select, update

from app.crud import Mapper
from app.middleware.RedisManager import RedisHelper
from app.models import async_session
from app.models.out_parameters import PityTestCaseOutParameters
from app.schema.testcase_out_parameters import PityTestCaseOutParametersForm
from app.utils.decorator import dao
from app.utils.logger import Log


@dao(PityTestCaseOutParameters, Log("PityTestCaseOutParametersDao"))
class PityTestCaseOutParametersDao(Mapper):

    @classmethod
    async def should_remove(cls, before, after):
        """
        找出要删除的数据
        :param before:
        :param after:
        :return:
        """
        data = []
        for b in before:
            for a in after:
                if a.id == b.id:
                    break
            else:
                data.append(b.id)
        return data

    @classmethod
    @RedisHelper.up_cache("dao")
    async def update_many(cls, case_id: int, data: List[PityTestCaseOutParametersForm], user_id: int):
        result = []
        try:
            async with async_session() as session:
                async with session.begin():
                    source = await session.execute(select(PityTestCaseOutParameters).where(
                        PityTestCaseOutParameters.case_id == case_id,
                        PityTestCaseOutParameters.deleted_at == 0,
                    ))
                    before = source.scalars().all()
                    should_remove = await cls.should_remove(before, data)
                    for item in data:
                        if item.id is None:
                            # add
                            temp = PityTestCaseOutParameters(**item.dict(), case_id=case_id, user_id=user_id)
                            session.add(temp)
                        else:
                            query = await session.execute(select(PityTestCaseOutParameters).where(
                                PityTestCaseOutParameters.id == item.id,
                            ))
                            temp = query.scalars().first()
                            if temp is None:
                                # 走新增逻辑
                                temp = PityTestCaseOutParameters(**item.dict(), case_id=case_id, user_id=user_id)
                                session.add(temp)
                            else:
                                temp.name = item.name
                                temp.case_id = case_id
                                temp.expression = item.expression
                                temp.source = item.source
                                temp.match_index = item.match_index
                                temp.update_user = user_id
                                temp.updated_at = datetime.now()
                        await session.flush()
                        session.expunge(temp)
                        result.append(temp)
                    if should_remove:
                        await session.execute(
                            update(PityTestCaseOutParameters).where(
                                PityTestCaseOutParameters.id.in_(should_remove)).values(
                                deleted_at=int(time.time() * 1000)))
            return result
        except Exception as e:
            cls.log.error(f"批量更新出参数据失败: {e}")
            raise Exception(f"批量更新出参数据失败: {e}")

should_remove方法,找出了应该删除的旧的数据,接着里面的内容就是批量更新的部分:

  • 先查出这个case下的所有出参数据
  • 找出该删除的
  • 遍历最新的数据,如果id存在,说明已经有数据了,有则更新,没有则新增
  • 删除该删除的数据

以上就是方法的全过程,但采用了事务保证,所以一旦有数据失败,我们也不会改变上一次的数据。在最后我们返回了所有编辑好的数据(注意这里会带上新增好的id重新给予前端,前端下次请求回来也能带上具体的id)

添加接口

其实是不需要这个接口的,因为我们会在更新用例的时候,顺便更新这个数据,也就不拆一个新的接口出来了。直接需要在用例form里面加上List[PityTestCaseOutParameters]字段。

我们在update用例的接口加上此方法

同时用例表单也加入了这个字段,那这样编辑态就搞定了。接着我们还要处理添加态,注意添加case的时候,出参数据只需要新增就好了,因为历史数据肯定不存在啊。

还记得我们编写过的一次性写入所有case数据的接口吗?

我们在这里也插入out_parameters,类似测试数据,前后置条件和断言。

这次改动内容很多,详情可看github:

前端调整

由于编辑态的时候,我们需要点一下编辑按钮才能编辑case,但实际上我们改动的是下面的出参提取数据,但让人再去点下编辑+保存,是有点太复杂了。

所以我们把接口请求里面的保存按钮,移到tab最后侧:

这样就解决了问题,我们看下crud正常不?

查询

查询的地方,我们也得把用例的参数数据拉取出来。

看看gif


这种表单编写的体验还是远远不如pts/postman/apipost这种工具,前路慢慢,目前还能发现一些小bug,任重道远,继续努力吧。越写前端越没信心,哎~

下一期讲讲如何提取参数,以及如何使用参数。

我是米洛,一直陪伴各位学习!免费的小黄心,帮我点一个吧!

项目地址: https://github.com/wuranxu/pity