测试平台系列(79) 编写Redis配置功能(下)

本文涉及的产品
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
简介: 编写Redis配置功能(下)

大家好~我是米洛


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


欢迎关注我的龚仲耗测试开发坑货,获取最新文章教程!

回顾


上一节我们提出了优化Dao逻辑的想法,那今天就试着来兑现之,并运用到Redis配置管理的开发中去。

初步构思list方法


我们在dao/init.py新建类: Mapper,以后所有的dao类都继承自它。

想想list需要什么,一般需要,字段参数是like还是等于这3个重要的信息。

明白这个以后,我们的伪代码就好编写了:


# 1. 获取session
async with async_session() as session:
  condition = [model.deleted_at == 0]
  # 2. 根据参数里的字段,字段值构造查询条件
  DatabaseHelper.where(字段,字段值,condition)
  # 3. 查询出结果,后续与原来的方式一致,就不写了

可以看到,这边除了需要上面的3个信息以外,还需要try去写入日志,也就是说需要我们平时经常创建的log成员变量,还需要model这个类,否则你不知道改的是什么表。

思考


我们平时的参数,都是key-value的形式,一旦value不为空,那么说明我们要根据这个条件来查询。

而model和log,我们可以通过类的装饰器,由子类传递给父类(这里会用到setattr和getattr)


class Mapper(object):
    log = None
    model = None
    @classmethod
    async def list_data(cls, **kwargs):
        try:
            async with async_session() as session:
                # 构造查询条件,默认是数据未被删除
                condition = [getattr(cls.model, "deleted_at") == 0]
                # 遍历参数,当参数不为None的时候传递
                for k, v in kwargs.items():
                    # 判断是否是like的情况 TODO: 这里没支持in查询
                    like = v is not None and len(v) > 2 and v.startswith("%") and v.endswith("%")
                    # 如果是like模式,则使用Model.字段.like 否则用 Model.字段 等于
                    DatabaseHelper.where(v, getattr(cls.model, k).like(v) if like else getattr(cls.model, k) == v,
                                         condition)
                result = await session.execute(select(cls.model).where(*condition))
                return result.scalars().all()
        except Exception as e:
            # 这边调用cls本身的log参数,写入日志+抛出异常
            cls.log.error(f"获取{cls.model}列表失败, error: {e}")
            raise Exception(f"获取数据失败")

注释写的比较详细,由于现在字段是个变量,所以我们不能用model.字段来取值,所以取而代之的是getattr(model, 字段)。不熟悉的朋友可以去搜索下getattr

这样,一个粗略的list方法就写好了,但是这个是不带分页的,所以我们还需要补充一个分页的模式,其实也很简单。


@classmethod
    async def list_data_with_pagination(cls, page, size, **kwargs):
        try:
            async with async_session() as session:
                condition = [getattr(cls.model, "deleted_at") == 0]
                for k, v in kwargs.items():
                    like = v is not None and len(v) > 2 and v.startswith("%") and v.endswith("%")
                    sql = DatabaseHelper.where(v, getattr(cls.model, k).like(v) if like else getattr(cls.model, k) == v,
                                               condition)
                return await DatabaseHelper.pagination(page, size, session, sql)
        except Exception as e:
            cls.log.error(f"获取{cls.model}列表失败, error: {e}")
            raise Exception(f"获取数据失败")

基本上长的差不多,只是最后返回那里,调用了pagination相关方法。

看看dao装饰器


5.jpg

这也是为什么要用classmethod而不是staticmethod的原因

我们很坏,把这个装饰器套到子类上,并把model和log传给父类。毕竟方法都是在调用父类的方法,父类无法直接拿到子类的数据

6.jpg

用的话,就是把model和log传入dao参数

这样就避免了在调用list方法的时候,还需要传入model和log的尴尬情况。

完善其他方法


list搞定以后,其他的还会远吗?但还真的好像还有点问题,因为我们一般会有一些重名判断,但没关系,我们可以编写一个query方法。


