通用技术 [java][jmeter] 性能测试 -- 基于自已实现执行器的性能测试

bauul · 2019年05月14日 · 1066 次阅读

缘由

我原来是做移动端测试工作的,后来接触了服务端,做了一套接口测试的执行器,
所有用例都是 json 串,并且所有的用例依赖关系也处理了(用例间的依赖,数据库依赖,缓存依赖,自定义方法),下面为阉割版:

{
  "projectName": "Demo",
  "testSetList": [
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseName": "login",
            "caseOwner": "lishu",
            "caseComment": "登录-胡斐"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "批发新款-提交"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "现货批发-增补供应商"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "现货批发-下单"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "翻单-增补供应商"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "下单审核-查询"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "下单审核-不通过"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "现货批发-修改订单"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "下单审核-通过"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "文案任务-提交"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "商品列表-试穿"
          }
        }
      ]
    }
  ]
}

后来有一位同事跟我说,我实现的功能,在 jmeter 中都有。。。
后来就看了 jmeter 的知识,确实,jmeter 很强大,就是感觉操作起来不太爽,调试起来不方便。
所以我也没有放弃对我的执行器的维护,但是一直有一个想法,就是如何让 jmeter 执行我的 json 串?
所以,我的目标是:让性能测试能与现有的接口测试用例共用一套用例,基于我的 json 串。

转化策略

  • 转 jmx 文件 -- 将我的 json 文件转化成 jmeter 的 jmx 文件,各个用例对应一个关键字,比如 HttpSample 是一个关键字
  • 转 java 代码 -- 将我的 json 文件转化成 n 条 javaSample 请求

实际方案

自己动手实践了一下 jmx 文件下 JavaSample,又踩了几个坑,最后实际是综合了上面两个,定下了方案:
编写一个 jmx 文件作为模板,该 jmx 文件作为执行器,其中包含一个 JavaSample 请求,
该 JavaSample 请求动态从 json 中获取用例并执行,所有的用例间依赖关系,采用自己设计接口测试执行器中的方案,
即我的接口自动化一期

使用版本

jmeter 5.1.1
pom 依赖

