mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-11-30 02:58:31 +08:00
feat(接口测试): jmx导入,解析机制
--task=1016131 --user=陈建星 协议插件的导入机制 https://www.tapd.cn/55049933/s/1570442
This commit is contained in:
parent
6fc6ffa1d1
commit
bf1860faa2
@ -18,7 +18,7 @@ import java.util.function.Function;
|
||||
* @createTime 2021-10-30 10:07
|
||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
||||
*/
|
||||
public abstract class AbstractJmeterElementConverter<T extends MsTestElement> implements JmeterElementConverter {
|
||||
public abstract class AbstractJmeterElementConverter<T extends MsTestElement> implements JmeterElementConverter<T> {
|
||||
|
||||
public Class<? extends MsTestElement> testElementClass;
|
||||
|
||||
@ -60,11 +60,6 @@ public abstract class AbstractJmeterElementConverter<T extends MsTestElement> im
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
||||
*/
|
||||
public abstract void toHashTree(HashTree tree, T element, ParameterConfig config);
|
||||
|
||||
/**
|
||||
* 解析 MsTestElement 的子节点
|
||||
*/
|
||||
|
@ -0,0 +1,49 @@
|
||||
package io.metersphere.plugin.api.spi;
|
||||
|
||||
|
||||
import lombok.Setter;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @createTime 2021-10-30 10:07
|
||||
* 将 Jmeter 的 TestElement 转换成 MsTestElement
|
||||
*/
|
||||
public abstract class AbstractMsElementConverter<T extends TestElement> implements MsElementConverter<T> {
|
||||
|
||||
public Class<? extends TestElement> testElementClass;
|
||||
|
||||
/**
|
||||
* 获取转换器的函数
|
||||
*/
|
||||
@Setter
|
||||
private static Function<Class<? extends TestElement>, AbstractMsElementConverter> getConverterFunc;
|
||||
|
||||
public AbstractMsElementConverter() {
|
||||
Type genericSuperclass = getClass().getGenericSuperclass();
|
||||
if (genericSuperclass instanceof ParameterizedType parameterizedType) {
|
||||
// 获取泛型的具体类型,即 MsTestElement 的具体实现类
|
||||
testElementClass = ((Class) parameterizedType.getActualTypeArguments()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 MsTestElement 的子节点
|
||||
*/
|
||||
public void parseChild(AbstractMsTestElement parentMsElement, TestElement parentElement, HashTree hashTree) {
|
||||
HashTree currentHashtree = hashTree.get(parentElement);
|
||||
if (currentHashtree == null) {
|
||||
return;
|
||||
}
|
||||
for (Object key : currentHashtree.keySet()) {
|
||||
if (key instanceof TestElement testElement) {
|
||||
getConverterFunc.apply(testElement.getClass()).toMsElement(parentMsElement, testElement, currentHashtree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ public abstract class AbstractMsTestElement implements MsTestElement {
|
||||
/**
|
||||
* 子组件
|
||||
*/
|
||||
private LinkedList<AbstractMsTestElement> children;
|
||||
private LinkedList<AbstractMsTestElement> children = new LinkedList<>();
|
||||
/**
|
||||
* 父组件
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
package io.metersphere.plugin.api.spi;
|
||||
|
||||
|
||||
import io.metersphere.plugin.api.dto.ParameterConfig;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.pf4j.ExtensionPoint;
|
||||
|
||||
/**
|
||||
@ -8,5 +10,10 @@ import org.pf4j.ExtensionPoint;
|
||||
* @createTime 2021-10-30 10:07
|
||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
||||
*/
|
||||
public interface JmeterElementConverter extends ExtensionPoint {
|
||||
public interface JmeterElementConverter<T extends MsTestElement> extends ExtensionPoint {
|
||||
|
||||
/**
|
||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
||||
*/
|
||||
void toHashTree(HashTree tree, T element, ParameterConfig config);
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package io.metersphere.plugin.api.spi;
|
||||
|
||||
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.pf4j.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* @author jianxing
|
||||
* @createTime 2021-10-30 10:07
|
||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
||||
*/
|
||||
public interface MsElementConverter<T extends TestElement> extends ExtensionPoint {
|
||||
|
||||
/**
|
||||
* 将 MsTestElement 具体实现类转换为 HashTree
|
||||
*/
|
||||
void toMsElement(AbstractMsTestElement parent, T element, HashTree hashTree);
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
package io.metersphere.api.utils;
|
||||
package io.metersphere.api.parser.jmeter;
|
||||
|
||||
import io.metersphere.api.parser.jmeter.MsCommonElementConverter;
|
||||
import io.metersphere.api.parser.jmeter.MsHTTPElementConverter;
|
||||
import io.metersphere.api.parser.jmeter.MsScenarioConverter;
|
||||
import io.metersphere.api.parser.jmeter.MsScriptElementConverter;
|
||||
import io.metersphere.api.parser.jmeter.child.MsCsvChildPreConverter;
|
||||
import io.metersphere.api.parser.jmeter.controller.MsConstantTimerControllerConverter;
|
||||
import io.metersphere.api.parser.jmeter.controller.MsIfControllerConverter;
|
@ -4,7 +4,6 @@ import io.metersphere.api.dto.ApiParamConfig;
|
||||
import io.metersphere.api.dto.request.MsScenario;
|
||||
import io.metersphere.api.dto.scenario.ScenarioOtherConfig;
|
||||
import io.metersphere.api.parser.TestElementParser;
|
||||
import io.metersphere.api.utils.JmeterElementConverterRegister;
|
||||
import io.metersphere.plugin.api.dto.ParameterConfig;
|
||||
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsProtocolTestElement;
|
||||
|
@ -0,0 +1,21 @@
|
||||
package io.metersphere.api.parser.ms;
|
||||
|
||||
import io.metersphere.api.dto.request.http.MsHTTPElement;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
||||
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2024-08-28 11:36
|
||||
*/
|
||||
public class HTTPSamplerConverter extends AbstractMsElementConverter<HTTPSamplerProxy> {
|
||||
@Override
|
||||
public void toMsElement(AbstractMsTestElement parent, HTTPSamplerProxy httpSampler, HashTree hashTree) {
|
||||
MsHTTPElement msHTTPElement = new MsHTTPElement();
|
||||
// todo 解析HTTP请求
|
||||
parent.getChildren().add(msHTTPElement);
|
||||
parseChild(msHTTPElement, httpSampler, hashTree);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package io.metersphere.api.parser.ms;
|
||||
|
||||
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2024-08-28 13:57
|
||||
* 作为通用的解析器,如果遇到不支持的组件,会使用这个解析器
|
||||
*/
|
||||
public class JmeterGeneralElementConverter extends AbstractMsElementConverter<TestElement> {
|
||||
@Override
|
||||
public void toMsElement(AbstractMsTestElement parent, TestElement element, HashTree hashTree) {
|
||||
// 不做任何处理,直接解析子元素
|
||||
parseChild(parent, element, hashTree);
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package io.metersphere.api.parser.ms;
|
||||
|
||||
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
|
||||
import io.metersphere.plugin.sdk.util.PluginLogUtils;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2023-11-03 17:14
|
||||
*/
|
||||
public class MsElementConverterRegister {
|
||||
|
||||
/**
|
||||
* 解析器集合
|
||||
* key 为 TestElement 实现类的 Class
|
||||
* value 为对应的转换器
|
||||
*/
|
||||
private static final Map<Class<? extends TestElement>, AbstractMsElementConverter<? extends TestElement>> parserMap = new HashMap<>();
|
||||
private static final JmeterGeneralElementConverter generalElementConverter = new JmeterGeneralElementConverter();
|
||||
|
||||
static {
|
||||
// 设置获取转换器的方法
|
||||
AbstractMsElementConverter.setGetConverterFunc(MsElementConverterRegister::getConverter);
|
||||
|
||||
// 注册默认的转换器
|
||||
register(TestPlanConverter.class);
|
||||
register(ThreadGroupConverter.class);
|
||||
register(HTTPSamplerConverter.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 TestElement 对应的转换器
|
||||
*
|
||||
* @param elementConverterClass 转换器的类
|
||||
*/
|
||||
public static void register(Class<? extends AbstractMsElementConverter<? extends TestElement>> elementConverterClass) {
|
||||
try {
|
||||
AbstractMsElementConverter<? extends TestElement> elementConverter = elementConverterClass.getDeclaredConstructor().newInstance();
|
||||
// 注册到解析器集合中
|
||||
parserMap.put(elementConverter.testElementClass, elementConverter);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
|
||||
NoSuchMethodException e) {
|
||||
handleRegistrationException(elementConverterClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应组件的转换器
|
||||
*
|
||||
* @param TestElementClass 组件的类
|
||||
* @return 转换器
|
||||
*/
|
||||
public static AbstractMsElementConverter getConverter(Class<? extends TestElement> TestElementClass) {
|
||||
AbstractMsElementConverter<? extends TestElement> converter = parserMap.get(TestElementClass);
|
||||
if (converter == null) {
|
||||
// 如果没有对应的转换器,则使用通用的转换器
|
||||
return generalElementConverter;
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理注册转换器时的异常
|
||||
*
|
||||
* @param elementConverterClass 转换器的类
|
||||
* @param e 异常
|
||||
*/
|
||||
private static void handleRegistrationException(Class<?> elementConverterClass, Exception e) {
|
||||
PluginLogUtils.error("注册转换器失败: " + elementConverterClass, e);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package io.metersphere.api.parser.ms;
|
||||
|
||||
import io.metersphere.api.dto.request.MsScenario;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
||||
import org.apache.jmeter.testelement.TestElement;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2023-10-27 10:07
|
||||
* <p>
|
||||
* 将 hashTree 转换为 MsTestElement
|
||||
*/
|
||||
public class MsTestElementParser {
|
||||
|
||||
public AbstractMsTestElement parse(HashTree hashTree) {
|
||||
MsScenario msScenario = new MsScenario();
|
||||
for (Object key : hashTree.keySet()) {
|
||||
if (key instanceof TestElement testElement) {
|
||||
MsElementConverterRegister.getConverter(testElement.getClass()).toMsElement(msScenario, testElement, hashTree);
|
||||
}
|
||||
}
|
||||
return msScenario;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.metersphere.api.parser.ms;
|
||||
|
||||
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
||||
import org.apache.jmeter.testelement.TestPlan;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2023-10-27 10:07
|
||||
* <p>
|
||||
* 脚本解析器
|
||||
*/
|
||||
public class TestPlanConverter extends AbstractMsElementConverter<TestPlan> {
|
||||
@Override
|
||||
public void toMsElement(AbstractMsTestElement parent, TestPlan element, HashTree hashTree) {
|
||||
// 测试计划不做任何处理,直接解析子元素
|
||||
parseChild(parent, element, hashTree);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.metersphere.api.parser.ms;
|
||||
|
||||
|
||||
import io.metersphere.api.dto.request.MsScenario;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsElementConverter;
|
||||
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
|
||||
import org.apache.jmeter.threads.ThreadGroup;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2023-10-27 10:07
|
||||
* <p>
|
||||
* 脚本解析器
|
||||
*/
|
||||
public class ThreadGroupConverter extends AbstractMsElementConverter<ThreadGroup> {
|
||||
@Override
|
||||
public void toMsElement(AbstractMsTestElement parent, ThreadGroup element, HashTree hashTree) {
|
||||
MsScenario msScenario = new MsScenario();
|
||||
// todo 解析线程组
|
||||
parent.getChildren().add(msScenario);
|
||||
parseChild(msScenario, element, hashTree);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package io.metersphere.api.service;
|
||||
|
||||
import io.metersphere.api.utils.ApiDataUtils;
|
||||
import io.metersphere.api.utils.JmeterElementConverterRegister;
|
||||
import io.metersphere.api.parser.jmeter.JmeterElementConverterRegister;
|
||||
import io.metersphere.plugin.api.spi.AbstractApiPlugin;
|
||||
import io.metersphere.plugin.api.spi.AbstractJmeterElementConverter;
|
||||
import io.metersphere.plugin.api.spi.JmeterElementConverter;
|
||||
|
@ -0,0 +1,46 @@
|
||||
package io.metersphere.api.utils;
|
||||
|
||||
import io.metersphere.api.parser.ms.MsTestElementParser;
|
||||
import io.metersphere.plugin.api.spi.MsTestElement;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @Author: jianxing
|
||||
* @CreateTime: 2024-08-28 11:46
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@AutoConfigureMockMvc
|
||||
public class MsTestElementParserTest {
|
||||
|
||||
@Test
|
||||
public void testParse() throws Exception {
|
||||
File httpJmx = new File(
|
||||
this.getClass().getClassLoader().getResource("file/http.jmx")
|
||||
.getPath()
|
||||
);
|
||||
Object scriptWrapper = SaveService.loadElement(new FileInputStream(httpJmx));
|
||||
HashTree hashTree = getHashTree(scriptWrapper);
|
||||
MsTestElementParser parser = new MsTestElementParser();
|
||||
MsTestElement msTestElement = parser.parse(hashTree);
|
||||
Assertions.assertNotNull(msTestElement);
|
||||
}
|
||||
|
||||
private HashTree getHashTree(Object scriptWrapper) throws Exception {
|
||||
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
return (HashTree) field.get(scriptWrapper);
|
||||
}
|
||||
}
|
76
backend/services/api-test/src/test/resources/file/http.jmx
Normal file
76
backend/services/api-test/src/test/resources/file/http.jmx
Normal file
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
|
||||
<hashTree>
|
||||
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" enabled="true">
|
||||
<boolProp name="TestPlan.functional_mode">false</boolProp>
|
||||
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
|
||||
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
|
||||
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
</TestPlan>
|
||||
<hashTree>
|
||||
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="172481082966700000" enabled="true">
|
||||
<intProp name="ThreadGroup.num_threads">1</intProp>
|
||||
<intProp name="ThreadGroup.ramp_time">1</intProp>
|
||||
<longProp name="ThreadGroup.delay">0</longProp>
|
||||
<longProp name="ThreadGroup.duration">0</longProp>
|
||||
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
|
||||
<boolProp name="ThreadGroup.scheduler">false</boolProp>
|
||||
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="LoopController" enabled="true">
|
||||
<intProp name="LoopController.loops">1</intProp>
|
||||
<boolProp name="LoopController.continue_forever">false</boolProp>
|
||||
</elementProp>
|
||||
</ThreadGroup>
|
||||
<hashTree>
|
||||
<UserParameters guiclass="UserParametersGui" testclass="UserParameters" testname="BBB_User Parameters" enabled="true">
|
||||
<boolProp name="UserParameters.per_iteration">true</boolProp>
|
||||
<collectionProp name="UserParameters.names">
|
||||
<stringProp name="-1422464715">testVar</stringProp>
|
||||
</collectionProp>
|
||||
<collectionProp name="UserParameters.thread_values">
|
||||
<collectionProp name="159016969">
|
||||
<stringProp name="159016938">2024-08-22 16:15:34</stringProp>
|
||||
</collectionProp>
|
||||
</collectionProp>
|
||||
</UserParameters>
|
||||
<hashTree/>
|
||||
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="BBB">
|
||||
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
|
||||
<collectionProp name="Arguments.arguments"/>
|
||||
</elementProp>
|
||||
<stringProp name="MS_RESOURCE_ID">2138842179805184</stringProp>
|
||||
<stringProp name="MS_STEP_ID">2138842179805184</stringProp>
|
||||
<stringProp name="MS_REPORT_ID">172481082966700000</stringProp>
|
||||
<stringProp name="PROJECT_ID">100001100001</stringProp>
|
||||
<stringProp name="HTTPSampler.method">GET</stringProp>
|
||||
<stringProp name="HTTPSampler.path">https://demo.halo.run/dddfsfs</stringProp>
|
||||
<stringProp name="HTTPSampler.connect_timeout">60000</stringProp>
|
||||
<stringProp name="HTTPSampler.response_timeout">60000</stringProp>
|
||||
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
|
||||
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
|
||||
</HTTPSamplerProxy>
|
||||
<hashTree>
|
||||
<AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="AuthManager" enabled="true">
|
||||
<collectionProp name="AuthManager.auth_list">
|
||||
<elementProp name="" elementType="Authorization">
|
||||
<stringProp name="Authorization.url"></stringProp>
|
||||
<stringProp name="Authorization.username">demo</stringProp>
|
||||
<stringProp name="Authorization.password">P@ssw0rd123..</stringProp>
|
||||
<stringProp name="Authorization.domain"></stringProp>
|
||||
<stringProp name="Authorization.realm"></stringProp>
|
||||
</elementProp>
|
||||
</collectionProp>
|
||||
</AuthManager>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
<DebugSampler guiclass="TestBeanGUI" testclass="DebugSampler" testname="RunningDebugSampler" enabled="true">
|
||||
<boolProp name="displayJMeterProperties">false</boolProp>
|
||||
<boolProp name="displayJMeterVariables">true</boolProp>
|
||||
<boolProp name="displaySystemProperties">false</boolProp>
|
||||
</DebugSampler>
|
||||
<hashTree/>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</hashTree>
|
||||
</jmeterTestPlan>
|
Loading…
Reference in New Issue
Block a user