试试流量回放,不用人工写自动化测试case了

news2024/11/29 11:40:55

大家好,我是洋子,接触过接口自动化测试的同学都知道,我们一般要基于某种自动化测试框架,编写自动化case,编写自动化case的依据来源于接口文档,对照接口文档里面的请求参数进行人工添加接口自动化case

其实,对于日常新的服务端需求的迭代,人工一次性补充20个以下接口自动化case,还是可以接受。如果一次性要补充50个以上的接口case,光靠人工去填写就显得非常耗时

还有一种比较常见的场景,一些老的服务模块需要进行迁移或者是重构,这些老模块本身可能也没有接口文档,业务逻辑也没有改变,如何确保原来业务在迁移后的正确运行非常重要,光靠QA进行接口测试也很难保障迁移后的新服务线上没有问题

为了最大程度的覆盖测试,我们可以通过复制线上流量拷贝到线下生成自动化case进行接口的功能测试以及diff测试,这种技术就叫流量回放

什么是流量回放

流量回放从字面意思理解,流量可以理解成互联网上发送和接收数据的量,由于我们网络通信协议一般都是HTTP请求,前后端交互方式一般通过后端API接口,所以流量的形式可以理解成线上的接口请求数量

而回放就是改变接口请求信息的位置,比如存放在到线下的数据库,Redis,或者分布式大数据集群中,也可以不经过存储进行实时回放,使得我们能够对线上流量进行利用

流量回放再通俗理解成两个字,就是引流

流量回放可以将流量进行拷贝后直接使用,也能把流量放大、流量缩小后使用

流量拷贝指的是将线上正式流量通过改写业务逻辑tcpcopy日志回放等方法在线下环境回放,从而达到测试的目的

流量拷贝可以完全模拟线上的流量,从而对复杂的业务场景进行仿真测试,并且不会对线上服务产生影响

一份流量复制多份,每份流量经过修改目的地址后发送到同一台实例上,比如拷贝5份流量到同一台实例,这台实例就能比线上环境同一时间多抗5倍压力,就能实现对线下环境进行压测,这就叫流量放大

那有小伙伴问,能放大,可以缩小吗?当然可以,流量缩小是通过复制部分流量并对其进行处理,以实现流量的缩小。这样做的原因可能是为了减少流量的规模或为了对流量进行进一步的分析和处理

在线下测试中难以覆盖的场景,都可以通过流量拷贝的方式来测试,测试的场景更广泛,覆盖面也更大

业界经验

目前业界也有一些常用的引流解决方案

阿里Doom

Doom是一个将一部分线上真实流量复制并用于自动回归测试的平台。其在应用内部通过aop切面编程方式实现的流量录制和回放功能,由于最底层借助了java的instrument实现aop,因此目前仅支持java应用的接入使用。其原理图如下:
请添加图片描述

TCPCopy

TCPCopy是一种请求复制 (所有基于tcp的packets) 工具,由tcpcopy和intercept组成,其中tcpcopy在线上服务上运行并利用原始socket复制线上流量到测试服务上 (粉红色的线),intercept部署在辅助服务器上,负责接收响应(绿色的线)并回传给tcpcopy (紫色的线),具体见下图:
TCPCopy原理

GoReplay

Goreplay是用 Golang开发的 HTTP 实时流量复制工具。支持流量的放大、缩小,频率限制,还支持把请求记录到文件,方便回放和分析,也支持和 ElasticSearch 集成,将流量存入 ES 进行实时分析GoReplay不是代理,而是监听网络接口上的流量,不需要更改生产基础架构,只需与服务相同的计算机上运行 。具体原理见下图

GoReplay原理

Nginx的ngx_http_mirror_module模块

Nginx 1.13.4 中引入ngx_http_miror_module模块来支持应用层的流量复制。该模块通过mirror配置指令来实现流量复制。如下图,可以通过下面配置来实现复制proxy.local流量到test.local
请添加图片描述

upstream backend {
	server backend.local:10000;
}

upstream test_backend {
	server test.local:20000;
}

server {
	server_name proxy.local;
	listen 8000;

	location / {
		mirror /mirror;
		proxy_pass http://backend;
	}
	
	location = /mirror {
		internal;
		proxy_pass http://test_backend$request_uri;
	}
}

