当前位置:首页 > 技术分析 > 正文内容

服务端性能测试实战3-性能测试脚本开发

前言

在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:

  1. 阶段一:了解各个接口的入参、出参,使用Python代码模拟前端发送http请求,重点是通过代码调用串联起各个接口,了解接口层面的基本流程,也为后续模拟压测打好基础;
  2. 阶段二:将代码的接口实现转移到性能测试工具中来实现,跑通流程,重点是做好细节实现以及接口关联设计

一、Python脚本开发

1.加解密函数编写

加密:GET、DELETE请求参数不需要加密,POST请求需要对参数进行加密;

解密:每个请求返回的都是密文,所以在接口调用时要对密文进行解密。

由于加解密逻辑较为复杂,且不具有普适性,因此,此处只作简单说明,不介绍具体逻辑实现。看似简单的两个加密、解密函数,其实还是花了不少时间和精力才搞出来。

def encrypt_aes(clear_text, aes_key):
    """
    使用 AES 加密明文
    :param clear_text: 明文
    :param aes_key: AES 密钥
    :return: 加密后的 Base64 编码字符串
    """
    pass


def decrypt_aes(encrypt_text, aes_key):
    """
    使用 AES 解密密文
    :param encrypt_text: 密文(Base64 编码字符串)
    :param aes_key: AES 密钥
    :return: 解密后的字符串
    """
    pass

2.封装接口

以快速创建接口为例:

def quick_start_meeting(self, auth_token, c_token, aes_key, tenant_id):
        """
        快速创建会议
        :param auth_token: 在头里用
        :param c_token: 在头里用
        :param topic: 会议主题
        :param tenantId: 租户ID
        :param scheduleTime: 日期时间, int
        :param timezone: 时区
        :return: 会议信息
        """
        path = "/meeting/start"
        headers = {"authtoken": auth_token, "Content-Type": "application/json", "ctoken": c_token, "ec": self.ec,
                   "ev": self.ev, "user-agent": self.user_agent, "deviceid": self.device_id}
        data = {"topic": "GG Meeting" + str(random.randint(1000, 9999)), "scheduleTime": self.timestamp,
                "timezone": "Asia/Shanghai", "tenantId": tenant_id}
        enc_data = encrypt_aes(json.dumps(data), aes_key)
        response = requests.post(self.url + path, headers=headers, json={"data": enc_data})
        dec_data = decrypt_aes(json.dumps(eval(response.text)["data"]), aes_key)
        print("会议创建成功")
        return dec_data

代码解释:

  1. headers:组装请求头
  2. data:组装创建会议所需的参数;
  3. 调用加密方法encrypt_aes,传入会议参数和Key进行加密操作;
  4. 发起POST请求
  5. 调用解密方法decrypt_aes,传入处理过的返回值和Key进行解密操作;
  6. 返回解密后的响应体;

3.调用接口

把所有的接口封装完,我们来调用接口验证是否能调用通。

可以看到接口调用成功,返回了会议id、会议Number等信息。

4.参数构造及数据关联设计

接口参数通常会涉及到三种类型:

  1. 灵活传递参数的字段:这类参数需要做关联设计,这样可以保证数据是“活”的。在测试过程中,每个环境的数据都不一样,我们不能用写死的数据来发起请求,例如某个ID字段,在A环境有,但是B环境没有。每次都手动改数据的话,也不现实。从上一个接口取值传递给下一个接口,是接口测试过程中常用的数据关联做法,这样可以保证数据是实时且有效的。
  2. 唯一性校验的字段:例如商品编号、身份证号码等。对于这类字段,我们尽量使用自生成的数据,保证每次提交的数据都是唯一的,不会造成参数重复之类的冲突,同时也方便日志定位。尽量不要从CSV这类的文件中读取,因为只能读取一轮,第二轮再读取又会提交相同的数据了,同时也会增加操作的复杂度。
  3. 没有唯一性校验的字段:例如商品价格、人员年龄。这类字段没有唯一性校验,可以固定写死。

5.验证接口流程

先在代码中确保每个接口都能正常调用,再通过参数传递的方式将各个接口串联起来。接口流程畅通后,接下来就可以把各个接口腾到Jmeter中来实现。

二、Jmeter脚本开发

不同于在Python代码中实现,在Jmeter工具中,绝不仅仅是添加一些HTTP请求、把各个接口腾过来这么简单;首要问题就是解决参数的加解密问题,这一步搞不定其他都是白瞎。

我最初的想法是在Jmeter工具中使用前后置处理器调用Python加解密函数来对参数进行加密、返回值解密,但是折腾了大半天发现Jmeter对于Python代码的调用并不能很好的支持(虽然也可以安装Jython插件、在JSR223后置处理器中编写Python代码,但Jython插件只支持Python2.7的语法,后续可能会面临一系列麻烦),所以最后放弃了这个方案。
Jmeter是Java写的,所以Jmeter的前后置处理器天然对于Java语言支持最好。所以可以在JSR223后置处理器中调用Java加解密函数,来实现参数、返回值的加解密。

