【测试联调】如何在前后端测试联调时优雅的构造异常场景

目录

背景

使用iptables实现

利用iptables丢弃某ip数据包

使用 -L 列出所有规则

IP 连通性 通信 测试

插入一条规则,丢弃此ip 的所有协议请求

列出所有规则

测试 丢弃规则内的IP 连通性

清除 规则列表的 限制

模拟ip进行丢包50%的处理。

mysql proxy 代理

proxy代码

直接使用pymysql 测试

Python 版本低于3.7

其他扩展

总结

资料获取方法


背景

当前的应用都使用了前后端分离的架构,前后端系统需要协同以实现各种功能。后端系统通常负责处理业务逻辑、数据存储和与其他服务的交互,而前端则负责用户界面和用户交互。而在前后端数据交互的过程中,各种异常和错误都有可能发生,确认异常发生时前后端系统的处理是否合理是测试验证中非常重要的一环。

在上一篇博客中我介绍了如何使用测试桩来隔离对环境的依赖,这次我们一起看看如何使用异常注入来应对联调中的异常场景。

使用iptables实现

在系统异常中,数据库连接失败、第三方服务不可用等都是比较典型的场景。常见的验证手段往往是前端的同学告知后台同学开启网络隔离,然后再进行验证。

利用iptables丢弃某ip数据包

使用 -L 列出所有规则

具体操作:

$  iptables     -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
IP 连通性 通信 测试
#  检查发现能 是否能正常 ping通
$  ping {数据库/后端地址IP}
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.*: icmp_seq=1 ttl=61 time=0.704 ms
64 bytes from 1.1.1.*: icmp_seq=2 ttl=61 time=0.802 ms           
^C
--- 1.1.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.704/0.753/0.802/0.049 ms
插入一条规则,丢弃此ip 的所有协议请求
$  iptables  -I   INPUT   -p   all   -s {数据库/后端地址IP}   -j   DROP
列出所有规则
$ iptables  -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  1.1.1.*        anywhere

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
测试 丢弃规则内的IP 连通性
$ ping 1.1.1.*
PING 1.1.1.1 (1.1.1.*) 56(84) bytes of data.
^C
--- 1.1.1.1 ping statistics ---
85 packets transmitted, 0 received, 100% packet loss, time 84312ms
清除 规则列表的 限制
$  iptables     -F
模拟ip进行丢包50%的处理。
iptables -I INPUT -s {后端IP} -m statistic --mode random --probability 0.5 -j DROP

上面这种方式能实现相关的联调,但是有两点可以改进的地方:

  • 这个方式最大的限制是会影响所有的调用这个系统的测试人员。
  • 需要人为的介入,每次都需要人为操作

那么有没有侵入更小,更优雅的异常场景验证方式呢?答案是肯定的。

mysql proxy 代理

更优雅的方式:写一个mysql proxy代理,让后端svr 直接连接这个proxy,proxy再连接真实的mysql。

  • 普通请求经过proxy时,proxy直接转发给真实mysql,并把mysql 的回包正常返回给调用端。
  • 当收到某个关键字(timeout)时,直接断开TCP连接,模拟连接DB超时场景

proxy代码

import asyncio

# 真实mysqlIP端口
mysql_host = settings.MYSQL_HOST
mysql_port = settings.MYSQL_PORT