其中每一条mirror配置项对应用户请求的一个副本,可以通过配置多次mirror指令来实现“流量放大”的效果。当然,你也可以将多个副本转发给不同的后端目标系统

流量回放的三种方式

从引流自身来看,主要有3种类型,分别是:主路复制、旁路复制、日志回放

主路复制:

在这里插入图片描述

主路复制指的是在调用链中进行流量拷贝。一种是在业务逻辑中进行流量复制,比如在调用API的过程中,由业务方编写代码逻辑记录请求/响应信息内容;另一种是在框架(如阿里的Doom)处理逻辑中进行流量复制。

  • 优点: 可以高度结合业务逻辑,实现细粒度定制化流量复制,比如可以只针对某些特征的流量进行复制
  • 缺点: 业务逻辑与引流逻辑耦合度较高,功能上相互影响;每个请求都需要进行额外引流处理,对业务流程存在性能影响

旁路复制

在这里插入图片描述
旁路复制一般是由第三方服务在网络协议栈中,监听复制流量,对业务无感。比如TCPCopy,相似的工具还有GoReplay

  • 优点: 与业务解耦,可以独立部署升级引流模块,业务方需关注引流功能实现
  • 缺点: 4层网卡层面的网络包抓取后,仍需要进行数据包重组和解析,需要额外的消耗计算资源,往往需要全量抓包解析再进行筛选,无法结合业务逻辑进行定制化的采样

基于网关的业务特点:1) 流量大,无差别的抓包、解包筛选会消耗大量CPU,2) 承接所有产品线的流量,需要提供特定产品线的流量复制能力

特别注意:流量复制是在线上服务上进行的,因此会消耗线上机器的CPU资源,为了不对线上业务带来影响,需要在流量复制时监控线上机器的资源使用,一旦资源消耗太多时需要立即停止流量复制

日志回放

日志回放有点类似旁路复制,但是不再监听网络协议请求,而是在线上服务实例放一个log agent,通过它来将日志转存到大数据分布式集群当中

使用日志回放的前提是,业务代码逻辑当中需要打印的日志,需要包括接口的请求参数,URL,Body,请求方式等必要的接口信息,如果没有打印接口信息的日志,则无法进行日志回放

所以要采取这种回放方式,第一步就是需要开发去协助规范日志
在这里插入图片描述
在HDFS/AFS集群当中存放日志以后,就能部署一个hadoop client 用来拉取集群上的日志,并对日志进行处理,解析出接口请求信息,有必要时需要对流量进行筛选后,再进行回放

下面分享一个使用Python多进程,解析日志获取流量的demo程序

假设现在已经从集群上拉下了日志,日志目录结构如下:
在这里插入图片描述
每一条日志文件service.log.wf.2023072615.18里面的内容为:一行接着一行的warning日志,部分warning日志带有接口信息

WARNING 24762924 20230726 13:00:24 Present.php:44 qa_traffic_playback: {"request_method":"POST","service_method":"getUserInfo","path":"\/service\/test?ngscfr=getUserInfo_10.221.110.27_fm_smallapp&method=getUserInfo&format=php&ie=utf-8","query":{"ngscfr":"getUserInfo_10.221.110.27_fm_smallapp","method":"getUserInfo","format":"php","ie":"utf-8"},"post":{"account_id":"59","account_type":"1","user_id":"123456","method":"getUserInfo","format":"php","ie":"utf-8","service_array_key":"a:0:{}","tb_sig":"7f2fcc8b398ca16979"}}
WARNING 24762924 20230726 13:00:24 Present.php:44 qa_traffic_playback: {"request_method":"POST","service_method":"getUserInfo","path":"\/service\/test?ngscfr=getUserInfo_10.221.110.27_fm_smallapp&method=getUserInfo&format=php&ie=utf-8","query":{"ngscfr":"getUserInfo_10.221.110.27_fm_smallapp","method":"getUserInfo","format":"php","ie":"utf-8"},"post":{"account_id":"59","account_type":"1","user_id":"56789","method":"getUserInfo","format":"php","ie":"utf-8","service_array_key":"a:0:{}","tb_sig":"7f2fcc8b398ca16979"}}
import os
import re
import json
import multiprocessing