1.解决参数加解密问题

想要在JSR223后置处理器中调用Java加解密函数,来实现参数、返回值的加解密。必然要导入这个加解密的方法。具体实现思路是(这种方式适用于引用所有自定义的Java方法):

  1. 用Java语言编写加解密工具类
  2. 编译Java工具类代码,生成class文件
  3. 将class文件打为jar包
  4. 将jar包放到Jmeter指定扩展目录
  5. 在前后置处理器中导入加解密工具类
  6. 前置处理器中调用工具类中的加密方法对组装好的参数进行加密
  7. 后置处理器中调用工具类中的解密方法对返回密文进行解密

① 编写 AESUtil.java加解密工具类

package com.csb.meeting.crypto.utils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

public class AESUtil {

    // 加密算法RSA
    public static final String KEY_ALGORITHM = "AES";
    // 编码方式
    public static final String CODE_TYPE = "UTF-8";
    // 填充类型 AES/ECB/PKCS5Padding   AES/ECB/ISO10126Padding
    public static final String AES_TYPE = "AES/ECB/PKCS5Padding";

    /**
     * 加密
     *
     * @param clearText 明文
     * @param aesKey    AES秘钥
     * @return 加密串
     */
    public static String encryptAes(String clearText, String aesKey) {
        ...
    }

    /**
     * 解密
     *
     * @param encryptText 密文
     * @param aesKey      AES秘钥
     * @return 解密串
     */
    public static String decryptAes(String encryptText, String aesKey) {
        ...
    }
}    

② 编译 AESUtil.java代码,生成class文件

确保 AESUtil.java 代码能成功编译。在命令行先使用 javac 进行编译,生成.class目录及文件:

javac -d . AESUtil.java

此命令会在当前目录下生成
com/meeting/crypto/utils/AESUtil.class
文件。

③ 打包class文件为jar包

再使用jar命令将.class目录及文件打成包jar包:

jar cf AESUtil.jar com

④ 将编译后的jar包添加到 JMeter 指定扩展路径

把生成的AESUtil.jar复制到 JMeter 的 lib/ext 目录下。这样 JMeter 就能在运行时找到 AESUtil 类了。

⑤ 在 JSR223 前/后置处理器中使用 Java 调用加解密方法

在 JMeter 中添加 JSR223 后置处理器,将语言选择为 Java,注意在开头一定要先导入加解密工具类“import
com.csb.meeting.crypto.utils.AESUtil;”,例如解密会议列表:

import com.meeting.crypto.utils.AESUtil;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;


try {
   // 获取 JMeter 变量
   JMeterVariables vars = JMeterContextService.getContext().getVariables();

   // 从 JMeter 变量中获取加密文本和 AES 密钥
   String meetingList = vars.get("meetingList");
   String aesKey = vars.get("aesKey");

   // 检查变量是否为空
   if (encryptedText == null || aesKey == null) {
       System.err.println("加密文本或 AES 密钥为空,请检查用户定义的变量。");
       return;
   }

   // 调用 decryptAes 方法进行解密
   String decryptedText = AESUtil.decryptAes(meetingList, aesKey);

   // 将解密后的结果保存到 JMeter 变量中
   vars.put("decryptedText", decryptedText);

   // 打印日志,方便调试
   System.out.println("AES 密钥: " + aesKey);
   System.out.println("解密后的数据: " + decryptedText);
}
catch (Exception e) {
    log.error("Decryption failed: " + e.getMessage(), e);
}

查看控制台,成功解密数据并打印:

2.Jmeter脚本设计

① 添加用户自定义的变量

将一些不经常变化的公共参数抽离到用户自定义的变量中保存,后续调用时直接使用${variableName}进行调用即可。

② 添加HTTP公共请求头

将一些每个接口中都会带的请求头抽离到公共请求头中保存,这样每个接口在发起请求的时候都会带上这些请求头内容。

如果个别接口需要传递一些单独的请求头内容,再在该接口下添加一个单独的请求头即可。例如创建会议接口需要带上“ec”和“ev”:

③ 添加HTTP请求

添加HTTP请求,填写接口路径,参数等等。

④ 添加监听器

  • 添加查看结果树:可以查看到每个请求的运行结果、请求参数、响应参数等信息,便于定位问题;
  • 添加聚合报告监听器,用于查看每个接口的性能指标,如平均响应时间、吞吐量、错误率等;
  • 添加图形结果监听器,以图形化方式展示接口的响应时间和吞吐量变化。

⑤ 添加前置处理器,加密参数

例如:创建会议接口需要先组装参数,再调用加密方法对参数进行加密。此时就需要在HTTP请求下添加“JSR223前置处理器”,语言选择Java,然后编写相应代码。