<properties>
        <okhttp3.version>3.10.0</okhttp3.version>
        <fastjson.version>1.2.47</fastjson.version>
        <assertj.version>3.8.0</assertj.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_core</artifactId>
            <version>5.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_java</artifactId>
            <version>5.1.1</version>
        </dependency>

        <dependency>
            <groupId>com.lazerycode.jmeter</groupId>
            <artifactId>jmeter-maven-plugin</artifactId>
            <version>2.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>${okhttp3.version}</version>
        </dependency>

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>logging-interceptor</artifactId>
            <version>${okhttp3.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>${assertj.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>com.wz</groupId>
            <artifactId>harConvert</artifactId>
            <version>1.0-SNAPSHOT</version>
            <exclusions>
                <exclusion>
                    <groupId>org.testng</groupId>
                    <artifactId>testng</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.google.guava</groupId>
                    <artifactId>guava</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <appendAssemblyId>false</appendAssemblyId>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

代码

import apiTestModel.ApiTestCase;
import com.wz.exec.JSONExecutor;
import com.wz.utils.ApiTestUtils;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;

public class MyJavaSample extends AbstractJavaSamplerClient {

    @Override
    public SampleResult runTest(JavaSamplerContext javaSamplerContext) {

        ApiTestCase apiTestCase = ApiTestUtils.getOne();
        javaSamplerContext.getJMeterContext().getCurrentSampler().setName(apiTestCase.getCaseInfo().getCaseName());

        JSONExecutor jsonExecutor = new JSONExecutor(apiTestCase);

        SampleResult sampleResult = new SampleResult();
        sampleResult.sampleStart();
        jsonExecutor.run();
        sampleResult.sampleEnd();

        apiTestCase = ApiTestUtils.getCurrentTestCase();
        sampleResult.setSamplerData(apiTestCase.getTestResult().getReqBody());
        sampleResult.setSuccessful(true);
        sampleResult.setResponseCode("200");
        sampleResult.setResponseMessage(apiTestCase.getTestResult().getResBody());

        ApiTestUtils.setApiTestResult(ApiTestUtils.getCurrentTestCase());

        return sampleResult;
    }
}


import apiTestModel.*;
import com.alibaba.fastjson.JSON;
import org.apache.commons.collections4.iterators.LoopingIterator;
import org.apache.commons.io.FileUtils;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;

import java.io.File;
import java.io.IOException;
import java.util.*;

public final class ApiTestUtils {

    private static List<ApiTestCase> apiTestCaseList = new ArrayList<>();
    private static ThreadLocal<LoopingIterator<ApiTestCase>> apiTestCaseLoopingIterator = new ThreadLocal<>();;
    private static ThreadLocal<ApiTestCase> apiTestCase = new ThreadLocal<>();
    private static ThreadLocal<Map<String, ApiTestCase>> resultMap = new ThreadLocal<>();
    private static ApiTestProject apiTestProject;
    private static ThreadLocal<Map<String, Object>> globalHeaders = new ThreadLocal<>();

    private ApiTestUtils() {

    }

    static {
        String JSONFilePath = JMeterContextService.getContext().getProperties().getProperty("JSONFilePath");
        System.out.println("JSONFilePath:" + JSONFilePath);

        System.out.println("ApiTestUtils static run start");
        String apiTestProjectStr;
        try {
            apiTestProjectStr = FileUtils.readFileToString(new File(JSONFilePath), "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("Read the json file failed:" + JSONFilePath);
        }

        apiTestProject = JSON.parseObject(apiTestProjectStr, ApiTestProject.class);

        if (apiTestProject.getGlobalHeaderMap() == null) {
            apiTestProject.setGlobalHeaderMap(new HashMap<>());
        }
        if (apiTestProject.getGlobalParameters() == null) {
            GlobalParameters globalParameters = new GlobalParameters();
            globalParameters.setGlobalParametersMap(new HashMap<>());
        }

        for (ApiTestSet apiTestSet : apiTestProject.getTestSetList()) {
            for (ApiTestCase testCase : apiTestSet.getTestCaseList()) {
                ApiTestResult apiTestResult = new ApiTestResult();
                testCase.setTestResult(apiTestResult);
                apiTestCaseList.add(testCase);
            }

        }

        System.out.println("ApiTestUtils static run finished");
    }

    public static ApiTestCase getOne() {
        if (apiTestCaseLoopingIterator.get() == null) {
            apiTestCaseLoopingIterator.set(new LoopingIterator<>(apiTestCaseList));
        }
        apiTestCase.set(apiTestCaseLoopingIterator.get().next());
        return apiTestCase.get();
    }

    public static ApiTestCase getCurrentTestCase() {
        return apiTestCase.get();
    }

    public static void setCurrentTestCase(ApiTestCase testCase) {
        apiTestCase.set(testCase);
    }

    public static void setApiTestResult(ApiTestCase apiTestCase) {
        if (resultMap.get() == null) {
            resultMap.set(new HashMap<String, ApiTestCase>());
        } else {
            resultMap.get().put(apiTestCase.getCaseInfo().getCaseName(), apiTestCase);
        }
    }

    public static Map<String, Object> getGlobalHeaders() {
        if (globalHeaders.get() == null) {
            globalHeaders.set(new HashMap<String, Object>());
        }
        return globalHeaders.get();
    }

    public static ApiTestCase getTestCase(String caseName) {
        return resultMap.get().get(caseName);
    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "--" + getOne().getCaseInfo().getCaseName());
                System.out.println(Thread.currentThread().getName() + "--" + getOne().getCaseInfo().getCaseName());
                System.out.println(Thread.currentThread().getName() + "--" + getOne().getCaseInfo().getCaseName());
            }
        };

        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);

        t1.start();
        t2.start();
    }

    public static ApiTestUtils getInstance() {
        return Instance.instance;
    }

    private static class Instance {
        private static ApiTestUtils instance;
        static {
            instance = new ApiTestUtils();
        }
    }
}

主要的问题点

  • 如何保证线程安全? ==> ThreadLocal
  • 如何在一个执行器内对用例进行循环执行? ==> LoopingIterator 循环迭代器
  • 多个线程实际操作的是同一个对象? ==> 对象的深度克隆 / 序列化再反序列化
  • 如何传参给 jmeter? ==>
// JMeter const variable
String constVariable = JMeterContextService.getContext().getVariables().get("constVariable");
System.out.println("constVariable:" + constVariable);
// JMeter property, use -J propName=propValue in shell/dos
String JSONFilePath = JMeterContextService.getContext().getProperties().getProperty("JSONFilePath");
System.out.println("JSONFilePath:" + JSONFilePath);

结果

目前基本上算是踩坑完成,看结果,我只有一个 JavaSample

接下来

上周六请了一天假去阿里西溪园参加性能测试的活动,可惜去之前也没做什么准备,听的我还是有点懵的,
接下来就是主要是整个 RPS 的实践,最好能发现开发的问题,并修改验证

  • 尚未经大量请求实践,找个安静的夜晚,压个成千上万的请求,试试
  • 分布式实践
  • RPS 实践
  • 断言优化
  • 性能数据统计并展示

RPS 依赖

下载地址:https://jmeter-plugins.org/downloads/old/

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册