目录
前言
业务需求
分析接口执行逻辑
思路:4个步骤
1、通过JSON Extractor,逐个地提取所需要的字段
2、通过BeanShell后置处理器拼接json格式字符串
3、将拼接好的Json格式的企业信息数据写入文件
4、上传生成的文件
前言
一般来说,在JMeter脚本中,我们需要上传文件时,都会在本地保存一批固定的数据文件,反复执行上传;
需要传递Json格式的参数时,会通过Sampler的Body Data上传,在其中可以方便地进行关联及参数化。
然而,在前文content-type中的multipart/form-data中的案例,却是以上二者的综合体:其中的第三部分要上传的文件不是一个静态json文本,而是需要在运行时根据前面请求返回的响应,动态生成所需要的json文件,然后再在请求中,将该json文件上传。其内容大致如下(json键值稍做删减)
------WebKitFormBoundary4FB5e6lCzhHGTwV3
Content-Disposition: form-data; name="model"; filename="blob"
Content-Type: application/json
{
"authorizedPersonRequestParam": {
},
"enterpriseRequestParam": {
"id": "12345"
}
}
业务需求
这里先详述一下业务需求,以便大家更清晰地理解脚本需求。
系统用户为企业管理员,需要变更企业信息。登录后选择企业信息,点击“编辑”,会读取当前系统保存的企业数据,用户进行修改后提交。
分析接口执行逻辑
使用Fiddler抓包,分析接口入参及响应数据后,梳理出接口的执行逻辑如下:
1、发送企业数据查询请求,查询企业的当前信息。
2、系统以json格式返回企业数据信息,包含企业名称、统一社会信用代码、联系人、注册地等几十条文字信息,以及该企业曾上传的营业执照、法人身份证等图片信息。
3、用户修改后,发送提交请求,更新企业信息。此时,因需要将步骤2中提及的各文字信息以及附件图片信息一并提交,所以使用的是上一篇文章中所提到的multipart/form-data格式提交数据。
前文说过,整个表单分为4个部分,其中第(1)、(2)、(4)部分都是固定值,分别使用对应的形式传递参数或者上传文件即可。但是第(3)部分,也就是页面表单中的所有企业信息的文本数据,将打包成一个json格式的文件上传,其中包含企业名称,统一社会信用代码等唯一值,系统进行严格校验,必须使用真实有效的数据。所以,不可以使用固定的文件,需要在执行过程中动态地获取到职业信息,实时生成所需文件后上传。幸运的是,这部分数据,都可以从步骤2中获取。
思路:4个步骤
所以我的思路如下:
1、从上面第二步的查询请求的响应中,获取到第三步目标json文件中所需要的所有数据
2、拼接成目标Json格式的文本
3、将拼接好的Json格式的企业信息数据写成一个文件
4、按照上篇中介绍的方法,上传Json文件
下面逐步解决。
1、通过JSON Extractor,逐个地提取所需要的字段
其实有几十个字段,一个屏幕显示不下。这里只贴一部分示意一下。
2、通过BeanShell后置处理器拼接json格式字符串
当然,也可以使用前置处理器,放到提交接口中。按照提交接口所需要的Json格式拼接json字符串,使用上一步提取出来的数据,一个一个的拼接其中的变量。
我贴一部分代码在这里,大家理解如何拼接即可(因为你需要拼接的json串肯定跟我的结构不一样,贴全部代码毫无意义):
StringBuilder ori_data = new StringBuilder("{\"authorizedPersonRequestParam\":{},\"enterpriseRequestParam\":{\"id\":\"");
ori_data.append("${id}");
ori_data.append("\"}}");
以上代码,就拼接出了下面的json串
{"authorizedPersonRequestParam":{},"enterpriseRequestParam":{"id":"12345"}}
格式化一下,长这样(其中的变量${id}在运行过程中会被替换成实际值):
{
"authorizedPersonRequestParam": {
},
"enterpriseRequestParam": {
"id": "12345"
}
}
3、将拼接好的Json格式的企业信息数据写入文件
import org.apache.jmeter.services.FileServer;
import java.io.*;
import java.nio.charset.Charset;
//获取Jmeter项目路径,以便将文件写到本项目根目录下
String jmxFileDir = FileServer.getFileServer().getBaseDir();
File file = new File(jmxFileDir,"blob"); //bolb是要保存的文件名称,自定义
//将前面拼接出来的字符串写到文件中
FileWriter fstream = new FileWriter(file,Charset.forName("UTF-8"),false); //以覆盖方式写文件,确保每一次迭代都把上一次的数据清空
BufferedWriter out = new BufferedWriter(fstream);
out.write(ori_data.toString());
//关闭文件流
jmxFileDir = null;
ori_data = null;
file = null;
out.close();
fstream.close();
4、上传生成的文件
至此,结合本篇开头提及的那篇文章,脚本编制完成,单线程调试成功。然而,2个线程并发的时候,发现又出错了。。。
原来,步骤3写文件时,使用了固定的文件名blob。导致并发时各线程都在写这一个文件,而上传时,不同用户也在上传这一个文件,里面的企业信息肯定错误。
于是,重新修正:
1)步骤3写文件时,文件名加${__threadNum},以区分不同线程的文件,修改后如下:
import org.apache.jmeter.services.FileServer;
import java.io.*;
import java.nio.charset.Charset;
//获取Jmeter项目路径,以便将文件写到本项目根目录下
String jmxFileDir = FileServer.getFileServer().getBaseDir();
//因为并发时每个线程创建一个文件,数量太多,把它们放在一个文件夹下。在根目录下创建一个目录
File blobDir = new File(jmxFileDir,"blob");
if(!blobDir.exists()){
blobDir.mkdirs();
}
File file = new File(jmxFileDir,"blob${__threadNum}"); //bolb后面添加线程号
//将前面拼接出来的字符串写到文件中
FileWriter fstream = new FileWriter(file,Charset.forName("UTF-8"),false); //以覆盖方式写文件,确保每一次迭代都把上一次的数据清空
BufferedWriter out = new BufferedWriter(fstream);
out.write(ori_data.toString());
//关闭文件流
jmxFileDir = null;
ori_data = null;
file = null;
out.close();
fstream.close();
2)上传时,文件名也添加${__threadNum},以区分不同线程的文件
这也就是上一篇截图中,文件名为什么有个${__threadNum},以及我说“在文件名的命名上,可能需要考虑业务并发逻辑”的原因了。
关于更多使用${__threadNum}的小技巧,可参考我的另一篇博文:
JMeter案例分享:使用内置函数threadNum实现线程间数据分离和确保生成不重复的随机数-CSDN博客
后记
后续执行过程中发现,脚本在我这里运行良好,在项目组同事那里写文件错误。检查发现,我用的JDK版本是2.0,他的是1.8,在java8中,FileWriter不支持指定编码格式。以下写文件代码适用于JDK1.8
import org.apache.jmeter.services.FileServer;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
String jmxFileDir = FileServer.getFileServer().getBaseDir();
File blobDir = new File(jmxFileDir,"blob");
if(!blobDir.exists()){
blobDir.mkdirs();
}
File file = new File(blobDir,"blob${__threadNum}");
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
osw.write(ori_data.toString());
osw.flush();
jmxFileDir = null;
ori_data = null;
file = null;
fos.close();
osw.close();