@classmethod
    def query_wrapper(cls, **kwargs):
        condition = [getattr(cls.model, "deleted_at") == 0]
        # 遍历参数,当参数不为None的时候传递
        for k, v in kwargs.items():
            # 判断是否是like的情况 TODO: 这里没支持in查询
            like = v is not None and len(v) > 2 and v.startswith("%") and v.endswith("%")
            # 如果是like模式,则使用Model.字段.like 否则用 Model.字段 等于
            DatabaseHelper.where(v, getattr(cls.model, k).like(v) if like else getattr(cls.model, k) == v,
                                 condition)
        return select(cls.model).where(*condition)
    @classmethod
    async def query_record(cls, **kwargs):
        try:
            async with async_session() as session:
                sql = cls.query_wrapper(**kwargs)
                result = await session.execute(sql)
                return result.scalars().first()
        except Exception as e:
            cls.log.error(f"查询{cls.model}失败, error: {e}")
            raise Exception(f"查询数据失败")

由于查询方法太过于通用,所以抽成了query_wrapper方法。

  • 正常编写insert接口


@classmethod
    async def insert_record(cls, model):
        try:
            async with async_session() as session:
                async with session.begin():
                    session.add(model)
                    await session.flush()
                    session.expunge(model)
                    return model
        except Exception as e:
            cls.log.error(f"添加{cls.model}记录失败, error: {e}")
            raise Exception(f"添加记录失败")

这边返回了model,如果不需要也可以不用,但咱还是给返回。

  • insert接口层

7.jpg

image

和以前一样,先查,如果没有再关掉。

但这样会产生2个session,开->关->开->关

但也解耦了查询和插入2个操作。

  • 编写删除和修改方法


@classmethod
    async def update_record_by_id(cls, user, model, not_null=False):
        try:
            async with async_session() as session:
                async with session.begin():
                    query = cls.query_wrapper(id=model.id)
                    result = await session.execute(query)
                    original = result.scalars().first()
                    if original is None:
                        raise Exception("记录不存在")
                    DatabaseHelper.update_model(original, model, user, not_null)
                    await session.flush()
                    session.expunge(original)
                    return original
        except Exception as e:
            cls.log.error(f"更新{cls.model}记录失败, error: {e}")
            raise Exception(f"更新记录失败")
    @classmethod
    async def delete_record_by_id(cls, user, id):
        """
        逻辑删除
        :param user:
        :param id:
        :return:
        """
        try:
            async with async_session() as session:
                async with session.begin():
                    query = cls.query_wrapper(id=id)
                    result = await session.execute(query)
                    original = result.scalars().first()
                    if original is None:
                        raise Exception("记录不存在")
                    DatabaseHelper.delete_model(original, user)
        except Exception as e:
            cls.log.error(f"删除{cls.model}记录失败, error: {e}")
            raise Exception(f"删除记录失败")
    @classmethod
    async def delete_by_id(cls, id):
        """
        物理删除
        :param id:
        :return:
        """
        try:
            async with async_session() as session:
                async with session.begin():
                    query = cls.query_wrapper(id=id)
                    result = await session.execute(query)
                    original = result.scalars().first()
                    if original is None:
                        raise Exception("记录不存在")
                    session.delete(original)
        except Exception as e:
            cls.log.error(f"逻辑删除{cls.model}记录失败, error: {e}")
            raise Exception(f"删除记录失败")

删除这边支持了物理删除和逻辑删除,当然我们一般是用软删除

完善其他接口


8.jpg

image

可以看到删除和修改和以前是差不多的(也是内部进行数据是否存在判断),这些步骤做完。redis的管理工作就可以顺利进行了,接着我们需要为之编写页面咯!