def parse_log_file(log_file):

    log_entries = []

    with open(log_file, 'r') as file:
        for line in file:
            if '{"request_method"' in line:
                # 使用正则表达式匹配 JSON 字典
                pattern = re.compile(r'{.*?}}')
                match = pattern.search(line)
                if match:
                    json_dict = match.group()

                    print("json_dict",json_dict)

                    # 解析 JSON 字典
                    data = json.loads(json_dict)
                    print("data",data)
                    print("解析 JSON 字典",json_dict)

    return log_entries

def process_directory(directory, results_dict):

    log_files = []
    
    for root, dirs, files in os.walk(directory):
        
        for file in files:
            if file.startswith('service_present.log.wf'):
                log_file = os.path.join(root, file)
                log_files.append(log_file)
                # 在这里处理每个匹配到的日志文件
                print("log_files B", log_file)

    parsed_entries=[]
    for log_file in log_files:
        parsed_entries += parse_log_file(log_file)
    results_dict[directory] = parsed_entries

if __name__ == '__main__':
    
    root_directory = '/home/work/' # 指定根目录
    results = multiprocessing.Manager().dict()

    processes = []
    for subdir in os.listdir(root_directory):
        directory = os.path.join(root_directory, subdir)
        if os.path.isdir(directory):
            process = multiprocessing.Process(target=process_directory, args=(directory, results))
            processes.append(process)
            process.start()

    for process in processes:
        process.join()

    for directory, entries in results.items():
        print(f'Directory: {directory}')
        for entry in entries:
            print(entry)

解释一下这个程序,process_directory方法用来遍历日志目录,获取日志文件的绝对路径,parse_log_file方法用来解析日志文件,利用正则表达式提取出日志里面含接口信息的Json字符串,并转化为字典存放,这段代码片段还有可以优化的地方,因为存在在字典非常占用内容,如果接口信息过多,可能会出现内存占用过高的情况,这时候可以选择将接口信息存入数据库或者Redis

最后再提一嘴,在实际应用过程当中,我们可能还要利用算法进行流量筛选,经过初筛和精筛拿到指定的流量,另外还需要统计回放下来的流量覆盖率,这样才能比较准确的衡量流量的覆盖程度

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

应用安全四十二:SSO安全

一、什么是SSO SSO是单点登录(Single Sign On)的缩写,是指在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是比较流行的企业业务整合的解决方案之一。 身份验证过程依赖于双方之间的信任关…

大数据毕业设计选题推荐-智慧小区大数据平台-Hadoop-Spark-Hive

✨作者主页:IT研究室✨ 个人简介:曾从事计算机专业培训教学,擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

2022年ISSCC会议报告分析

Tutorial Fundamentals of Self-Sensing Processor Systems AMD Zen架构的CCD die中有很多传感器检测die的频率、电压、电压和温度 HBM DRAM and 3D Stacked Memory Advances in Digital vs. Analog AI Accelerators Nvidia的Multi-chip架构的DNN加速器 Form1: Compute-in…

Pycharm-community-2021版安装和配置

一、下载Pycharm-community-2021 1.从官网下载pycharm-community Pycharm 版本官网 二、安装PyCharm 1.打开下载完成的安装包,点击Next 2.安装PyCharm到其他位置,点击Next 3.一定把更新PATH变量勾上,可以创建桌面快捷方式,创建关联,最后…

apache-maven-3.6.3 安装配置教程

链接:https://pan.baidu.com/s/1RkMXipnvac9EKcZyUStfGQ?pwdl32m 提取码:l32m 1. 将 maven 压缩包解压至指定文件夹 2. 配置环境变量 (1)打开此电脑-> 鼠标右键选择属性->点击高级系统设置 (2)点…

【移远QuecPython】EC800M物联网开发板的硬件PWM和PWM输出BUG

【移远QuecPython】EC800M物联网开发板的硬件PWM和PWM输出BUG 文章目录 导入库初始化PWM开启PWMPWM硬件BUG附录:列表的赋值类型和py打包列表赋值BUG复现代码改进优化总结 py打包 导入库 from misc import PWM_V2或者 from misc import PWM但我觉得PWM_V2好用 初…

嵌入式中如何把C++代码改写成C语言代码