# 处理客户端连接
async def handle_connection(client_reader, client_writer):
    # 连接到实际的 MySQL 服务器
    mysql_reader, mysql_writer = await asyncio.open_connection(mysql_host, mysql_port)

    # 转发握手包
    handshake_packet = await mysql_reader.read(4096)
    client_writer.write(handshake_packet)
    await client_writer.drain()

    # 处理客户端认证请求
    auth_packet = await client_reader.read(4096)
    mysql_writer.write(auth_packet)
    await mysql_writer.drain()

    # 转发认证响应
    auth_response = await mysql_reader.read(4096)
    client_writer.write(auth_response)
    await client_writer.drain()

    # 转发请求和响应
    while True:
        data = await client_reader.read(4096)
        if not data:
            break
        sql_query = data[5:].decode('utf-8')
        if "timeout" in sql_query:  # sql 包含timeout 关键字时,直接关闭连接
            await asyncio.sleep(1)
            break
        else:
            mysql_writer.write(data)
            await mysql_writer.drain()
            response = await mysql_reader.read(4096)
            client_writer.write(response)
            await client_writer.drain()
    # 关闭连接
    client_writer.close()
    mysql_writer.close()

async def main(host, port):
    server = await asyncio.start_server(handle_connection, host, port)
    async with server:
        await server.serve_forever()

# 使用示例
if __name__ == "__main__":
    # 本地监听 3307, svr 连接到3307
    host = "0.0.0.0"
    port = 3307

    asyncio.run(main(host, port))
直接使用pymysql 测试

下面的代码会在执行到第二条select 语句时超时:

import pymysql

connection = pymysql.connect(
    host="192.168.31.76",
    port=8899,
    # 真实mysql 账户信息
    user="{user}",
    password="{password}",
    database="mydb",

)

curs = connection.cursor()
curs.execute("select * from user where name='bingo';")
print(curs.fetchall())

curs.execute("insert into user set name='bingtime';")
connection.commit()

curs.execute("select * from user where name='timeoutbingo';")
print(curs.fetchall())
curs.close()
connection.close()
Python 版本低于3.7

低版本的Python没有asyncio.run 和server.serve_forever()需要修改main函数.

# 主函数,启动服务器并监听连接
async def main(host, port):
    server = await asyncio.start_server(handle_connection, host, port)
    try:
        # Python 3.6.5 中没有 server.serve_forever() 方法,所以需要使用一个无限循环来保持服务器运行。
        # 我们使用 asyncio.sleep() 来避免阻塞事件循环,使其可以处理其他任务,如新连接。
        while True:
            await asyncio.sleep(3600)  # 1 hour
    except KeyboardInterrupt:
        pass
    finally:
        server.close()
        await server.wait_closed()


# 使用示例
if __name__ == "__main__":
    host = "0.0.0.0"
    port = 3307

    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main(host, port))
    except KeyboardInterrupt:
        pass
    finally:
        loop.close()

其他扩展

写一个proxy,监听来往的SQL 语句:

import pymysql
import socket
import threading
# 配置
SERVER_ADDRESS = (settings.MYSQL_HOST, settings.MYSQL_PORT)  # 真实MySQL 服务器地址
PROXY_ADDRESS = ('0.0.0.0', 8899)  # 监听代理服务器地址

def print_query(data):
    try:
        command = data[4]
        if command == pymysql.constants.COMMAND.COM_QUERY:
            query = data[5:].decode("utf-8")
            print(f"SQL: {query}")
    except Exception as e:
        print(f"Error parsing packet: {e}")

def forward_data(src_socket, dst_socket, parser=None):
    while True:
        try:
            data = src_socket.recv(4096)
            if not data:
                break
            if parser:
                parser(data)
            dst_socket.sendall(data)
        except OSError as e:
            if e.errno == 9:
                break
            else:
                raise

def main():
    # 创建代理服务器套接字
    proxy_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    proxy_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    proxy_socket.bind(PROXY_ADDRESS)
    proxy_socket.listen(5)
    print(f"Proxy server listening on {PROXY_ADDRESS}")

    while True:
        client_socket, client_address = proxy_socket.accept()
        print(f"Client {client_address} connected")

        # 连接到 MySQL 服务器
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.connect(SERVER_ADDRESS)

        try:
            # 创建线程转发客户端数据到服务器
            client_to_server = threading.Thread(target=forward_data, args=(client_socket, server_socket, print_query))
            client_to_server.start()

            # 创建线程转发服务器数据到客户端
            server_to_client = threading.Thread(target=forward_data, args=(server_socket, client_socket))
            server_to_client.start()

            # 等待线程完成
            client_to_server.join()
            server_to_client.join()

        finally:
            client_socket.close()
            server_socket.close()
            print(f"Client {client_address} disconnected")