代码解释:

  1. 导入相应的包,尤其是自定义的加解密工具类;
  2. 从Jmeter全局变量中读取密钥和其他相应参数,用于加密操作;
  3. 引用系统自带的函数,生成自定义参数,用于接口参数组装;
  4. 调用工具类中的加密方法,对组装好的参数进行加密;
  5. 将加密后的密文保存到Jmeter全局变量中,HTTP请求中引用组装后的密文;

⑥ 添加断言

断言有多种类型,我只断言了接口返回值中的code字段是否等于200,基本上返回200就表示接口响应成功了(并不是HTTP响应状态码)。

⑦ 添加JSON提取器,提取返回值

由于接口返回的内容也是加密的,所以需要先把密文部分全部提取出来,后续再用后置处理器进行解密,提取指定字段的返回值。

⑧ 添加后置处理器,解密密文并保存

与前置处理器类似,需要在HTTP请求下添加“JSR223后置处理器”,语言选择Java,然后编写相应代码。

代码解释:

  1. 导入相应的包,尤其是自定义的加解密工具类;
  2. 从Jmeter全局变量中读取密钥和其他相应参数,例如前面保存的meetingInfo;
  3. 调用工具类中的解密方法,对meetingInfo进行解密;
  4. 再从解密后的内容中提取指定字段返回值,例如meetingNumber,
  5. 将meetingNumber保存到Jmeter全局变量中,后续的接口再添加前置处理器,从Jmeter全局变量中读取这个meetingNumber;

⑨ 添加循环控制器,模拟多个状态上报

其中会控接口和状态上报接口的参数,是个枚举值数组,不同的值代表不同动作,所以这里添加一个循环控制器,将会控接口和状态上报接口放进去,循环三次,每次都是取随机值,这样就可以模拟三种不同的状态上报和会控操作。

⑩ 添加随机定时器

添加随机定时器,模拟接口之间的延时,时间设置为100ms~150ms,类似于人手动点击某个页面(发起A请求)后,又点了某个按钮(发起B请求),二者之间的思考间隔。

3.验证Jmeter脚本

上述所有工作完成后,就可以运行脚本进行调试验证了。其实无论是写代码还是写Jmeter脚本,无论是小白还是大神,尤其是实现比较复杂的情况下,基本没有一次就运行通过的。在经历了无数次的“报错->排查定位->修改->再运行”后,看到全绿,就表示大功告成了!

至此,终于完成了脚本开发的阶段性目标。

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/4752.html

分享给朋友:

“服务端性能测试实战3-性能测试脚本开发” 的相关文章

「图解」父子组件通过 props 进行数据交互的方法

1.组件化开发,经常有这样的一个场景,就是父组件通过 Ajax 获取数据,传递给子组件,如何通过 props 进行数据交互来实现,便是本图解的重点。2.代码的结构3.具体代码 ①在父组件 data 中存放数据 ms。 ②将父组件 data 中的数据 ms 绑定到子组件中的属性 ms。 ③子组件在 p...

【Vue3 基础】05.组件化

这是 Vue3 + Vite + Pinia +TS + Element-Plus 实战系列文档。最近比较忙没什么时间写文章,争取早日把这个系列完结吧~生命周期和模板引用在本章之前,我们通过响应式 api 和声明式渲染,处理了 DOM 的更新,但光是这些,对于一些复杂的需要手动操作 DOM 的情况,...

使用cgroup限制进程资源

这里使用containerd项目中的cgroup包来实现进程资源限制。先写一个耗费一个CPU并且一秒增加10m内存的测试进程package mainimport ( "fmt" "math/rand" "time")func main() { go f...

虚幻引擎5.5发布

IT之家 11 月 13 日消息,虚幻引擎 5.5 现已发布。据介绍,新版本虚幻引擎在动画创作、虚拟制作和移动游戏开发方面取得进步;渲染、摄像机内视觉特效和开发人员迭代等领域的部分功能已可用于生产。IT之家整理部分功能亮点如下:动画Sequencer增强虚幻引擎的非线性动画编辑器 Sequencer...

雅马哈TMAX 560 TECH MAX 外媒深度测评

应雅马哈(Yamaha)的邀请,在葡萄牙埃斯托里尔对全新的Yamaha TMAX 560 Tech Max踏板车进行了测试,在这里TMAX 560 Tech Max售价为11649英镑。雅马哈TMAX长期以来一直站在踏板车的顶端,就声誉和知名度而言,它是当之无愧的大踏板界NO.1。2020 TMAX...

学前端,这30个CSS选择器,你必须熟记

你学会了基本的id,class类选择器和descendant后代选择器,然后就觉得完事了吗?如果这样,你就会错过许多灵活运用CSS的机会。虽然本文提到的许多选择器都属于CSS3,并且只能在现代的浏览器中使用,但学会这些是大有好处的。什么是CSS选择器呢?每一条css样式定义由两部分组成,形式如下:[...