由于C++解释器比C语言解释器占用的存储空间要大500k左右。为了节省有限的存储空间、降低成本,同时也为了提高效率,将用C++语言写的源程序用C语言改写是很有必要的。 C++与C最大的区…

高位片选与低位交叉编址、芯片扩展与多模块存储器、多通道内存

多模块存储器 是一种空间并行技术,利用多个结构完全相同的存储模块的并行工作来增加存储器的吞吐率。根据不同的编址方式,多模块存储器分为连续编址和交叉编址两种结构 连续编址方式:主存地址的高位表示模块号(体号)…

Python--快速入门二

Python--快速入门二 1.Python数据类型 1.可以通过索引获取字符串中特定位置的字符: a "Hello" print(a[3]) 2.len函数获取字符串的长度: a "Hello" print(a) print(len(a)) 3.空值类型表示完全没有值: 若不确定当…

【嵌入式项目应用】__物联网小知识:不同通讯线的通讯距离是多少,你知道吗?

目录 前言 不同协议通讯线的传输距离 无线传输协议与距离 1. 蓝牙 2. Zigbee 3. LoRa 4. Wi-Fi 5. 蜂窝网络 6. Sigfox 7. LoRaWAN (* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。 前言 在物联网中,通讯线的作用是…

leetcode经典面试150题---5.多数元素

目录 题目描述 前置知识 代码 方法一 排序法 思路 实现 复杂度 方法二 哈希表 思路 实现 题目描述 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给…

pg14-sql基础(四)-多表联查

多表联查 内联查询 SELECT e.department_id, e.first_name, d.department_name FROM employees e INNER JOIN departments d -- JOIN departments d ON e.department_id d.department_id;左外联查询 SELECT e.department_id, e.first_name, d.department_name FROM employees…

【C语言基础】第01章_C语言入门

讲师:康师傅 视频:https://www.bilibili.com/video/BV1Bh4y1q7Nt?p1&vd_source3eaa9d17f2454e1ae80abc50d16e66b5 文章目录 本章专题脉络1初识计算机语言1.1 计算机语言是什么1.2 计算机语言简史 2初识C语言2.1 C语言的由来2.2 为什么要学习C语言2.…

英语——分享篇——每日200词——1-200

1——ball——[bɔːl]——n.球——ball——ba爸(拼音)ll筷子(象形)——爸爸用筷子夹球——The kid is playing the ball. ——孩子在玩皮球。 2——boat——[bəʊt]——n.船——boat——bo60(象形)at在(熟词)——60个人在船上——I have 60 boats.——我有60艘船。 3——bag—…

【架构图解】API架构图解:如何以图表形式展现复杂系统

文章目录 前言序列图组件图数据流程图结论 前言 架构图是链接到 API 的不同组件/服务如何相互交互的直观表示。 当需要理解 API 的架构并将其传达给不同的利益相关者(包括其他开发人员、项目经理和客户)时,这些图表非常有用。 图表/视觉效…

Python基础入门例程50-NP50 程序员节(循环语句)

最近的博文: Python基础入门例程49-NP49 字符列表的长度-CSDN博客 Python基础入门例程48-NP48 验证登录名与密码(条件语句)-CSDN博客 Python基础入门例程47-NP47 牛牛的绩点(条件语句)-CSDN博客 目录 最近的博文&a…

告知粉丝 重要

告知! 亲爱的粉丝朋友们,由于CSDN(博客)平台的限制,被迫转入 公众号!希望粉丝朋友们谅解!我们也是被逼无奈~~ 但我们还是会在CSDN(博客)发文章,但很少。 如果想要接触更好的文章请关注我们公众号,谢谢! 微信公众号:

POJ-3630电话表(考察字典树)

2023每日刷题&#xff08;二十&#xff09; POJ-3630电话表 题目原地址 输入样例&#xff1a; 2 3 911 97625999 91125426 5 113 12340 123440 12345 98346输出结果&#xff1a; NO YES实现代码 #include<iostream> #include<string> #include<cstring>…

AD9371 官方例程 NO-OS 主函数 headless 梳理(一)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

错误:ERROR Cannot read properties of null (reading ‘type‘)

ERROR Cannot read properties of null (reading ‘type’) TypeError: Cannot read properties of null (reading ‘type’) <template><el-card><el-row :gutter="20" class="header"><el-col :span="7"><el-input…