性能工具之 HAR 格式化转换JMeter JMX 脚本文件

文章目录

一、前言

最近要做一个系统的性能测试,菜单多,相对接口应该也比较多,想一想能否通过har格式转换成一个脚本呢?网上有不少案例,找了几个发现都没有成功,后面想了想还是自己搞定。

二、什么是HAR文件?

HAR(HTTP Archive format),是一种或 JSON 格式的存档格式文件,通用扩展名为 .har。Web 浏览器可以使用该格式导出有关其加载的网页的详细性能数据。

三、参考代码

代码主要实现以下功能:

  1. 配置JMeter路径
  2. 读取HAR文件
  3. 解析HAR文件
  4. 构建JMX文件
  5. 输出JMX 脚本
java 复制代码
// 设置jmeterHome路径
    // 主要是读取了几个配置文件,jmeter.properties,user.properties,system.properties。
    // 设置一下的本地的Locale环境。
    // 其实到这里,是可以仅将这3个配置文件抽离出来,即不需要整个Jmeter的home目录,仅要这3个配置文件就能运行Jmeter脚本。
    // 甚至仅在代码中写要的配置,都不需要实体的配置文件即可。
    // 当然随着功能越来越多,平台跟Jmeter的耦合也越来越多,这个Jmeter_home目录还是越来越必要了。
    public static void main(String[] args) {
        // jmeter 路径
        File jmeterHome = new File("D:\\Program Files\\apache-jmeter-5.6.3");
        // 分隔符
        String slash = System.getProperty("file.separator");
        //判断jmeterHome
        if (jmeterHome.exists()) {
            File jmeterProperties = new File(jmeterHome.getPath() + slash + "bin" + slash + "jmeter.properties");
            if (jmeterProperties.exists()) {
                // 初始化压测引擎
                StandardJMeterEngine jmeter = new StandardJMeterEngine();
                // JMeter初始化(属性、日志级别、区域设置等)
                JMeterUtils.setJMeterHome(jmeterHome.getPath());
                JMeterUtils.loadJMeterProperties(jmeterProperties.getPath());
                // 可以注释这一行,查看额外的日志,例如DEBUG级别
                JMeterUtils.initLogging();
                JMeterUtils.initLocale();
                // JMeter测试计划,基本上是JOrphan HashTree
                HashTree testPlanTree = new HashTree();
                String Name = "baidu-jmx";
                // har 路径
                FileReader fileReader = new FileReader("aa".har");
                String result = fileReader.readString();
                JSONObject harJson = JSONObject.parseObject(result);
                // 获取 HAR 文件中的 entries
                JSONObject log = harJson.getJSONObject("log");
                JSONArray entries = log.getJSONArray("entries");
                // Loop Controller 循环控制
                LoopController loopController = getLoopController();
                // Thread Group 线程组
                ThreadGroup threadGroup = getThreadGroup(Name, loopController);
                // Test Plan 测试计划
                TestPlan testPlan = getTestPlan();
                // 从以上初始化的元素构造测试计划
                testPlanTree.add(testPlan);
                HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
                // 构建事务
                TransactionController transactionController = getTransactionController();

                HashTree transactionControllerHashTree = threadGroupHashTree.add(transactionController);
                entries.forEach(item -> {
                    JSONObject json = (JSONObject) item;
                    JSONObject request = json.getJSONObject("request");
                    String method = request.getString("method");
                    String url = request.getString("url");

                    HTTPSamplerProxy Sampler = new HTTPSamplerProxy();
                    buildTheUnderlyingData(url, Sampler, method);
                    Sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
                    Sampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
                    // 头信息
                    HeaderManager headerManager = new HeaderManager();
                    JSONArray headersList = request.getJSONArray("headers");
                    headersList.forEach(header -> {
                        JSONObject jsonObject = (JSONObject) header;
                        Header headers = new Header();
                        headers.setName(jsonObject.getString("name"));
                        headers.setValue(jsonObject.getString("value"));
                        headerManager.add(headers);
                    });
                    headerManager.setComment("");
                    headerManager.setName("header Manager");
                    headerManager.setProperty(TestElement.TEST_CLASS, HeaderManager.class.getName());
                    headerManager.setProperty(TestElement.GUI_CLASS, HeaderPanel.class.getName());
                    // 构建参数
                    buildParameters(method, headersList, Sampler, request);
                    // 处理断言
                    ResponseAssertion assertion = getResponseAssertion();
                    // 构造新的树
                    transactionControllerHashTree.add(Sampler, headerManager);
                    transactionControllerHashTree.add(Sampler, assertion);
                });

                try {
                 // 将生成的测试计划保存为JMeter的.jmx文件格式
                    String jmxFilePath = "D:\\work\\jmx\\aaa.jmx";
                    SaveService.saveTree(testPlanTree, Files.newOutputStream(Paths.get(jmxFilePath)));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * 根据给定的URL配置 HTTPSamplerProxy 对象
     * 该方法用于解析URL并配置HTTPSamplerProxy对象的相关参数,以便进行HTTP请求
     *  buildTheUnderlyingData
     * @param url      要请求的URL地址
     * @param Sampler  HTTPSamplerProxy对象,用于发送HTTP请求
     * @param method   HTTP请求方法,如GET、POST等
     * @throws RuntimeException 如果URL格式错误,则通过抛出运行时异常来处理
     */
    private static void buildTheUnderlyingData(String url, HTTPSamplerProxy Sampler, String method) {
        String path;
        int port = 80;
        String domain = "";
        try {
            // 解析URL
            URL urlPath = new URL(url);
            // 获取URL的路径
            path = urlPath.getPath();
            // 获取URL的域名
            domain = urlPath.getHost();
            // 获取URL的端口,如果未指定则默认为-1
            port = urlPath.getPort();
            // 获取并设置URL的协议,如http、https等
            String protocol = urlPath.getProtocol();
            Sampler.setProtocol(protocol);
            // 根据URL的端口设置Sampler的端口,默认为80
            if (port == -1) {
                Sampler.setPort(port);
            } else {
                Sampler.setPort(80);
            }
            // 设置Sampler的域名、路径和请求方法
            Sampler.setDomain(domain);
            Sampler.setPath(path);
            Sampler.setMethod(method);
        } catch (MalformedURLException e) {
            // 如果URL格式不正确,抛出运行时异常
            throw new RuntimeException(e);
        }
        // 设置Sampler的名称为URL的路径
        Sampler.setName(path);
    }



    /**
     * 构建HTTP请求参数
     * 根据请求方法、请求头和请求体的不同情况,设置HTTP请求的参数
     * 目前支持GET、POST请求方法,DELETE和PUT方法留作未来扩展
     *
     * @param method 请求方法,如"GET"、"POST"、"DELETE"、"PUT"
     * @param headersList 请求头的JSONArray,用于判断内容类型
     * @param Sampler HTTPSamplerProxy对象,用于添加请求参数
     * @param request 请求的JSONObject,包含请求数据
     */
    private static void buildParameters(String method, JSONArray headersList, HTTPSamplerProxy Sampler, JSONObject request) {
        // 处理POST请求
        if (method.equals("POST")) {
            // 过滤出内容类型为application/json的请求头
            List<JSONObject> filteredObjects = filterJsonObjectsByValue(headersList, "application/json");
            // 如果存在application/json类型的请求头,设置请求体为原始格式,并设置请求体内容
            if (!filteredObjects.isEmpty()) {
                Sampler.setPostBodyRaw(true);
                JSONObject postData = request.getJSONObject("postData");
                Sampler.addArgument("", postData.getString("text"), "=");
            }
            // 过滤出内容类型为application/x-www-form-urlencoded的请求头
            filteredObjects = filterJsonObjectsByValue(headersList, "application/x-www-form-urlencoded");
            // 如果存在application/x-www-form-urlencoded类型的请求头,设置请求体为非原始格式,并添加查询字符串参数
            if (!filteredObjects.isEmpty()) {
                Sampler.setPostBodyRaw(false);
                JSONArray queryString = request.getJSONArray("queryString");
                queryString.forEach(item1 -> {
                    JSONObject jsonObject = (JSONObject) item1;
                    Sampler.addArgument(jsonObject.getString("name"), jsonObject.getString("value"), "=");
                });
            }
        }
        // 处理GET请求
        else if (method.equals("GET")) {
            // 设置请求体为非原始格式,并添加查询字符串参数
            Sampler.setPostBodyRaw(false);
            JSONArray queryString = request.getJSONArray("queryString");
            queryString.forEach(item -> {
                JSONObject jsonObject = (JSONObject) item;
                Sampler.addArgument(jsonObject.getString("name"), jsonObject.getString("value"), "=");
            });
        }
        // 处理DELETE请求,目前未实现具体逻辑
        else if (method.equals("DELETE")) {
            // todo 根据实际情况处理
        }
        // 处理PUT请求,目前未实现具体逻辑
        else if (method.equals("PUT")) {
            // todo 根据实际情况处理
        }
    }


    /**
     * 获取响应断言对象
     * <p>
     * 此方法用于创建并配置一个ResponseAssertion对象,主要用于测试过程中对响应内容进行断言
     * 它设置了断言的基本属性,并配置它以检查响应数据中是否存在特定的内容
     *
     * @return ResponseAssertion 返回配置好的ResponseAssertion对象,用于后续的HTTP响应断言
     */
    private static @NotNull ResponseAssertion getResponseAssertion() {
        // 创建一个响应断言实例
        ResponseAssertion assertion = new ResponseAssertion();
        // 设置测试字段的响应数据
        assertion.setTestFieldResponseData();
        // 设置不默认假设成功
        assertion.setAssumeSuccess(false);
        // 定义断言内容
        String assertContent = "success\":";
        // 设置测试类和GUI类的属性
        assertion.setProperty(TestElement.TEST_CLASS, ResponseAssertion.class.getName());
        assertion.setProperty(TestElement.GUI_CLASS, AssertionGui.class.getName());
        // 设置断言的名称
        assertion.setName("Response Assertion");
        // 如果断言内容非空,添加到断言中
        if (Objects.nonNull(assertContent)) {
            assertion.addTestString(assertContent.trim());
        }
        // 设置断言类型为包含测试
        assertion.setToContainsType();
        // 返回配置好的断言对象
        return assertion;
    }

    /**
     * 获取一个配置好的TransactionController实例
     *
     * 该方法负责创建并配置一个TransactionController对象,用于后续的事务控制
     * TransactionController是用于在性能测试中管理事务的重要类,它可以控制事务的开始和结束,
     * 以及是否包含计时器等属性的设置
     *
     * @return TransactionController 返回一个配置好的TransactionController实例,用于在测试中控制事务
     */
    private static @NotNull TransactionController getTransactionController() {
        // 创建一个新的TransactionController实例
        TransactionController transactionController = new TransactionController();
        // 设置TransactionController的名称
        transactionController.setName("Transaction Controller");
        // 设置测试类名为TransactionController类的名称
        transactionController.setProperty(TestElement.TEST_CLASS, TransactionController.class.getName());
        // 设置GUI类名为TransactionControllerGui类的名称
        transactionController.setProperty(TestElement.GUI_CLASS, TransactionControllerGui.class.getName());
        // 设置生成父样本的标志,以便在采样时包含父事务的信息
        transactionController.setGenerateParentSample(true);
        // 设置不包含计时器,即事务中不包含计时操作
        transactionController.setIncludeTimers(false);
        // 返回配置好的TransactionController实例
        return transactionController;
    }

    /**
     * 创建并返回一个JMeter测试计划
     * 测试计划用于组织和管理JMeter测试的所有方面
     *
     * @return 返回一个已经初始化的TestPlan实例,从未返回null
     */
    private static @NotNull TestPlan getTestPlan() {
        // 创建一个新的测试计划,命名为"创建JMeter脚本"
        TestPlan testPlan = new TestPlan("创建JMeter脚本");

        // 设置测试元素的测试类属性,为其提供类名
        // 这是JMeter内部用来识别和分类测试元素的一种机制
        testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());

        // 设置测试元素的GUI类属性,为其提供对应的GUI类名
        // 这影响了在图形界面模式下测试元素如何被呈现
        testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());

        // 设置用户定义的变量
        // 这里使用ArgumentsPanel创建一个TestElement,并将其转换为Arguments类型,以便设置用户变量
        // Arguments是JMeter中用于管理用户定义变量的类
        testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement());

        // 返回初始化完成的测试计划对象
        return testPlan;
    }

    /**
     * 根据给定的线程名称和循环控制器创建并返回一个新的线程组
     * 此方法用于初始化一个线程组实例,设置其基本属性,如名称、线程数、预热步骤和采样控制器
     *
     * @param Name 线程组的名称,用于标识该线程组
     * @param loopController 控制线程循环的控制器,决定线程如何循环执行任务
     * @return 返回一个初始化完毕的、不可为空的线程组实例
     */
    private static @NotNull ThreadGroup getThreadGroup(String Name, LoopController loopController) {
        // 创建一个新的线程组实例
        ThreadGroup threadGroup = new ThreadGroup();
        // 设置线程组的名称
        threadGroup.setName(Name);
        // 设置线程组中的线程数量为1
        threadGroup.setNumThreads(1);
        // 设置预热步骤为1,通常用于性能测试前的预热
        threadGroup.setRampUp(1);
        // 设置采样控制器,控制线程的循环行为
        threadGroup.setSamplerController(loopController);
        // 设置线程组的类名属性,用于测试元素的分类
        threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
        // 设置线程组的GUI类名属性,用于图形用户界面的关联
        threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
        // 返回配置完毕的线程组实例
        return threadGroup;
    }

    /**
     * 获取循环控制器实例
     *
     * 该方法用于创建并初始化一个LoopController实例,用于控制测试循环的行为
     * 循环控制器在性能测试中扮演重要角色,它定义了测试用例如何循环执行
     *
     * @return 返回一个已经初始化的LoopController实例,保证不会返回null
     */
    private static @NotNull LoopController getLoopController() {
        // 创建一个新的循环控制器实例
        LoopController loopController = new LoopController();
        // 设置循环次数为1,意味着测试用例将被执行一次
        loopController.setLoops(1);
        // 将第一个循环标志设置为true
        // 这个标志用于指示当前的循环是否是第一次循环,在某些逻辑中可能会用到这个信息
        loopController.setFirst(true);
        // 设置循环控制器的测试类属性为其自身的类名
        // 这是为了在JMeter的GUI中正确识别该元素的类型
        loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
        // 设置循环控制器的GUI类属性为其对应的GUI组件类名
        // 这是为了在JMeter的GUI中显示相应的配置面板
        loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName());
        // 初始化循环控制器
        // 初始化步骤是必要的,确保循环控制器能够正确地开始工作
        loopController.initialize();
        // 返回已经配置和初始化的循环控制器实例
        return loopController;
    }

    /**
     * 过滤JSON对象数组中的值
     *
     * 该方法通过搜索字符串来过滤一个JSON对象数组,返回值中包含搜索字符串的所有JSON对象
     *
     * @param jsonArray JSON对象数组,其中每个对象都包含一个"key"-"value"对
     * @param searchString 搜索字符串,用于过滤JSON对象
     * @return 包含搜索字符串的所有JSON对象的列表
     */
    public static List<JSONObject> filterJsonObjectsByValue(JSONArray jsonArray, String searchString) {
        // 创建一个列表,用于存储过滤后的JSON对象
        List<JSONObject> filteredObjects = new ArrayList<>();

        // 遍历JSON对象数组
        for (int i = 0; i < jsonArray.size(); i++) {
            // 获取当前迭代的JSON对象
            JSONObject jsonObject = jsonArray.getJSONObject(i);
            // 获取JSON对象中的"value"字段字符串
            String value = jsonObject.getString("value");

            // 如果"value"字段字符串包含搜索字符串,则将该JSON对象添加到过滤列表中
            if (value.contains(searchString)) {
                filteredObjects.add(jsonObject);
            }
        }

        // 返回过滤后的JSON对象列表
        return filteredObjects;
    }

四、操作步骤

上面代码写完毕后,打开"浏览器开启开发者模式",点击<Fetch/XHR>:

保存上面数据:

执行上面脚本:

修改执行保存路径:

打开存储路,查看文件:

打开JMeter:

添加结果查看树,并且执行该脚本:

执行正确,为什么会是红色显示呢,大家看上面代码在看看断言就知道是上面原因呢?

通过上面步骤就能构建一个完整的 JMX 文件。

如果想通过内置StandardJMeterEngine 执行该脚本。

可以参考如下:

相关推荐
sszmvb12341 小时前
测试开发 | 电商业务性能测试: Jmeter 参数化功能实现注册登录的数据驱动
jmeter·面试·职场和发展
小码哥说测试3 小时前
接口测试用例设计的关键步骤与技巧解析!
自动化测试·测试工具·jmeter·职场和发展·测试用例·接口测试·postman
小钱c76 小时前
Mac下安装Apache JMeter并启动
jmeter·macos·apache
古人诚不我欺9 小时前
jmeter常用配置元件介绍总结之函数助手
jmeter
川石课堂软件测试9 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
古人诚不我欺9 小时前
jmeter常用配置元件介绍总结之取样器
jmeter
十叶知秋9 小时前
【jmeter】jmeter的线程组功能的详细介绍
数据库·jmeter·性能测试
我非夏日9 小时前
JMeter基础篇
jmeter
茶馆大橘19 小时前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel
土小帽软件测试1 天前
jmeter基础01-2_环境准备-Mac系统安装jdk
java·测试工具·jmeter·macos·软件测试学习