由于是很基础的表格页面,所以我们不赘述。下一节我们会利用配置的redis连接编写redisManager,管理我们的连接数据,为之后在线执行redis以及将它作为前置条件打下基础。



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8天前
|
JSON 移动开发 监控
快速上手|HTTP 接口功能自动化测试
HTTP接口功能测试对于确保Web应用和H5应用的数据正确性至关重要。这类测试主要针对后台HTTP接口,通过构造不同参数输入值并获取JSON格式的输出结果来进行验证。HTTP协议基于TCP连接,包括请求与响应模式。请求由请求行、消息报头和请求正文组成,响应则包含状态行、消息报头及响应正文。常用的请求方法有GET、POST等,而响应状态码如2xx代表成功。测试过程使用Python语言和pycurl模块调用接口,并通过断言机制比对实际与预期结果,确保功能正确性。
35 3
快速上手|HTTP 接口功能自动化测试
|
30天前
|
存储 Ubuntu 安全
ROS2教程02 ROS2的安装、配置和测试
本文是关于ROS2(机器人操作系统2)的安装、配置和测试的教程。内容包括使用一键安装脚本快速安装ROS2 Humble版,手动安装步骤,设置语言环境、添加软件源、更新软件包、安装ROS2桌面版和开发工具,配置ROS2环境,创建工作空间,配置ROS2领域以避免网络冲突,以及如何删除ROS2。此外,还包括了测试ROS2是否安装成功的两个案例:基本的Topic通信测试和使用Turtlesim演示程序。适用于Ubuntu 22.04操作系统。
60 1
ROS2教程02 ROS2的安装、配置和测试
|
26天前
|
NoSQL Redis 容器
【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
|
27天前
|
缓存 NoSQL 测试技术
【Azure Redis 缓存】Azure Redis 功能性讨论三: 调优参数配置
【Azure Redis 缓存】Azure Redis 功能性讨论三: 调优参数配置
|
29天前
|
XML Web App开发 数据挖掘
Postman接口测试工具全解析:功能、脚本编写及优缺点探讨
文章详细分析了Postman接口测试工具的功能、脚本编写、使用场景以及优缺点,强调了其在接口自动化测试中的强大能力,同时指出了其在性能分析方面的不足,并建议根据项目需求和个人偏好选择合适的接口测试工具。
38 1
|
30天前
|
测试技术 Android开发 iOS开发
Appium 是一个开源的自动化测试框架,它支持多种平台和多种编程语言
Appium是一款开源自动化测试框架,支持iOS和Android多平台及多种编程语言。通过WebDriver协议,开发者可编写自动化测试脚本。在iPhone上实现屏幕点击等操作需安装Appium及其依赖,启动服务器,并设置所需的测试环境参数。利用Python等语言编写测试脚本,模拟用户交互行为,最后运行测试脚本来验证应用功能。对于iPhone测试,需准备真实设备或Xcode模拟器。
59 1
|
22天前
|
Web App开发 敏捷开发 测试技术
自动化测试之美:使用Selenium WebDriver进行网页功能验证
【8月更文挑战第29天】在数字时代,软件质量是企业竞争力的关键。本文将深入探讨如何通过Selenium WebDriver实现自动化测试,确保网页应用的可靠性和性能。我们将从基础设置到编写测试用例,逐步引导读者掌握这一强大的测试工具,同时分享实战经验,让测试不再是开发的负担,而是质量保证的利器。
|
22天前
|
测试技术
一款功能完善的智能匹配1V1视频聊天App应该通过的测试CASE
文章列举了一系列针对1V1视频聊天App的测试用例,包括UI样式、权限请求、登录流程、匹配逻辑、消息处理、充值功能等多个方面的测试点,并标注了每个测试用例的执行状态,如通过(PASS)、失败(FAIL)或需要进一步处理(延期修改、待定、方案再定等)。
27 0
|
27天前
|
存储 缓存 NoSQL
【Azure Redis 缓存】由Azure Redis是否可以自定义密码而引申出Azure PaaS的Redis服务是否可以和自建的Redis进行主从配置呢?
【Azure Redis 缓存】由Azure Redis是否可以自定义密码而引申出Azure PaaS的Redis服务是否可以和自建的Redis进行主从配置呢?
|
27天前
|
缓存 NoSQL 网络协议
【Azure Redis 缓存】如何使得Azure Redis可以仅从内网访问? Config 及 Timeout参数配置
【Azure Redis 缓存】如何使得Azure Redis可以仅从内网访问? Config 及 Timeout参数配置