if __name__ == "__main__":
    main()

如果使用上面的pymysql测试代码执行,会打印如下的信息:

Client ('127.0.0.1', 57184) connected
SQL: SET AUTOCOMMIT = 0
SQL: select * from user where name='bingo';
SQL: insert into user set name='bing21211';
SQL: COMMIT
SQL: select * from user where name='bingo';
SQL: select * from user where name='bingo';
SQL: select * from user where name='timeoutbingo';
Client ('127.0.0.1', 57184) disconnected

总结

通过实现合适的异常处理机制,可以确保用户在遇到问题时获得有用的反馈,验证这些处理机制能提高系统的稳定性和安全性。iptables 功能强大但是需要手动操作,mysql proxy代理功能直接,但是应用场景较为有限,大家可以根据实际情况进行选择。


资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/56037.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【业务功能篇56】SpringBoot 日志SLF4J Logback

3.5.1 日志框架分类与选择 3.5.1.1 日志框架的分类 日志门面 (日志抽象)日志实现JCL(Jakarta Commons Logging) SLF4J(Simple Logging Facade for Java)Jul(Java Util Logging) , Log4j , Log4j2 , Logback 记录型日志框架 Jul (Java Util Logging):JDK中的日志…

【电网技术复现】考虑实时市场联动的电力零售商鲁棒定价策略(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

阿里云AK创建

要在阿里云上创建 Access Key(AK),您需要按照以下步骤进行操作: 登录到阿里云控制台([https://www.aliyun.com/?utm_contentse_1014243503))。 点击右上方的主账号,点击“AccessKey管理”。 …

NLP实战9:Transformer实战-单词预测

目录 一、定义模型 二、加载数据集 三、初始化实例 四、训练模型 五、评估模型 🍨 本文为[🔗365天深度学习训练营]内部限免文章(版权归 *K同学啊* 所有) 🍖 作者:[K同学啊] 模型结构图: &a…

idea 安装 插件jrebel 报错LS client not configured.

这个报错找了好久,有博主说版本不对,我脑子没反应过来以为是随便换一个低版本的就行,没想到只能是2022.4.1 这个版本才行 一定要用jrebel 2022.4.1的插件版本!!!!! 插件下载地址&…

[Java] 观察者模式简述

模式定义:定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,他的所有依赖者都会收到通知并且更新 依照这个图,简单的写一个代码 package Section1.listener;import java.ut…

安卓耗电量分析

这里写自定义目录标题 耗电原因分析分析类型 生成分析数据batterystats操作步骤:生成report报告 battery-historian手动编译安装容器安装内容解析 耗电原因分析 下文有阐述,很详细 https://www.cnblogs.com/SA226343/p/6047543.html https://www.cnblogs.com/mytec…

Spring框架——IOC配置文件方式

Spring框架的概述和入门 目录 Spring框架的概述和入门 什么是Spring框架 Spring框架的特点 Spring框架的IOC核心功能快速入门 Spring框架中的工厂(了解) Spring 创建Bean对象的三种方式 Spring框架的Bean管理的配置文件方式 Spring框架中标签的配…

Python Web开发(详细教程)

前言 PythonWeb开发是使用Python语言进行Web应用程序开发的过程。Python是一种简洁、易读且功能强大的编程语言,因此在Web开发领域广受欢迎。 一、PythonWeb开发简介 PythonWeb开发可以涵盖多个方面,包括服务器端开发、数据库管理、前端设计和API开发…

SQL-每日一题【1084. 销售分析III】

题目 Table: Product Table: Sales 编写一个SQL查询,报告2019年春季才售出的产品。即仅在2019-01-01至2019-03-31(含)之间出售的商品。 以 任意顺序 返回结果表。 查询结果格式如下所示。 示例 1: 解题思路 前置知识 between and between…

SQL-每日一题【1158. 市场分析 I】

题目 Table: Users Table: Orders Table: Items 请写出一条SQL语句以查询每个用户的注册日期和在 2019 年作为买家的订单总数。 以 任意顺序 返回结果表。 查询结果格式如下。 示例 1: 解题思路 1.题目要求我们查询每个用户的注册日期和在 2019 年作为买家的订单总数。我们可…

基于控制屏障函数的安全关键系统二次规划(适用于ACC)(Matlab代码实现)

目录 💥1 概述 📚2 运行结果 🎉3 参考文献 👨‍💻4 Matlab代码 💥1 概述 基于控制屏障函数的安全关键系统二次规划(适用于ACC)是一种用于自适应巡航控制(ACC&#x…

SpringBoot集成jasypt,加密yml配置文件

SpringBoot集成jasypt,加密yml配置文件 一、pom配置二、生成密文代码三、配置3.1、yml加密配置3.2、密文配置3.3、启动配置3.4、部署配置 四、遇到的一些坑 最新项目安全检测,发现配置文件中数据库密码,redis密码仍处理明文状态 一、pom配置…

vscode 前端开发插件 2023

自己记录 安装vscode后必装插件 chinesegit 必装没啥可说 随时更新 1.CSS Navigation CTRL点击类名可跳转到对应样式位置。 如果是scss less的话。css peak插件无法生效 2.GitLens — Git supercharged 可以看到每一行的git提交记录。 3.Auto Rename Tag 可以同步更新…

SPDK的块设备抽象层,从一个简单的示例程序讲起

最早的SPDK仅仅是一个NVMe驱动,但现在的SPDK已经不是原来的SPDK了,其功能涵盖了整个存储栈。为了能够实现丰富的功能,SPDK实现了一个块设备抽象层,其功能与Linux内核的块设备层类似,这个块设备抽象层称为BDEV。 块设备抽象层BDEV在整个SPDK栈中的位置如图所示,它位于中间…

【JavaEE】深入了解Spring中Bean的可见范围(作用域)以及前世今生(生命周期)

【JavaEE】Spring的开发要点总结(4) 文章目录 【JavaEE】Spring的开发要点总结(4)1. Bean的作用域1.1 一个例子感受作用域的存在1.2 通过例子说明作用域的定义1.3 六种不同的作用域1.3.1 singleton单例模式(默认作用域…

hbase基础

hbase安装 tar -zxvf hbase-2.4.11-bin.tar.gz -C . ln -s f hbase-2.4.11-bin hbasemv /export/server/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar /export/server/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar.bakvim conf/regionser…

C++ __builtin_popcount函数作用

__builtin_popcount函数是系统自带的一个返回值是int/long/long long二进制1的个数的函数。 对于int,long, long long分别用一下三种函数: __builtin_popcount(unsigned int n)//返回值为int __builtin_popcountl(unsigned int n)//返回值为…

python与深度学习(九):CNN和cifar10

目录 1. 说明2. cifar10实战2.1 导入相关库2.2 加载数据2.3 数据预处理2.4 数据处理2.5 构建网络模型2.6 模型编译2.7 模型训练2.8 模型保存2.9 模型评价2.10 模型测试2.11 模型训练结果的可视化 3. cifar10的CNN模型可视化结果图4. 完整代码5. 改进后的代码和结果 1. 说明 本…

docker 安装 字体文件

先说一下我当前的 场景 及 环境,这样同学们可以先评估本篇文章是否有帮助。 环境: dockerphp8.1-fpmwindows 之所以有 php,是因为这个功能是使用 php 开发的,其他语言的同学,如果也有使用到 字体文件,那么…