├── .gitignore ├── README.md ├── pom.xml ├── src └── main │ ├── java │ └── com │ │ └── ly │ │ ├── AutoApplication.java │ │ ├── api │ │ ├── BaseDefaultApiTestCase.java │ │ ├── BaseHttpClient.java │ │ ├── BaseTestApiTestCase.java │ │ ├── DefaultApiClient.java │ │ └── TestApiClient.java │ │ ├── core │ │ ├── actuator │ │ │ ├── TestNgActuator.java │ │ │ ├── TestNgActuatorHandler.java │ │ │ ├── TestNgActuatorInterface.java │ │ │ └── TestNgRun.java │ │ ├── annotation │ │ │ ├── ApiAfterClass.java │ │ │ ├── ApiAfterMethod.java │ │ │ ├── ApiAfterSuite.java │ │ │ ├── ApiAfterTest.java │ │ │ ├── ApiBeforeClass.java │ │ │ ├── ApiBeforeMethod.java │ │ │ ├── ApiBeforeSuite.java │ │ │ ├── ApiBeforeTest.java │ │ │ ├── DataFile.java │ │ │ ├── DataModel.java │ │ │ ├── DataParams.java │ │ │ ├── Filters.java │ │ │ ├── HttpServer.java │ │ │ ├── RetryCount.java │ │ │ ├── TearDown.java │ │ │ ├── TestFail.java │ │ │ └── TestSetup.java │ │ ├── base │ │ │ ├── AutoParseResponseOptions.java │ │ │ ├── BaseFailHandle.java │ │ │ ├── BaseProcessorHandle.java │ │ │ ├── BaseTestCase.java │ │ │ ├── DataProviderUtils.java │ │ │ ├── ExtractResponseOptions.java │ │ │ ├── FailureResponseOptions.java │ │ │ ├── HookableProcessorUtil.java │ │ │ ├── ProcessorResponseOptions.java │ │ │ ├── Response.java │ │ │ ├── SaveDataResponseOptions.java │ │ │ └── ValidateResponseOptions.java │ │ ├── config │ │ │ ├── ApiBeanConfig.java │ │ │ ├── ApiBeanDefinitionRegistryPostProcessor.java │ │ │ ├── ApiClassPathScanningCandidateComponentProvider.java │ │ │ ├── DbConfig.java │ │ │ ├── DefaultConstantConfig.java │ │ │ ├── RedisClusterConfig.java │ │ │ └── RedisStandaloneConfig.java │ │ ├── db │ │ │ ├── BaseDbServer.java │ │ │ └── MysqlServer.java │ │ ├── enums │ │ │ ├── HttpType.java │ │ │ ├── MatchesEnum.java │ │ │ └── ModelType.java │ │ ├── exception │ │ │ ├── AssertionException.java │ │ │ └── BizException.java │ │ ├── listener │ │ │ ├── BaseLifeCycleListener.java │ │ │ ├── HeadersFilterAdapter.java │ │ │ ├── LifeCycleListenerProcessorUtil.java │ │ │ ├── RetryAnalyzer.java │ │ │ └── TestClassListener.java │ │ ├── matches │ │ │ ├── BaseSaveMatcher.java │ │ │ ├── BaseValidateMatcher.java │ │ │ ├── EqValidateMatcher.java │ │ │ ├── JsonPathValidateMatcher.java │ │ │ ├── PathValidateMatcherAdapter.java │ │ │ ├── SaveCache.java │ │ │ ├── SaveGlobalMatcher.java │ │ │ ├── SaveMethodMatcher.java │ │ │ ├── SaveThreadMatcher.java │ │ │ ├── Validate.java │ │ │ ├── ValidateByPathHandlers.java │ │ │ ├── ValidateUtils.java │ │ │ └── XPathValidateMatcher.java │ │ ├── notification │ │ │ ├── BaseNotificationServer.java │ │ │ ├── DingTalkNotificationHandler.java │ │ │ ├── DingTalkNotificationRequest.java │ │ │ ├── DingTalkNotificationSererImpl.java │ │ │ ├── MailNotificationHandler.java │ │ │ ├── MailNotificationRequest.java │ │ │ ├── MailNotificationServerImpl.java │ │ │ ├── NotificationContextHandler.java │ │ │ ├── NotificationHandler.java │ │ │ └── NotificationRequest.java │ │ ├── parse │ │ │ ├── BaseModel.java │ │ │ ├── CsvParse.java │ │ │ ├── CustomPropertyUtils.java │ │ │ ├── DataEntity.java │ │ │ ├── FormModel.java │ │ │ ├── Har2Yaml.java │ │ │ ├── JsonModel.java │ │ │ ├── JsonPath.java │ │ │ ├── MapToXml.java │ │ │ ├── MultipleModel.java │ │ │ ├── PathParse.java │ │ │ ├── RepresenterNotNull.java │ │ │ ├── SwaggerParse.java │ │ │ ├── TestCase.java │ │ │ ├── TextModel.java │ │ │ ├── XmlModel.java │ │ │ ├── Xpath.java │ │ │ └── YmlParse.java │ │ ├── proxy │ │ │ ├── ProxyApi.java │ │ │ ├── ProxyFactoryBean.java │ │ │ ├── ProxySupport.java │ │ │ └── ProxySupportBefore.java │ │ ├── redis │ │ │ ├── RedisConfig.java │ │ │ ├── RedisService.java │ │ │ └── RedisServiceImpl.java │ │ ├── restassured │ │ │ └── RestassuredHttpHandleBuilder.java │ │ ├── support │ │ │ ├── AllureSetStepHttpPostProcessor.java │ │ │ ├── CacheLifeCycleByClassPostProcessor.java │ │ │ ├── CacheLifeCyclePostProcessor.java │ │ │ ├── ConfigurationMethodSupportByClassPostProcessor.java │ │ │ ├── ConfigurationMethodSupportExcludeClassPostProcessor.java │ │ │ ├── HookHttpPostProcessor.java │ │ │ ├── HttpContext.java │ │ │ ├── HttpPostProcessor.java │ │ │ ├── MethodDefinition.java │ │ │ ├── MethodDefinitionHolder.java │ │ │ ├── NotificationPostProcessor.java │ │ │ ├── Order.java │ │ │ ├── PostProcessor.java │ │ │ ├── PostProcessorHolder.java │ │ │ ├── PrintLogPostProcessor.java │ │ │ ├── TestAnnotatinPostProcessor.java │ │ │ ├── TestNgClassPostProcessor.java │ │ │ ├── TestNgLifeCyclePostProcessor.java │ │ │ └── TestNgLifeCyclePostProcessorAdapter.java │ │ └── utils │ │ │ ├── AnnotationUtils.java │ │ │ ├── AssertUtils.java │ │ │ ├── ContextDataStorage.java │ │ │ ├── JSONSerializerUtil.java │ │ │ ├── MapTool.java │ │ │ ├── ParameterizationUtil.java │ │ │ ├── PatternUtil.java │ │ │ ├── ReflectUtil.java │ │ │ ├── ResourcesUtil.java │ │ │ ├── Source2Yaml.java │ │ │ ├── SpringContextUtil.java │ │ │ ├── TestCase2BaseModel.java │ │ │ ├── ThreadPool.java │ │ │ ├── ThreadUncaughtExceptionHandler.java │ │ │ └── Utils.java │ │ ├── example │ │ ├── TestExpressionOne.java │ │ ├── TestExpressionRetry.java │ │ └── TestExpressionTwo.java │ │ ├── failurecallback │ │ └── TestFailHandle.java │ │ ├── headersfilter │ │ ├── RestAssuredLogFilter.java │ │ └── TestHeadersFilter.java │ │ ├── plugin │ │ └── PluginSupport.java │ │ ├── processorcallback │ │ └── TestProcessorCallback.java │ │ └── testcase │ │ └── ExampleApiTestCase.java │ └── resources │ ├── allure.properties │ ├── application-qa.yml │ ├── application-release.yml │ ├── application.yml │ ├── example.yml │ └── logback.xml ├── testng-test.xml └── testng-thread.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.iml 3 | /bin/ 4 | .idea/ 5 | log/ -------------------------------------------------------------------------------- /src/main/java/com/ly/AutoApplication.java: -------------------------------------------------------------------------------- 1 | package com.ly; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class AutoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(AutoApplication.class, args); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/api/BaseDefaultApiTestCase.java: -------------------------------------------------------------------------------- 1 | package com.ly.api; 2 | 3 | import com.ly.core.base.BaseTestCase; 4 | import io.qameta.allure.Feature; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2020/3/27 16:09. 10 | */ 11 | //Features:标注主要功能模块 12 | //该类用于引入必要client 13 | //主要通过模块base类来标注,该模块下所有都继承该base类 14 | @Feature("default-api") 15 | public abstract class BaseDefaultApiTestCase extends BaseTestCase { 16 | @Autowired 17 | protected DefaultApiClient apiClient; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ly/api/BaseHttpClient.java: -------------------------------------------------------------------------------- 1 | package com.ly.api; 2 | 3 | import com.ly.core.base.Response; 4 | import com.ly.core.parse.BaseModel; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/5/15 17:31. 11 | */ 12 | public interface BaseHttpClient { 13 | /** 14 | * 调用接口前等待时间 15 | * @param unit 16 | * @param interval 17 | * @return 18 | */ 19 | T wait(TimeUnit unit, long interval); 20 | 21 | /** 22 | * 调用接口前设置一个作用域在该请求级别的缓存 23 | * @param k 24 | * @param v 25 | * @return 26 | */ 27 | T saveAsk(String k, Object v); 28 | 29 | /** 30 | * 调用接口前设置一个作用域在该测试方法的缓存 31 | * @param k 32 | * @param v 33 | * @return 34 | */ 35 | T saveMethod(String k, Object v); 36 | 37 | /** 38 | * 调用接口前设置一个作用域在该线程的缓存 39 | * @param k 40 | * @param v 41 | * @return 42 | */ 43 | T saveThread(String k, Object v); 44 | 45 | /** 46 | * 调用接口前设置一个作用域全局的缓存 47 | * @param k 48 | * @param v 49 | * @return 50 | */ 51 | T saveGlobal(String k, Object v); 52 | 53 | /** 54 | * http请求发起 55 | * @param model 56 | * @return 57 | */ 58 | Response doHttp(BaseModel model); 59 | 60 | /** 61 | * http请求发起 62 | * @param modelName 63 | * @return 64 | */ 65 | Response doHttp(String modelName); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/ly/api/BaseTestApiTestCase.java: -------------------------------------------------------------------------------- 1 | package com.ly.api; 2 | 3 | import com.ly.core.base.BaseTestCase; 4 | import com.ly.plugin.PluginSupport; 5 | import io.qameta.allure.Feature; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/3/27 16:09. 11 | */ 12 | //Features:标注主要功能模块 13 | //该类用于引入必要client 14 | //主要通过模块base类来标注,该模块下所有都继承该base类 15 | @Feature("test-api") 16 | public abstract class BaseTestApiTestCase extends BaseTestCase { 17 | 18 | @Autowired 19 | protected PluginSupport support; 20 | 21 | @Autowired 22 | protected TestApiClient testApiClient; 23 | 24 | @Autowired 25 | protected DefaultApiClient apiClient; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ly/api/DefaultApiClient.java: -------------------------------------------------------------------------------- 1 | package com.ly.api; 2 | 3 | import com.ly.core.annotation.Filters; 4 | import com.ly.core.annotation.HttpServer; 5 | import com.ly.headersfilter.RestAssuredLogFilter; 6 | 7 | /** 8 | * 该类做通用默认调用,只添加一个日志过滤器 9 | * @Author: luoy 10 | * @Date: 2020/5/14 11:05. 11 | */ 12 | @HttpServer 13 | @Filters({RestAssuredLogFilter.class}) 14 | public interface DefaultApiClient extends BaseHttpClient{ 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ly/api/TestApiClient.java: -------------------------------------------------------------------------------- 1 | package com.ly.api; 2 | 3 | import com.ly.core.annotation.Filters; 4 | import com.ly.core.annotation.HttpServer; 5 | import com.ly.headersfilter.RestAssuredLogFilter; 6 | import com.ly.headersfilter.TestHeadersFilter; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/5/14 11:05. 11 | */ 12 | @HttpServer(baseUrl = "${test.url}") 13 | @Filters({RestAssuredLogFilter.class, TestHeadersFilter.class}) 14 | public interface TestApiClient extends BaseHttpClient{ 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/actuator/TestNgActuatorHandler.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.actuator; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.lang.reflect.InvocationHandler; 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.Arrays; 9 | 10 | /** 11 | * 动态代理的方式处理运行前与运行后 12 | * @Author: luoy 13 | * @Date: 2020/6/2 16:57. 14 | */ 15 | @Slf4j 16 | public class TestNgActuatorHandler implements InvocationHandler { 17 | private TestNgActuator actuator; 18 | 19 | public TestNgActuatorHandler(TestNgActuator actuator) { 20 | this.actuator = actuator; 21 | } 22 | 23 | @Override 24 | public Object invoke(Object proxy, Method method, Object[] args) { 25 | Object result = null; 26 | try { 27 | before(method, args); 28 | result = method.invoke(actuator, args); 29 | after(result); 30 | } catch (IllegalAccessException e) { 31 | e.printStackTrace(); 32 | } catch (InvocationTargetException e) { 33 | e.printStackTrace(); 34 | } 35 | return result; 36 | } 37 | 38 | private void before(Method method, Object[] args) { 39 | log.info("=====================runStart====================="); 40 | log.info("run method:{}, args: {}, time:{}", method.getName(), Arrays.toString(args), System.currentTimeMillis()); 41 | } 42 | 43 | private void after(Object result) { 44 | log.info("=====================runEnd====================="); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/actuator/TestNgActuatorInterface.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.actuator; 2 | 3 | import org.testng.xml.XmlSuite; 4 | 5 | /** 6 | * @Author: luoy 7 | */ 8 | public interface TestNgActuatorInterface { 9 | 10 | /** 11 | * 根据groups运行测试用例 12 | * @param name 13 | * @param groupsName 14 | */ 15 | void runGroups(String name, String...groupsName); 16 | 17 | /** 18 | * 根据类运行测试用例 19 | * @param name 20 | * @param classesName 21 | */ 22 | void runClasses(String name, String...classesName); 23 | 24 | /** 25 | * 根据类中方法运行测试用例 26 | * @param name 27 | * @param classesName 28 | */ 29 | void runClasses4Methods(String name, String classesName, String...methodName); 30 | 31 | /** 32 | * 指定groups运行 33 | * @param suiteName 34 | * @param testName 35 | * @param parallelMode 36 | * 并发级别 37 | * @param threadCount 38 | * 并发线程数 39 | * @param groupsName 40 | * groups名字 41 | */ 42 | void runGroups(String suiteName, String testName, XmlSuite.ParallelMode parallelMode, int threadCount, String...groupsName); 43 | 44 | /** 45 | * 指定类运行 46 | * @param suiteName 47 | * @param testName 48 | * @param parallelMode 49 | * @param threadCount 50 | * @param className 51 | */ 52 | void runClasses(String suiteName, String testName, XmlSuite.ParallelMode parallelMode, int threadCount, String...className); 53 | 54 | /** 55 | * 指定类与类中方法运行 56 | * @param suiteName 57 | * @param testName 58 | * @param parallelMode 59 | * @param threadCount 60 | * @param className 61 | * @param methodName 62 | */ 63 | void runClasses4Methods(String suiteName, String testName, XmlSuite.ParallelMode parallelMode, int threadCount, String className, String...methodName); 64 | 65 | /** 66 | * 失败重跑 默认重跑的suitename为TestNgActuator.DEFAULT_SUITE_NAME 67 | */ 68 | void runFailed(); 69 | 70 | /** 71 | * 失败重跑 72 | * @param suiteName 73 | */ 74 | void runFailed(String suiteName); 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/actuator/TestNgRun.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.actuator; 2 | 3 | import org.testng.xml.XmlSuite; 4 | 5 | import java.lang.reflect.Proxy; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2020/6/2 17:13. 10 | */ 11 | public class TestNgRun implements TestNgActuatorInterface{ 12 | 13 | public TestNgActuatorInterface proxy = (TestNgActuatorInterface) Proxy.newProxyInstance(TestNgActuator.getInstance().getClass().getClassLoader(), 14 | new Class[]{TestNgActuatorInterface.class}, 15 | new TestNgActuatorHandler(TestNgActuator.getInstance())); 16 | 17 | @Override 18 | public void runGroups(String name, String...groupsName) { 19 | proxy.runGroups(name, groupsName); 20 | } 21 | 22 | @Override 23 | public void runClasses(String name, String... classesName) { 24 | proxy.runClasses(name, classesName); 25 | } 26 | 27 | @Override 28 | public void runClasses4Methods(String name, String classesName, String... methodName) { 29 | proxy.runClasses4Methods(name, classesName, methodName); 30 | } 31 | 32 | @Override 33 | public void runGroups(String suiteName, String testName, XmlSuite.ParallelMode parallelMode, int threadCount, String... groupsName) { 34 | proxy.runGroups(suiteName, testName, parallelMode, threadCount, groupsName); 35 | } 36 | 37 | @Override 38 | public void runClasses(String suiteName, String testName, XmlSuite.ParallelMode parallelMode, int threadCount, String... className) { 39 | proxy.runClasses(suiteName, testName, parallelMode, threadCount, className); 40 | } 41 | 42 | @Override 43 | public void runClasses4Methods(String suiteName, String testName, XmlSuite.ParallelMode parallelMode, int threadCount, String className, String... methodName) { 44 | proxy.runClasses4Methods(suiteName, testName, parallelMode, threadCount, className, methodName); 45 | } 46 | 47 | @Override 48 | public void runFailed() { 49 | proxy.runFailed(); 50 | } 51 | 52 | @Override 53 | public void runFailed(String suiteName) { 54 | proxy.runFailed(suiteName); 55 | } 56 | 57 | 58 | public static void main(String[] args) { 59 | TestNgRun run = new TestNgRun(); 60 | run.runGroups("api-test", "api"); 61 | 62 | // run.runFailed(); 63 | // run.runClasses("api-test", "com.ly.testcase.ExampleApiTestCase", "com.ly.testcase.ExampleApiTestCase"); 64 | // run.runClasses("测试", "api-test", XmlSuite.ParallelMode.CLASSES, 2, "com.ly.testcase.ExampleApiTestCase", "com.ly.testcase.ExampleApiTestCase"); 65 | // run.runClasses4Methods("", "com.ly.testcase.ExampleApiTestCase", "login"); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiAfterClass.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> AfterClass 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiAfterClass { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiAfterMethod.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> AfterMethod 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiAfterMethod { 16 | /** 17 | * 排除的方法 18 | */ 19 | String[] excludeOnMethod() default {}; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiAfterSuite.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> AfterSuite 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiAfterSuite { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiAfterTest.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> AfterTest 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiAfterTest { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiBeforeClass.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> BeforeClass 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiBeforeClass { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiBeforeMethod.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> BeforeMethod 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiBeforeMethod { 16 | /** 17 | * 排除的方法 18 | */ 19 | String[] excludeOnMethod() default {}; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiBeforeSuite.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> BeforeSuite 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiBeforeSuite { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/ApiBeforeTest.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * ==> BeforeTest 10 | * @Author: luoy 11 | * 测试结束操作 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD}) 15 | public @interface ApiBeforeTest { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/DataFile.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | import java.lang.annotation.Target; 6 | 7 | /** 8 | * Created by luoy on 2019/6/17. 9 | *

数据驱动设置数据读取方式与数据路径 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({java.lang.annotation.ElementType.METHOD}) 13 | public @interface DataFile { 14 | Format format() default Format.CSV; 15 | 16 | String path(); 17 | 18 | enum Format 19 | { 20 | CSV, 21 | EXCEL, 22 | XML, 23 | YML, 24 | JSON 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/DataModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | import java.lang.annotation.Target; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2019/8/2 10:52. 10 | * 11 | *

数据驱动 yaml模式 12 | *

当Format= SINGLE, vaule = {"login", "login1"} 13 | * 表示该用例是单接口模式 14 | * 用例入参模型为: BaseModel 15 | * 入参值为yaml文档name对应的login与login1和这两个name下的所有parameters 16 | * eg. 17 | * @DataModel(value = {"login", "login1"}, format = DataModel.Format.SINGLE) 18 | * public void login(BaseModel model) { 19 | * apiClient.doHttp(model).auto(); 20 | * } 21 | * 22 | *

当Format= MULTIPLE, vaule = {"login", "profile"} 23 | * 表示该用例是业务流模式,只会运行一次,业务对应入参可从 24 | * 用例入参模型为: MultipleModel 25 | * value可省略 26 | * eg. 27 | * @DataModel(format = DataModel.Format.MULTIPLE) 28 | * public void login1(MultipleModel model) { 29 | * defaultApiClient.doHttp(model.getModel("login")).auto(); 30 | * defaultApiClient.doHttp(model.getModel("profile")).auto(); 31 | * } 32 | * 33 | */ 34 | @Retention(RetentionPolicy.RUNTIME) 35 | @Target({java.lang.annotation.ElementType.METHOD}) 36 | public @interface DataModel { 37 | /** 取yaml的name */ 38 | String[] value() default {}; 39 | 40 | /** yaml文件名字,支持多个文件引入,默认路径为 resources下的data.yml */ 41 | String[] path() default {"data.yml"}; 42 | 43 | /** 模式 */ 44 | DataModel.Format format() default Format.MULTIPLE; 45 | 46 | enum Format 47 | { 48 | /**单接口**/ 49 | SINGLE, 50 | /**串行**/ 51 | MULTIPLE 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/DataParams.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | import java.lang.annotation.Target; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2019/8/2 10:52. 10 | * 11 | *

数据驱动直接设置数据 12 | * 数据格式: 13 | * 单次运行数据:{"13000000001,000001"} 14 | * 多次运行数据:{"13000000001,000001","13000000002,000002"} 15 | * 数据个数与测试方法入参个数一致 16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target({java.lang.annotation.ElementType.METHOD}) 19 | public @interface DataParams { 20 | 21 | String[] value() default {}; 22 | 23 | String splitBy() default ","; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/Filters.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import io.restassured.filter.Filter; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2019/8/23 11:03. 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({java.lang.annotation.ElementType.TYPE, ElementType.METHOD}) 16 | public @interface Filters { 17 | Class[] value(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/HttpServer.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | import java.lang.annotation.Target; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2019/8/21 17:17. 10 | * 11 | * 接口类标注,必填,否则无法扫描到容器,无法注入 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({java.lang.annotation.ElementType.TYPE}) 15 | public @interface HttpServer { 16 | /** 17 | * 非必填
18 | * 支持${}通配符,只需要在配置文件spring.httprul下配置即可
19 | * 优先取接口上url,如果接口url为带http/https则最终请求url为接口url, 如果不带http/https,则最终请求url为baseUrl + 接口url 20 | * @return 21 | */ 22 | String baseUrl() default ""; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/RetryCount.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | 9 | /** 10 | *

设置testCase重试次数, default=3 11 | *

可在类上直接加该注解,作用域整个类 12 | *

可在方法上加该注解,作用域该方法 13 | *

如果两种都加了,优先取方法上count 14 | *

如果ide内直接运行类该注解需要起作用需要设置@Test(retryAnalyzer = RetryAndlyzer.class) 15 | *

如果在testng.xml中运行,@Test注解中不需要再增加retryAnalyzer,需要增加过滤器: 16 | * {@code 17 | * 18 | * 19 | * 20 | * } 21 | */ 22 | @Retention(RetentionPolicy.RUNTIME) 23 | @Target({java.lang.annotation.ElementType.METHOD, TYPE}) 24 | public @interface RetryCount { 25 | int count() default 3; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/TearDown.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Author: luoy 10 | * 测试结束操作 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.TYPE, ElementType.METHOD}) 14 | public @interface TearDown { 15 | String[] value(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/TestFail.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Author: luoy 10 | * 测试错误操作 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.TYPE, ElementType.METHOD}) 14 | public @interface TestFail { 15 | String[] value(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/annotation/TestSetup.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Author: luoy 10 | * 测试开始操作 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.TYPE, ElementType.METHOD}) 14 | public @interface TestSetup { 15 | String[] value(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/AutoParseResponseOptions.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2020/5/6 15:58. 8 | */ 9 | public interface AutoParseResponseOptions { 10 | 11 | /** 12 | * 等待毫秒数 13 | * @param ms 14 | * @return 15 | */ 16 | R waitMs(long ms); 17 | 18 | /** 19 | *等待时间 20 | * @param unit 21 | * @param time 22 | * @return 23 | */ 24 | R wait(TimeUnit unit, long time); 25 | 26 | /** 27 | * 自动解析yml文件, 默认返回值为200 28 | * @return 29 | */ 30 | R auto(); 31 | 32 | /** 33 | * 自动解析yml文件, 返回值为httpStatusCode 34 | * @param httpStatusCode 35 | * @return 36 | */ 37 | R auto(int httpStatusCode); 38 | 39 | /** 40 | * 自动解析yml文件, 但是不会自动调用done()方法结束,需要手动调用done结束 41 | * 主要用于给该http请求添加更多的自定义处理 42 | * eg. autoExcludeDone().saveThread("a", "a").done(); 43 | * @return 44 | */ 45 | R autoExcludeDone(); 46 | 47 | /** 48 | * 手动设置httpStatusCode,处理与autoExcludeDone()一致 49 | * @param httpStatusCode 50 | * @return 51 | */ 52 | R autoExcludeDone(int httpStatusCode); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/BaseFailHandle.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/5/6 14:17 6 | */ 7 | @FunctionalInterface 8 | public interface BaseFailHandle { 9 | void handle(T t); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/BaseProcessorHandle.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/5/6 14:17 6 | */ 7 | @FunctionalInterface 8 | public interface BaseProcessorHandle { 9 | void processor(T t); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/BaseTestCase.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | import com.ly.core.listener.BaseLifeCycleListener; 4 | import com.ly.core.listener.TestClassListener; 5 | import com.ly.core.parse.BaseModel; 6 | import com.ly.core.parse.MultipleModel; 7 | import com.ly.core.utils.ContextDataStorage; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; 10 | import org.testng.IHookCallBack; 11 | import org.testng.ITestResult; 12 | import org.testng.annotations.DataProvider; 13 | import org.testng.annotations.Listeners; 14 | 15 | import java.lang.reflect.Method; 16 | 17 | /** 18 | * 19 | * @author luoy 20 | * @date 2019/7/12 21 | */ 22 | //properties属性指定本地测试需要用到的配置文件 23 | //本地运行时,需要设置properties值为具体的某个配置文件 24 | @SpringBootTest(properties = {"spring.profiles.active=qa"}) 25 | //通过maven命令运行时需要把该参数去掉 26 | //@SpringBootTest 27 | @Listeners({BaseLifeCycleListener.class, TestClassListener.class}) 28 | public abstract class BaseTestCase extends AbstractTestNGSpringContextTests { 29 | public static final String PARAMETER_KEY = "&&PARAMETER_KEY"; 30 | /** 31 | * 数据驱动,frameworkMethod运行的测试类 32 | * @param method 33 | * @return 34 | */ 35 | @DataProvider 36 | public static Object[][] loadDataFile(Method method){ 37 | return DataProviderUtils.loadDataFile(method); 38 | } 39 | 40 | /** 41 | * 数据驱动 42 | * @param method 43 | * @return 44 | */ 45 | @DataProvider 46 | public static Object[][] loadDataParams(Method method){ 47 | return DataProviderUtils.loadDataParams(method); 48 | } 49 | 50 | /** 51 | * 数据驱动 52 | * @param method 53 | * @return 54 | */ 55 | @DataProvider 56 | public static Object[][] loadDataModel(Method method) { 57 | return DataProviderUtils.loadDataModel(method); 58 | } 59 | 60 | @Override 61 | protected void springTestContextPrepareTestInstance() throws Exception { 62 | super.springTestContextPrepareTestInstance(); 63 | } 64 | 65 | /** 66 | * 把spring容器启动设置为测试类实例化时候 67 | */ 68 | public BaseTestCase() { 69 | super(); 70 | try { 71 | springTestContextPrepareTestInstance(); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } 75 | } 76 | 77 | /** 78 | * IHookable 监听器提供了类似与面向方面编程(AOP)中的 Around Advice 的功能。 79 | * 它在测试方法执行前后提供了切入点,从而使用户能够在测试方法运行前后注入特定的功能。 80 | * 例如,用户可以在当前测试方法运行前加入特定的验证逻辑以决定测试方法是否运行或者跳过,甚至覆盖测试方法的逻辑。 81 | * 下面是 IHookable 监听器要求实现的方法签名。 82 | * 83 | * void run(IHookCallBack callBack, ITestResult testResult) 84 | * 如要运行原始测试方法逻辑,需要调用 runTestMethod 方法。 85 | * 86 | * callBack.runTestMethod(testResult); 87 | * @param callBack 88 | * @param testResult 89 | */ 90 | @Override 91 | public void run(IHookCallBack callBack, ITestResult testResult) { 92 | //方法请求参数保存下 93 | Object[] parameters = callBack.getParameters(); 94 | if(parameters.length == 1) { 95 | if(parameters[0] instanceof BaseModel || parameters[0] instanceof MultipleModel) { 96 | ContextDataStorage.getInstance().setMethodAttribute(BaseTestCase.PARAMETER_KEY, parameters[0]); 97 | } 98 | } 99 | callBack.runTestMethod(testResult); 100 | } 101 | 102 | /** 103 | * Features:标注主要功能模块 104 | * 主要通过模块base类来标注,该模块下所有都继承该base类 105 | * Stories:标注Features功能模块下的分支功能 106 | * Title:标注Stories下测试用例名称 107 | * Step:标注测试用例的重要步骤 108 | * Severity:标注测试用例的重要级别 109 | * Description: 标注测试用例的描述 110 | * Issue和TestCaseId据说是可以集成bug管理系统的 111 | * 112 | * 1. Blocker级别——中断缺陷 113 | * 客户端程序无响应,无法执行下一步操作。 114 | * 2. Critical级别――临界缺陷,包括: 115 | * 功能点缺失,客户端爆页。 116 | * 3. Major级别——较严重缺陷,包括: 117 | * 功能点没有满足需求。 118 | * 4. Normal级别――普通缺陷,包括: 119 | * 1. 数值计算错误 120 | * 2. JavaScript错误。 121 | * 5. Minor级别———次要缺陷,包括: 122 | * 1. 界面错误与UI需求不符。 123 | * 2. 打印内容、格式错误 124 | * 3. 程序不健壮,操作未给出明确提示。 125 | * 6. Trivial级别——轻微缺陷,包括: 126 | * 1. 辅助说明描述不清楚 127 | * 2. 显示格式不规范,数字,日期等格式。 128 | * 3. 长时间操作未给用户进度提示 129 | * 4. 提示窗口文字未采用行业术语 130 | * 5. 可输入区域和只读区域没有明显的区分标志 131 | * 6. 必输项无提示,或者提示不规范。 132 | * 7. Enhancement级别——测试建议、其他(非缺陷) 133 | * 1. 以客户角度的易用性测试建议。 134 | * 2. 通过测试挖掘出来的潜在需求。 135 | */ 136 | public void intor() { 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/ExtractResponseOptions.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | import com.ly.core.parse.BaseModel; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2020/5/6 16:01. 10 | */ 11 | public interface ExtractResponseOptions { 12 | /** 13 | * 获取返回头 14 | * @param header 15 | * @return 16 | */ 17 | String getHeader(String header); 18 | 19 | /** 20 | * 获取返回值为json的body 21 | * @return 22 | */ 23 | String getJsonBody(); 24 | 25 | /** 26 | * 获取返回值为xml的body 27 | * @return 28 | */ 29 | Object getXmlBody(); 30 | 31 | /** 32 | * 获取返回值为html的body 33 | * @return 34 | */ 35 | Object getHtmlBody(); 36 | 37 | /** 38 | *获取所有返回头 39 | * @return 40 | */ 41 | Map getHeaders(); 42 | 43 | /** 44 | * 获取返回状态code 45 | * @return 46 | */ 47 | int getStatusCode(); 48 | 49 | /** 50 | * 获取sessionId 51 | * @return 52 | */ 53 | String getSessionId(); 54 | 55 | /** 56 | * 获取cookie 57 | * @param name 58 | * @return 59 | */ 60 | String getCookie(String name); 61 | 62 | /** 63 | * 获取所有cookie 64 | * @return 65 | */ 66 | Map cookies(); 67 | 68 | /** 69 | * 获取path位置值 70 | * @param path 71 | * @return 72 | */ 73 | Object getPath(String path); 74 | 75 | /** 76 | * 获取请求信息 77 | * @return 78 | */ 79 | BaseModel getRequests(); 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/FailureResponseOptions.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/5/6 15:58. 6 | */ 7 | public interface FailureResponseOptions { 8 | /** 9 | * 自定义无入参处理 实现BaseFailHandle类并且重写handle方法或者lambda表达式编写 10 | * @param failHandle 11 | * @return 12 | */ 13 | R onFailure(BaseFailHandle failHandle); 14 | 15 | /** 16 | * 自定义有入参处理 17 | * @param t 18 | * 传入参数 19 | * @param failHandle 20 | * 失败处理逻辑 21 | * @return 22 | */ 23 | R onFailure(T t, BaseFailHandle failHandle); 24 | 25 | /** 26 | * 支持通配符${} 与Path 27 | * @param expressions 28 | * @param failHandle 29 | * @return 30 | */ 31 | R onFailureByExpr(String expressions, BaseFailHandle failHandle); 32 | 33 | /** 34 | * 自定义无入参处理 clazz类型为实现BaseFailHandle类 35 | * @param clazz 36 | * @param args 37 | * 有参构造函数入参 38 | * @return 39 | */ 40 | R onFailure(Class> clazz, Object...args); 41 | 42 | /** 43 | * 自定义有入参处理 44 | * @param t 45 | * 传入参数 46 | * @param clazz 47 | * 失败处理逻辑 类型为实现BaseFailHandle类 48 | * @param args 49 | * 有参构造函数入参 50 | * @return 51 | */ 52 | R onFailure(T t, Class> clazz, Object...args); 53 | 54 | /** 55 | * 支持通配符${} 与path 56 | * @param expressions 57 | * @param clazz 类型为实现BaseFailHandle类 58 | * @param args 59 | * 有参构造函数入参 60 | * @return 61 | */ 62 | R onFailureByExpr(String expressions, Class> clazz, Object...args); 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/HookableProcessorUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | import com.ly.core.annotation.TearDown; 4 | import com.ly.core.annotation.TestFail; 5 | import com.ly.core.config.DefaultConstantConfig; 6 | import com.ly.core.utils.ReflectUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.testng.ITestResult; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | 12 | /** 13 | * IHookable通用处理器 14 | * @Author: luoy 15 | * @Date: 2019/9/5 15:38. 16 | */ 17 | @Slf4j 18 | public class HookableProcessorUtil { 19 | 20 | private static volatile HookableProcessorUtil INSTANCE; 21 | 22 | private HookableProcessorUtil() {} 23 | 24 | public static HookableProcessorUtil create() { 25 | if (INSTANCE == null) { 26 | synchronized (HookableProcessorUtil.class) { 27 | if (INSTANCE == null) { 28 | INSTANCE = new HookableProcessorUtil(); 29 | } 30 | } 31 | } 32 | return INSTANCE; 33 | } 34 | 35 | /** 36 | * 用例失败后处理 37 | * @param result 38 | * @return 39 | */ 40 | public HookableProcessorUtil testFail(ITestResult result) { 41 | if(isFail(result)) { 42 | TestFail testFail = result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(TestFail.class); 43 | if (testFail != null) { 44 | ReflectUtil.hooksCall(testFail.value(), DefaultConstantConfig.TEST_ANNOTATION_HOOK_METHOD_BY_CLASS); 45 | } 46 | } 47 | return this; 48 | } 49 | 50 | /** 51 | * 用例成功后处理 52 | * @param result 53 | * @return 54 | */ 55 | public HookableProcessorUtil tearDown(ITestResult result) { 56 | if(!isFail(result)) { 57 | TearDown tearDown = result.getMethod().getConstructorOrMethod().getMethod().getAnnotation(TearDown.class); 58 | if (tearDown != null) { 59 | ReflectUtil.hooksCall(tearDown.value(), DefaultConstantConfig.TEST_ANNOTATION_HOOK_METHOD_BY_CLASS); 60 | } 61 | } 62 | return this; 63 | } 64 | 65 | /** 66 | * 打印日志 67 | * @param testResult 68 | * @return 69 | */ 70 | public HookableProcessorUtil logPrint(ITestResult testResult) { 71 | if(isFail(testResult)) { 72 | if (testResult.getThrowable() instanceof InvocationTargetException) { 73 | log.error("error", testResult.getThrowable().getCause()); 74 | } else { 75 | log.error("error", testResult.getThrowable()); 76 | } 77 | } 78 | return this; 79 | } 80 | 81 | private boolean isFail(ITestResult testResult) { 82 | return testResult.getThrowable() != null; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/ProcessorResponseOptions.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/5/6 15:58. 6 | */ 7 | public interface ProcessorResponseOptions { 8 | /** 9 | * 自定义无入参处理 实现BaseProcessorHandle类并且重写processor方法或者lambda表达式编写 10 | * @param processorHandle 11 | * @return 12 | */ 13 | R processor(BaseProcessorHandle processorHandle); 14 | 15 | /** 16 | * 自定义有入参处理 17 | * @param t 18 | * 需要传入的参数 19 | * @param processorHandle 20 | * 处理逻辑 21 | * @return 22 | */ 23 | R processor(T t, BaseProcessorHandle processorHandle); 24 | 25 | /** 26 | * 支持通配符${} 与path表达式 27 | * @param expressions 28 | * @param processorHandle 29 | * @return 30 | */ 31 | R processorByExpr(String expressions, BaseProcessorHandle processorHandle); 32 | 33 | /** 34 | * 自定义无入参处理 processorHandle extends BaseProcessorHandle 35 | * @param processorHandle 36 | * @param args 37 | * 有参构造函数入参 38 | * @return 39 | */ 40 | R processor(Class> processorHandle, Object...args); 41 | 42 | /** 43 | * 自定义有入参处理 processorHandle extends BaseProcessorHandle 44 | * @param object 45 | * 需要传入的参数 46 | * @param processorHandle 47 | * 处理逻辑 48 | * @param args 49 | * 有参构造函数入参 50 | * @return 51 | */ 52 | R processor(T object, Class> processorHandle, Object...args); 53 | 54 | /** 55 | * 支持通配符${} 与Path表达式 processorHandle extends BaseProcessorHandle 56 | * @param expressions 57 | * @param processorHandle 58 | * @param args 59 | * 有参构造函数入参 60 | * @return 61 | */ 62 | R processorByExpr(String expressions, Class> processorHandle, Object...args); 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/SaveDataResponseOptions.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/5/6 15:58. 6 | */ 7 | public interface SaveDataResponseOptions { 8 | /** 9 | * 存储全局缓存 10 | * @param key 11 | * @param path 12 | * @return 13 | */ 14 | R saveGlobal(String key, String path); 15 | 16 | /** 17 | * 存入一个作用域在该测试方法的缓存 18 | * @param key 19 | * @param path 20 | * @return 21 | */ 22 | R saveMethod(String key, String path); 23 | 24 | /** 25 | * 存入一个作用域在该class的缓存 26 | * @param key 27 | * @param path 28 | * @return 29 | */ 30 | R saveClass(String key, String path); 31 | 32 | /** 33 | * 存入一个作用域在当前线程运行的所有用例 34 | * @param key 35 | * @param path 36 | * @return 37 | */ 38 | R saveThread(String key, String path); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/base/ValidateResponseOptions.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.base; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * @Author: luoy 8 | * @Date: 2020/5/6 15:56. 9 | */ 10 | public interface ValidateResponseOptions { 11 | /** 12 | * path方式断言, 如果返回content-type 不是application/json与application/xml 无法使用该断言 13 | * @param path 14 | * @param matcher 15 | * org.hamcrest.Matche 16 | * @return 17 | */ 18 | R validate(String path, org.hamcrest.Matcher matcher); 19 | 20 | /** 21 | * yaml方式断言 22 | * @param validate 23 | * @return 24 | */ 25 | R validate(Map> validate); 26 | 27 | /** 28 | * 比较值是否与expected相等 29 | * @param actual 30 | * @param expected 31 | * @return 32 | */ 33 | R eq(Object actual, Object expected); 34 | 35 | /** 36 | * 比较Path是否与expected相等 37 | * path方式断言, 如果返回content-type 不是application/json与application/xml 无法使用该断言 38 | * @param path 39 | * @param expected 40 | * @return 41 | */ 42 | R eqByPath(String path, Object expected); 43 | 44 | /** 45 | * 调用方法的方式验证 46 | * @param method 47 | * @param args 48 | * @return 49 | */ 50 | R validatePlugin(String method, Object...args); 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/ApiBeanConfig.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2019/8/26 16:03. 10 | * 11 | */ 12 | @Configuration 13 | public class ApiBeanConfig { 14 | 15 | /** 16 | * api框架中需要扫描自动注入的api接口路径 17 | */ 18 | private static final String[] scanning = {""}; 19 | 20 | @ConditionalOnMissingBean(ApiBeanDefinitionRegistryPostProcessor.class) 21 | @Bean 22 | public static ApiBeanDefinitionRegistryPostProcessor apiBeanDefinitionRegistryPostProcessor() { 23 | return new ApiBeanDefinitionRegistryPostProcessor(scanning); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/ApiBeanDefinitionRegistryPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import com.ly.core.annotation.HttpServer; 4 | import com.ly.core.exception.BizException; 5 | import com.ly.core.proxy.ProxyFactoryBean; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.config.BeanDefinition; 8 | import org.springframework.beans.factory.config.BeanDefinitionHolder; 9 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 10 | import org.springframework.beans.factory.support.BeanDefinitionBuilder; 11 | import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; 12 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 13 | import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; 14 | 15 | import java.util.Set; 16 | 17 | /** 18 | * @Author: luoy 19 | * @Date: 2019/9/6 14:18. 20 | */ 21 | public class ApiBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { 22 | 23 | private String[] scanning; 24 | 25 | public ApiBeanDefinitionRegistryPostProcessor(String...scanning) { 26 | this.scanning = scanning; 27 | } 28 | 29 | @Override 30 | public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 31 | //创建扫描类 32 | ApiClassPathScanningCandidateComponentProvider beanScanner = new ApiClassPathScanningCandidateComponentProvider(HttpServer.class); 33 | //创建自己扫描规则,扫描所有带@HttpServer注解的类 34 | 35 | Set beanDefinitions = beanScanner.findCandidateComponents(scanning); 36 | for (BeanDefinition beanDefinition : beanDefinitions) { 37 | Class clazz; 38 | try { 39 | clazz = Class.forName(beanDefinition.getBeanClassName()); 40 | } catch (ClassNotFoundException e) { 41 | throw new BizException("class not found:" + beanDefinition.getBeanClassName()); 42 | } 43 | BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProxyFactoryBean.class); 44 | String beanName = beanDefinition.getBeanClassName(); 45 | beanDefinitionBuilder.addPropertyValue("clazz", clazz); 46 | 47 | BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinitionBuilder.getBeanDefinition(), beanName); 48 | BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); 49 | } 50 | } 51 | 52 | @Override 53 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/ApiClassPathScanningCandidateComponentProvider.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import com.ly.core.exception.BizException; 4 | import org.assertj.core.util.Sets; 5 | import org.springframework.beans.factory.config.BeanDefinition; 6 | import org.springframework.context.annotation.ScannedGenericBeanDefinition; 7 | import org.springframework.core.io.Resource; 8 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 9 | import org.springframework.core.type.ClassMetadata; 10 | import org.springframework.core.type.classreading.CachingMetadataReaderFactory; 11 | import org.springframework.core.type.classreading.MetadataReader; 12 | 13 | import java.io.IOException; 14 | import java.lang.annotation.Annotation; 15 | import java.util.Set; 16 | 17 | /** 18 | * @Author: luoy 19 | * @Date: 2019/9/9 13:21. 20 | * 扫描类,只对指定basePackage目录下带指定annotation注解的接口进行扫描 21 | */ 22 | public class ApiClassPathScanningCandidateComponentProvider { 23 | public static final String CLASSPATH_URL_PREFIX = "classpath:"; 24 | 25 | public static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; 26 | 27 | private Class annotationType; 28 | 29 | public ApiClassPathScanningCandidateComponentProvider(Class annotationType) { 30 | this.annotationType = annotationType; 31 | } 32 | 33 | public Set findCandidateComponents(String...basePackages) { 34 | Set candidates = Sets.newLinkedHashSet(); 35 | for(String basePackage : basePackages) { 36 | String convertPath = basePackage.replace(".", "/"); 37 | String packageSearchPath = CLASSPATH_URL_PREFIX + convertPath + '/' + DEFAULT_RESOURCE_PATTERN; 38 | try { 39 | Resource[] resources = new PathMatchingResourcePatternResolver().getResources(packageSearchPath); 40 | 41 | for (Resource resource : resources) { 42 | if (resource.isReadable()) { 43 | MetadataReader metadataReader = new CachingMetadataReaderFactory().getMetadataReader(resource); 44 | if (isCandidateComponent(metadataReader)) { 45 | ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 46 | sbd.setResource(resource); 47 | sbd.setSource(resource); 48 | if (isCandidateComponent(metadataReader)) { 49 | candidates.add(sbd); 50 | } 51 | } 52 | } 53 | } 54 | } catch (IOException e) { 55 | throw new BizException("I/O failure during classpath scanning", e); 56 | } 57 | } 58 | return candidates; 59 | } 60 | 61 | private boolean isCandidateComponent(MetadataReader metadataReader){ 62 | ClassMetadata metadata = metadataReader.getClassMetadata(); 63 | if (!metadata.isInterface()) { 64 | return false; 65 | } 66 | try { 67 | Object o = Class.forName(metadata.getClassName()).getAnnotation(annotationType); 68 | if(o != null) { 69 | return true; 70 | } 71 | return false; 72 | 73 | } catch (ClassNotFoundException e) { 74 | e.printStackTrace(); 75 | } 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/DbConfig.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import com.ly.core.db.BaseDbServer; 4 | import com.ly.core.db.MysqlServer; 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.boot.jdbc.DataSourceBuilder; 8 | import org.springframework.context.annotation.Bean; 9 | 10 | import javax.sql.DataSource; 11 | 12 | /** 13 | * @Author: luoy 14 | * @Date: 2019/8/30 9:58. 15 | */ 16 | //@Configuration 17 | //懒加载,当第一次使用这个bean的时候才加载进容器,目的为2点, 18 | // 1:减少IOC容器启动时间 19 | // 2:当不需要操作mysql时候可不配mysql参数 20 | // 3: 可直接用JdbcTemplate 21 | //@Lazy 22 | public class DbConfig { 23 | //给数据源注入springboot容器,并且指定bean名,方便注入 24 | @Bean("qa-dataSource") 25 | @ConfigurationProperties(prefix = "spring.datasource.qa") 26 | public DataSource createAtDataSource() { 27 | return DataSourceBuilder.create().build(); 28 | } 29 | 30 | @Bean("uat-dataSource") 31 | @ConfigurationProperties(prefix = "spring.datasource.uat") 32 | public DataSource createAsDataSource() { 33 | return DataSourceBuilder.create().build(); 34 | } 35 | 36 | @Bean("qa") 37 | public BaseDbServer createCarrier(@Qualifier("qa-dataSource")DataSource dataSource) { 38 | return new MysqlServer(dataSource); 39 | } 40 | 41 | @Bean("uat") 42 | public BaseDbServer createUat(@Qualifier("uat-dataSource")DataSource dataSource) { 43 | return new MysqlServer(dataSource); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/DefaultConstantConfig.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import com.ly.plugin.PluginSupport; 4 | 5 | /** 6 | * 一些系统用到的默认配置 7 | * @Author: luoy 8 | * @Date: 2020/9/28 10:45. 9 | */ 10 | public class DefaultConstantConfig { 11 | /** testFail teardown注解中调用方法所在类 */ 12 | public static final Class TEST_ANNOTATION_HOOK_METHOD_BY_CLASS = PluginSupport.class; 13 | 14 | /** plugin节点调用方法所在类 */ 15 | public static final Class PLUGIN_METHOD_BY_CLASS = PluginSupport.class; 16 | 17 | /** setUp 节点调用方法所在 */ 18 | public static final Class SETUP_METHOD_BY_CLASS = PluginSupport.class; 19 | 20 | /** TEARDOWN 节点调用方法所在 */ 21 | public static final Class TEARDOWN_METHOD_BY_CLASS = PluginSupport.class; 22 | 23 | /** onFailure 节点调用方法所在 */ 24 | public static final Class ONFAILURE_METHOD_BY_CLASS = PluginSupport.class; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/RedisClusterConfig.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.core.env.MapPropertySource; 7 | import org.springframework.data.redis.connection.RedisClusterConfiguration; 8 | import org.springframework.data.redis.connection.RedisConnectionFactory; 9 | import org.springframework.data.redis.connection.RedisPassword; 10 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import redis.clients.jedis.JedisPoolConfig; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | 18 | /** 19 | * redis cluster config 20 | */ 21 | // 因为配置文件不对,注释掉Configuration, 避免启动失败 22 | //@Configuration 23 | //@Lazy 24 | public class RedisClusterConfig { 25 | @Value("${spring.redis.cluster.nodes}") 26 | private String clusterNodes; 27 | 28 | @Value("${spring.redis.cluster.timeout}") 29 | private Long timeout; 30 | 31 | @Value("${spring.redis.cluster.password}") 32 | private String auth; 33 | 34 | @Bean(name = "redisClusterConfiguration") 35 | public RedisClusterConfiguration getClusterConfiguration() { 36 | Map config = new HashMap<>(); 37 | config.put("spring.redis.cluster.nodes", clusterNodes.trim()); 38 | config.put("spring.redis.cluster.timeout", timeout); 39 | // config.put("spring.redis.cluster.password", auth); 40 | RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(new MapPropertySource("RedisClusterConfiguration", config)); 41 | redisClusterConfiguration.setPassword(RedisPassword.of(auth)); 42 | return redisClusterConfiguration; 43 | } 44 | 45 | @Bean(name = "redisClusterPoolConfig") 46 | public JedisPoolConfig getJedisPoolConfig() { 47 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 48 | return jedisPoolConfig; 49 | } 50 | 51 | @Bean(name = "redisClusterConnectionFactory") 52 | public RedisConnectionFactory connectionFactory(@Qualifier("redisClusterConfiguration") RedisClusterConfiguration redisClusterConfig, 53 | @Qualifier("redisClusterPoolConfig") JedisPoolConfig pooConfig) { 54 | 55 | JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisClusterConfig, pooConfig); 56 | return jedisConnectionFactory; 57 | } 58 | 59 | /** 60 | * redis模板 61 | * 62 | * @param factory 63 | * @param 64 | * @return 65 | */ 66 | @Bean(name = "redisClusterTemplate") 67 | public RedisTemplate redisTemplate(@Qualifier("redisClusterConnectionFactory") RedisConnectionFactory factory) { 68 | RedisTemplate redisTemplate = new RedisTemplate<>(); 69 | redisTemplate.setConnectionFactory(factory); 70 | RedisStandaloneConfig.initSerializerTemplate(redisTemplate, factory); 71 | return redisTemplate; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/config/RedisStandaloneConfig.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.data.redis.connection.RedisConnectionFactory; 10 | import org.springframework.data.redis.connection.RedisPassword; 11 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 12 | import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; 13 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 16 | import org.springframework.data.redis.serializer.RedisSerializer; 17 | import org.springframework.data.redis.serializer.StringRedisSerializer; 18 | 19 | import java.time.Duration; 20 | 21 | // 因为配置文件不对,注释掉Configuration, 避免启动失败 22 | //@Configuration 23 | //@Lazy 24 | public class RedisStandaloneConfig { 25 | @Value("${spring.redis.standalone.host}") 26 | private String host; 27 | 28 | @Value("${spring.redis.standalone.port}") 29 | private int port; 30 | 31 | @Value("${spring.redis.standalone.password}") 32 | private String password; 33 | 34 | @Value("${spring.redis.standalone.timeout}") 35 | private int timeout; 36 | 37 | @Value("${spring.redis.standalone.database}") 38 | private int database; 39 | 40 | @Bean(name = "redisStandaloneTemplate") 41 | public RedisTemplate redisTemplate(@Qualifier("redisStandaloneConnectionFactory") RedisConnectionFactory factory) { 42 | RedisTemplate redisTemplate = new RedisTemplate<>(); 43 | redisTemplate.setConnectionFactory(factory); 44 | RedisStandaloneConfig.initSerializerTemplate(redisTemplate, factory); 45 | return redisTemplate; 46 | } 47 | 48 | @Bean(name = "redisStandaloneConnectionFactory") 49 | public RedisConnectionFactory connectionFactory() { 50 | RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); 51 | configuration.setHostName(host); 52 | configuration.setPassword(RedisPassword.of(password)); 53 | configuration.setDatabase(database); 54 | configuration.setPort(port); 55 | JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); 56 | jedisClientConfigurationBuilder.connectTimeout(Duration.ofMillis(timeout)); 57 | return new JedisConnectionFactory(configuration, jedisClientConfigurationBuilder.build()); 58 | } 59 | 60 | /** 61 | * redis 序列化 ,存储关键字是字符串 ,值是Jdk序列化 62 | * @param redisTemplate 63 | * @param factory 64 | */ 65 | public static void initSerializerTemplate(RedisTemplate redisTemplate, RedisConnectionFactory factory) { 66 | RedisSerializer redisSerializer = new StringRedisSerializer(); 67 | 68 | ObjectMapper objectMapper = new ObjectMapper(); 69 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 70 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); 71 | objectMapper.registerModule(new Jdk8Module()); 72 | GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper); 73 | 74 | redisTemplate.setKeySerializer(redisSerializer); 75 | redisTemplate.setHashKeySerializer(redisSerializer); 76 | redisTemplate.setValueSerializer(redisSerializer); 77 | redisTemplate.setHashValueSerializer(redisSerializer); 78 | // redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 79 | // redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 80 | redisTemplate.setEnableTransactionSupport(true); 81 | redisTemplate.setConnectionFactory(factory); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/db/BaseDbServer.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.db; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * 数据库操接口 8 | * @author luoy 9 | * 10 | */ 11 | public interface BaseDbServer { 12 | 13 | /** 14 | * PreparedStatement 改 15 | * @param sql 16 | * @param params 17 | * @return 18 | */ 19 | int update(String sql, String... params); 20 | 21 | /** 22 | * PreparedStatement 增 23 | * @param sql 24 | * @param params 25 | * @return 26 | */ 27 | int insert(String sql, String... params); 28 | 29 | /** 30 | * PreparedStatement 删 31 | * @param sql 32 | * @param params 33 | * @return 34 | */ 35 | int delete(String sql, String... params); 36 | 37 | /** 38 | * PreparedStatement 查单行 39 | * @param sql 40 | * @param params 41 | * @return 42 | */ 43 | Map select(String sql, String... params); 44 | 45 | /** 46 | * PreparedStatement 多行数据中的第row行 row从0开始 47 | * @param sql 48 | * @param params 49 | * @return 50 | */ 51 | Map select(String sql, int row, String... params); 52 | 53 | /** 54 | * PreparedStatement 查所有数据 55 | * @param sql 56 | * @param params 57 | * @return 58 | */ 59 | List> selects(String sql, String... params); 60 | 61 | /** 62 | * PreparedStatement 返回查询到的结果集行数量 63 | * @param sql 64 | * @return 65 | */ 66 | int selectRow(String sql, String... params); 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/enums/HttpType.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.enums; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2019/12/4 15:49. 6 | */ 7 | public enum HttpType { 8 | 9 | GET("get"), 10 | POST("post"), 11 | PUT("put"), 12 | DELETE("delete"); 13 | 14 | private String type; 15 | 16 | HttpType(String type) { 17 | this.type = type; 18 | } 19 | 20 | public String getType() { 21 | return type; 22 | } 23 | 24 | public static HttpType get(String httpType) { 25 | switch (httpType) { 26 | case "get": 27 | return GET; 28 | case "post": 29 | return POST; 30 | case "put": 31 | return PUT; 32 | case "delete": 33 | return DELETE; 34 | default: 35 | throw new IllegalArgumentException("No enum constant " + httpType); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/enums/MatchesEnum.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.enums; 2 | 3 | /** 4 | * @Author: luoy 5 | */ 6 | public enum MatchesEnum { 7 | 8 | /**相等*/ 9 | EQ("eq"), 10 | /**为空*/ 11 | NULL("isNull"), 12 | /**不为空*/ 13 | NOTNULL("notNull"), 14 | /**调用PluginSuppot类方法支持**/ 15 | PLUGIN("plugin"), 16 | /**大于*/ 17 | GT("gt"), 18 | /**小于*/ 19 | LT("lt"), 20 | /**包含*/ 21 | CONTAINS("contains"), 22 | /** map key匹配*/ 23 | HASKEY("hasKey"), 24 | /** map value匹配*/ 25 | HASVAULE("hasValve"), 26 | LEN("len"); 27 | 28 | 29 | private String type; 30 | 31 | MatchesEnum(String type) { 32 | this.type = type; 33 | } 34 | 35 | public String getType() { 36 | return type; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/enums/ModelType.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.enums; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2019/12/4 15:49. 8 | */ 9 | public enum ModelType { 10 | JSON("json"), 11 | XML("xml"), 12 | TEXT("text"), 13 | FORM("form"); 14 | 15 | private String type; 16 | 17 | ModelType(String type) { 18 | this.type = type; 19 | } 20 | 21 | public String getType() { 22 | return type; 23 | } 24 | 25 | public static ModelType get(String type) { 26 | if (StringUtils.isBlank(type)) { 27 | return null; 28 | } 29 | for (ModelType modelType : ModelType.values()) { 30 | if (modelType.getType().equals(type)) { 31 | return modelType; 32 | } 33 | } 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/exception/AssertionException.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.exception; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2019/8/1 17:15. 6 | */ 7 | public class AssertionException extends RuntimeException { 8 | 9 | public AssertionException() { 10 | super(); 11 | } 12 | 13 | public AssertionException(String message) { 14 | super(message); 15 | } 16 | 17 | public AssertionException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | 21 | public AssertionException(Throwable cause) { 22 | super(cause); 23 | } 24 | 25 | protected AssertionException(String message, Throwable cause, 26 | boolean enableSuppression, 27 | boolean writableStackTrace) { 28 | super(message, cause, enableSuppression, writableStackTrace); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/exception/BizException.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.exception; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2019/8/1 17:15. 6 | */ 7 | public class BizException extends RuntimeException { 8 | 9 | public BizException() { 10 | super(); 11 | } 12 | 13 | public BizException(String message) { 14 | super(message); 15 | } 16 | 17 | public BizException(String message, Object...args) { 18 | super(String.format(message, args)); 19 | } 20 | 21 | public BizException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public BizException(Throwable cause) { 26 | super(cause); 27 | } 28 | 29 | protected BizException(String message, Throwable cause, 30 | boolean enableSuppression, 31 | boolean writableStackTrace) { 32 | super(message, cause, enableSuppression, writableStackTrace); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/listener/HeadersFilterAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.listener; 2 | 3 | import io.restassured.filter.Filter; 4 | import io.restassured.filter.FilterContext; 5 | import io.restassured.response.Response; 6 | import io.restassured.specification.FilterableRequestSpecification; 7 | import io.restassured.specification.FilterableResponseSpecification; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * 一个设置http请求头部的适配器 15 | * @Author: luoy 16 | * @Date: 2019/8/16 17:11. 17 | */ 18 | @Slf4j 19 | public abstract class HeadersFilterAdapter implements Filter { 20 | @Override 21 | public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) { 22 | Map defHeaders = defHeaders(); 23 | 24 | defHeaders.forEach((k ,v) -> { 25 | if(StringUtils.isBlank(requestSpec.getHeaders().getValue(k.toLowerCase())) && !StringUtils.isBlank(v)) { 26 | requestSpec.header(k.toLowerCase(), v); 27 | } 28 | }); 29 | return ctx.next(requestSpec, responseSpec); 30 | } 31 | 32 | public abstract Map defHeaders(); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/listener/LifeCycleListenerProcessorUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.listener; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.ly.core.annotation.TestSetup; 5 | import com.ly.core.notification.NotificationContextHandler; 6 | import com.ly.core.notification.NotificationHandler; 7 | import com.ly.core.utils.ResourcesUtil; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.testng.ITestContext; 10 | import org.testng.ITestNGMethod; 11 | import org.testng.ITestResult; 12 | 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | /** 18 | * LifeCycleListener通用处理器 19 | * 20 | * @Author: luoy 21 | * @Date: 2019/9/5 15:38. 22 | */ 23 | @Slf4j 24 | public class LifeCycleListenerProcessorUtil { 25 | 26 | private static volatile LifeCycleListenerProcessorUtil INSTANCE; 27 | 28 | private LifeCycleListenerProcessorUtil() { 29 | } 30 | 31 | public static LifeCycleListenerProcessorUtil create() { 32 | if (INSTANCE == null) { 33 | synchronized (LifeCycleListenerProcessorUtil.class) { 34 | if (INSTANCE == null) { 35 | INSTANCE = new LifeCycleListenerProcessorUtil(); 36 | } 37 | } 38 | } 39 | return INSTANCE; 40 | } 41 | 42 | /** 43 | * 通知处理器 44 | */ 45 | public LifeCycleListenerProcessorUtil notificationOnFinish() { 46 | String channel = ResourcesUtil.getProp("notification.channel"); 47 | 48 | NotificationContextHandler handlerContext = new NotificationContextHandler(); 49 | 50 | if (NotificationContextHandler.Channel.No.getType().equals(channel)) { 51 | log.info("不进行通知"); 52 | } else if (NotificationContextHandler.Channel.ALL.getType().equals(channel)) { 53 | log.info("全渠道通知"); 54 | handlerContext.getInstanceAll().forEach(NotificationHandler::notification); 55 | } else { 56 | log.info("单渠道通知:{}", channel); 57 | handlerContext.getInstance(channel).notification(); 58 | } 59 | return this; 60 | } 61 | 62 | public LifeCycleListenerProcessorUtil testSetup(ITestContext context) { 63 | ITestNGMethod[] allTestMethods = context.getAllTestMethods(); 64 | Set clazzSet = new HashSet<>(); 65 | for (ITestNGMethod method : allTestMethods) { 66 | clazzSet.add(method.getRealClass()); 67 | } 68 | 69 | clazzSet.forEach(clazz -> { 70 | TestSetup testSetup = (TestSetup) clazz.getAnnotation(TestSetup.class); 71 | if (testSetup != null) { 72 | // hookProcessor(testSetup.value()); 73 | } 74 | }); 75 | 76 | return this; 77 | } 78 | 79 | 80 | /** 81 | * 打印错误日志 82 | * @param context 83 | * @return 84 | */ 85 | public LifeCycleListenerProcessorUtil printLog(ITestContext context) { 86 | context.getFailedTests().getAllResults().stream().forEach(e -> log.error("Failed method:" + e.getName(), e.getThrowable())); 87 | 88 | context.getFailedConfigurations().getAllResults().stream().forEach(e -> log.error("FailedConfigurations method:" + e.getName(), e.getThrowable())); 89 | 90 | context.getSkippedConfigurations().getAllResults().stream().forEach(e -> log.error("SkippedConfigurations method:" + e.getName(), e.getThrowable())); 91 | 92 | context.getSkippedTests().getAllResults().stream().forEach(e -> log.error("Skipped method:" + e.getName(), e.getThrowable())); 93 | return this; 94 | } 95 | 96 | /** 97 | * 过滤掉因为继承AbstractTestNGSpringContextTests类而执行的Configuration方法日志打印 98 | * @param tr 99 | * @return 100 | */ 101 | public LifeCycleListenerProcessorUtil skipConfigurationStartPrintLog(ITestResult tr) { 102 | List skipNames = Lists.newArrayList("springTestContextPrepareTestInstance", "springTestContextBeforeTestClass", "springTestContextBeforeTestMethod" 103 | , "springTestContextAfterTestMethod", "springTestContextAfterTestClass"); 104 | if (!skipNames.contains(tr.getName())) { 105 | log.info("=====================onConfigurationStart: {}=====================", tr.getName()); 106 | } 107 | return this; 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/listener/RetryAnalyzer.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.listener; 2 | 3 | import com.ly.core.annotation.RetryCount; 4 | import com.ly.core.utils.ResourcesUtil; 5 | import org.testng.IRetryAnalyzer; 6 | import org.testng.ITestResult; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2019/8/7 16:47. 11 | */ 12 | public class RetryAnalyzer implements IRetryAnalyzer { 13 | private int counter = 0; 14 | /** 15 | * 如果要重新执行失败的测试,此方法实现应返回true,如果不想重新执行测试,则返回false 16 | * @param iTestResult 17 | * @return 18 | */ 19 | @Override 20 | public boolean retry(ITestResult iTestResult) { 21 | int retryLimit = 0; 22 | 23 | RetryCount retryMethodCount = iTestResult.getMethod().getMethod().getAnnotation(RetryCount.class); 24 | //方法上没有@RetryCount注解取类上@RetryCount注解 25 | if (retryMethodCount == null) { 26 | RetryCount typeRetry = iTestResult.getTestClass().getRealClass().getAnnotation(RetryCount.class); 27 | //类上没有@RetryCount注解取配置文件 28 | if (typeRetry == null) { 29 | boolean retryable = ResourcesUtil.getPropBoolean("retry.retryable"); 30 | if(retryable) { 31 | retryLimit = ResourcesUtil.getPropInt("retry.count"); 32 | } 33 | } else { 34 | retryLimit = typeRetry.count(); 35 | } 36 | } else { 37 | retryLimit = retryMethodCount.count(); 38 | } 39 | 40 | if(counter < retryLimit) 41 | { 42 | counter++; 43 | return true; 44 | } 45 | return false; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/listener/TestClassListener.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.listener; 2 | 3 | import com.ly.core.support.PostProcessorHolder; 4 | import com.ly.core.support.TestNgClassPostProcessor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.testng.IClassListener; 7 | import org.testng.ITestClass; 8 | 9 | /** 10 | * testNg Class生命周期 (涉及到一些并发和同步问题,不能和其他生命周期一起使用) 11 | * @Author: luoy 12 | * @Date: 2020/12/7 9:57. 13 | */ 14 | @Slf4j 15 | public class TestClassListener implements IClassListener { 16 | @Override 17 | public void onBeforeClass(ITestClass testClass) { 18 | PostProcessorHolder.getInstance().getPostProcessor(TestNgClassPostProcessor.class) 19 | .forEach(testNgClassPostProcessor -> testNgClassPostProcessor.onTestClassStartBeforePostProcessor(testClass)); 20 | log.info("=====================onBeforeClass:{} =====================", testClass.getName() ); 21 | } 22 | 23 | @Override 24 | public void onAfterClass(ITestClass testClass) { 25 | PostProcessorHolder.getInstance().getPostProcessor(TestNgClassPostProcessor.class) 26 | .forEach(testNgClassPostProcessor -> testNgClassPostProcessor.onTestClassFinishAfterPostProcessor(testClass)); 27 | log.info("=====================onAfterClass:{} =====================", testClass.getName() ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/BaseSaveMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.utils.AssertUtils; 4 | import org.hamcrest.BaseMatcher; 5 | import org.hamcrest.Description; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2020/3/26 16:56. 10 | */ 11 | public abstract class BaseSaveMatcher extends BaseMatcher { 12 | protected final String k; 13 | 14 | public BaseSaveMatcher(String k) { 15 | this.k = k; 16 | } 17 | 18 | @Override 19 | public boolean matches(Object v) { 20 | AssertUtils.notNull(v, "key:" + k + ",保存失败, 传入值为空"); 21 | return save(v); 22 | } 23 | 24 | @Override 25 | public void describeTo(Description description) { 26 | description.appendText("key:" + k + ",保存失败"); 27 | } 28 | 29 | protected abstract boolean save(Object v); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/BaseValidateMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.utils.ParameterizationUtil; 4 | import org.hamcrest.BaseMatcher; 5 | import org.hamcrest.Description; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | */ 13 | public abstract class BaseValidateMatcher extends BaseMatcher { 14 | protected Map> validate; 15 | 16 | protected String errorText = ""; 17 | 18 | public BaseValidateMatcher(Map> validate) { 19 | this.validate = validate; 20 | } 21 | 22 | @Override 23 | public boolean matches(Object operand) { 24 | validateParameterization(); 25 | return validate(operand); 26 | } 27 | 28 | @Override 29 | public void describeTo(Description description) { 30 | description.appendText(errorText); 31 | } 32 | 33 | protected abstract boolean validate(Object v); 34 | 35 | /** 36 | * 断言参数化处理 37 | */ 38 | private void validateParameterization() { 39 | ParameterizationUtil.wildcardMatcherValidate(validate); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/EqValidateMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.enums.MatchesEnum; 4 | import org.hamcrest.BaseMatcher; 5 | import org.hamcrest.Description; 6 | 7 | /** 8 | * @Author: luoy 9 | */ 10 | public class EqValidateMatcher extends BaseMatcher { 11 | private Object actual; 12 | 13 | private Object expected; 14 | 15 | protected String errorText = ""; 16 | 17 | public EqValidateMatcher(Object actual, Object expected) { 18 | this.actual = actual; 19 | this.expected = expected; 20 | } 21 | 22 | @Override 23 | public boolean matches(Object operand) { 24 | boolean isReturn = ValidateUtils.eq(actual, expected); 25 | if (!isReturn) { 26 | this.errorText = ValidateByPathHandlers.prettyErrorMsg(expected, actual, null, MatchesEnum.EQ, "两值不匹配"); 27 | } 28 | return isReturn; 29 | } 30 | 31 | @Override 32 | public void describeTo(Description description) { 33 | description.appendText(errorText); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/JsonPathValidateMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.parse.JsonPath; 4 | import com.ly.core.parse.PathParse; 5 | import com.ly.core.utils.Utils; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @Author: luoy 11 | * @Date: 2020/11/27 17:01. 12 | */ 13 | public class JsonPathValidateMatcher extends PathValidateMatcherAdapter{ 14 | public JsonPathValidateMatcher(Map validate) { 15 | super(validate); 16 | } 17 | 18 | @Override 19 | public PathParse getParseHandler(String operand) { 20 | if (!Utils.isJSONValid(operand)) { 21 | this.errorText = "返回值不为json:" + operand.getClass(); 22 | return null; 23 | } 24 | return JsonPath.create(operand); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/PathValidateMatcherAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.exception.BizException; 4 | import com.ly.core.parse.PathParse; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * path 断言适配器 13 | * @Author: luoy 14 | */ 15 | public abstract class PathValidateMatcherAdapter extends BaseValidateMatcher { 16 | public PathValidateMatcherAdapter(Map> validate) { 17 | super(validate); 18 | } 19 | 20 | @Override 21 | protected boolean validate(Object operand) { 22 | 23 | if (!(operand instanceof String)) { 24 | this.errorText = "返回值类型匹配出错,需要类型: String,实际类型: " + operand.getClass(); 25 | return false; 26 | } 27 | 28 | if (validate == null || validate.size() == 0) { 29 | return true; 30 | } 31 | 32 | PathParse pathParse = getParseHandler(String.valueOf(operand)); 33 | 34 | if (pathParse == null) { 35 | return false; 36 | } 37 | 38 | ValidateByPathHandlers validateByJsonHandlers = new ValidateByPathHandlers(pathParse); 39 | Method[] declaredMethods = validateByJsonHandlers.getClass().getDeclaredMethods(); 40 | boolean isMethodExist = false; 41 | 42 | for(String k: validate.keySet()) { 43 | for(Method method : declaredMethods) { 44 | if (k.equals(method.getName())) { 45 | isMethodExist = true; 46 | for(Object obj : validate.get(k)) { 47 | try { 48 | method.invoke(validateByJsonHandlers, obj); 49 | } catch (IllegalAccessException e) { 50 | e.printStackTrace(); 51 | } catch (InvocationTargetException e) { 52 | if(e.getTargetException() instanceof BizException) { 53 | this.errorText = e.getTargetException().getMessage(); 54 | return false; 55 | } 56 | throw new BizException("%s类, 方法:%s, 异常: %s", validateByJsonHandlers.getClass().getName(), k, e.getTargetException()); 57 | } catch (IllegalArgumentException e) { 58 | e.printStackTrace(); 59 | throw new BizException("yaml validate解析错误,请检查: " + validate.get(k)); 60 | } 61 | } 62 | } 63 | } 64 | if (!isMethodExist) { 65 | throw new BizException("断言方法不存在: " + k); 66 | } 67 | } 68 | return true; 69 | } 70 | 71 | /** 获取path解析器*/ 72 | public abstract PathParse getParseHandler(String operand); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/SaveCache.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/3/26 17:19. 6 | */ 7 | public class SaveCache { 8 | 9 | /** 10 | * 保存值到缓存,作用域为thread 11 | * @param k 12 | * @param 13 | * @return 14 | */ 15 | public static org.hamcrest.Matcher saveSuite(String k) { 16 | return new SaveThreadMatcher<>(k); 17 | } 18 | 19 | /** 20 | * 保存值到缓存,作用域method 21 | * @param k 22 | * @param 23 | * @return 24 | */ 25 | public static org.hamcrest.Matcher saveTest(String k) { 26 | return new SaveMethodMatcher<>(k); 27 | } 28 | 29 | /** 30 | * 保存值到缓存,作用域全局 31 | * @param k 32 | * @param 33 | * @return 34 | */ 35 | public static org.hamcrest.Matcher saveGlobal(String k) { 36 | return new SaveGlobalMatcher<>(k); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/SaveGlobalMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.utils.ContextDataStorage; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2020/3/26 16:56. 8 | */ 9 | public class SaveGlobalMatcher extends BaseSaveMatcher { 10 | public SaveGlobalMatcher(String k) { 11 | super(k); 12 | } 13 | 14 | @Override 15 | public boolean save(Object v) { 16 | ContextDataStorage.getInstance().setAttribute(k, v); 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/SaveMethodMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.utils.ContextDataStorage; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2020/3/26 16:56. 8 | */ 9 | public class SaveMethodMatcher extends BaseSaveMatcher { 10 | 11 | public SaveMethodMatcher(String k) { 12 | super(k); 13 | } 14 | 15 | @Override 16 | public boolean save(Object v) { 17 | ContextDataStorage.getInstance().setMethodAttribute(k, v); 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/SaveThreadMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.utils.ContextDataStorage; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2020/3/26 16:56. 8 | */ 9 | public class SaveThreadMatcher extends BaseSaveMatcher { 10 | public SaveThreadMatcher(String k) { 11 | super(k); 12 | } 13 | 14 | @Override 15 | public boolean save(Object v) { 16 | ContextDataStorage.getInstance().setThreadAttribute(k, v); 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/Validate.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * @Author: luoy 8 | */ 9 | public class Validate { 10 | /** 11 | * yaml jsonPath 断言 12 | * @param validate 13 | * @param 14 | * @return 15 | */ 16 | public static org.hamcrest.Matcher validateJson(Map> validate) { 17 | return new JsonPathValidateMatcher(validate); 18 | } 19 | 20 | /** 21 | * yaml xpath断言 22 | * @param validate 23 | * @param 24 | * @return 25 | */ 26 | public static org.hamcrest.Matcher validateXpath(Map> validate) { 27 | return new XPathValidateMatcher(validate); 28 | } 29 | 30 | public static org.hamcrest.Matcher validateEq(Object actual, Object expected) { 31 | return new EqValidateMatcher<>(actual, expected); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/ValidateUtils.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.config.DefaultConstantConfig; 4 | import com.ly.core.utils.ReflectUtil; 5 | 6 | import java.lang.reflect.Array; 7 | import java.math.BigDecimal; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * @Author: luoy 13 | * @Date: 2020/4/21 13:09. 14 | */ 15 | public class ValidateUtils { 16 | public final static String METHOD_NAME_KEY = "method"; 17 | public final static String METHOD_ARGS_KEY = "args"; 18 | 19 | public static boolean eq(Object actual, Object expected) { 20 | if (actual == null) { 21 | return expected == null; 22 | } 23 | 24 | if (expected != null && isArray(actual)) { 25 | return isArray(expected) && areArraysEqual(actual, expected); 26 | } 27 | 28 | return actual.equals(expected); 29 | } 30 | 31 | public static boolean isNull(Object operand) { 32 | if (operand == null) { 33 | return true; 34 | } 35 | 36 | if (operand instanceof Map) { 37 | return ((Map) operand).size() == 0; 38 | } 39 | 40 | if (operand instanceof List) { 41 | return ((List) operand).size() == 0; 42 | } 43 | 44 | if (operand instanceof String) { 45 | return operand == ""; 46 | } 47 | return false; 48 | } 49 | 50 | public static boolean notNull(Object operand) { 51 | return !isNull(operand); 52 | } 53 | 54 | public static boolean plugin(Map operand) { 55 | Object invoke = null; 56 | if (operand.get(METHOD_ARGS_KEY) instanceof String) { 57 | invoke = ReflectUtil.reflectInvoke(DefaultConstantConfig.PLUGIN_METHOD_BY_CLASS, (String) operand.get(METHOD_NAME_KEY), (String) operand.get(METHOD_ARGS_KEY)); 58 | } else if (operand.get(METHOD_ARGS_KEY) instanceof Object[]) { 59 | Object[] args = (Object[]) operand.get(METHOD_ARGS_KEY); 60 | invoke = ReflectUtil.reflectInvoke(DefaultConstantConfig.PLUGIN_METHOD_BY_CLASS, (String) operand.get(METHOD_NAME_KEY), args); 61 | } else { 62 | invoke = ReflectUtil.reflectInvoke(DefaultConstantConfig.PLUGIN_METHOD_BY_CLASS, (String) operand.get(METHOD_NAME_KEY), ""); 63 | } 64 | if (invoke != null && invoke instanceof Boolean) { 65 | return (boolean) invoke; 66 | } 67 | return true; 68 | } 69 | 70 | public static boolean plugin(String method, Object...args) { 71 | Object invoke = ReflectUtil.reflectInvoke(DefaultConstantConfig.PLUGIN_METHOD_BY_CLASS, method, args); 72 | if (invoke != null && invoke instanceof Boolean) { 73 | return (boolean) invoke; 74 | } 75 | return true; 76 | } 77 | 78 | public static boolean gt(BigDecimal actual, BigDecimal expected) { 79 | return actual.doubleValue() > expected.doubleValue(); 80 | } 81 | 82 | public static boolean lt(BigDecimal actual, BigDecimal expected) { 83 | return actual.doubleValue() < expected.doubleValue(); 84 | } 85 | 86 | public static boolean contains(List operand, Object expected) { 87 | return operand.contains(expected); 88 | } 89 | 90 | public static boolean hasKey(Map operand, Object expected) { 91 | return operand.containsKey(expected); 92 | } 93 | 94 | public static boolean hasValue(Map operand, Object expected) { 95 | return operand.containsValue(expected); 96 | } 97 | 98 | public static boolean len(Object operand, int expected) { 99 | if ( operand instanceof Map) { 100 | return ((Map) operand).size() == expected; 101 | } 102 | if (operand instanceof List ) { 103 | return ((List) operand).size() == expected; 104 | } 105 | 106 | return String.valueOf(operand).length() == expected; 107 | } 108 | 109 | private static boolean areArraysEqual(Object actualArray, Object expectedArray) { 110 | return areArrayLengthsEqual(actualArray, expectedArray) && areArrayElementsEqual(actualArray, expectedArray); 111 | } 112 | 113 | private static boolean areArrayLengthsEqual(Object actualArray, Object expectedArray) { 114 | return Array.getLength(actualArray) == Array.getLength(expectedArray); 115 | } 116 | 117 | private static boolean areArrayElementsEqual(Object actualArray, Object expectedArray) { 118 | for (int i = 0; i < Array.getLength(actualArray); i++) { 119 | if (!eq(Array.get(actualArray, i), Array.get(expectedArray, i))) { 120 | return false; 121 | } 122 | } 123 | return true; 124 | } 125 | 126 | private static boolean isArray(Object o) { 127 | return o.getClass().isArray(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/matches/XPathValidateMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.matches; 2 | 3 | import com.ly.core.exception.BizException; 4 | import com.ly.core.parse.PathParse; 5 | import com.ly.core.parse.Xpath; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @Author: luoy 11 | * @Date: 2020/11/27 17:01. 12 | */ 13 | public class XPathValidateMatcher extends PathValidateMatcherAdapter{ 14 | public XPathValidateMatcher(Map validate) { 15 | super(validate); 16 | } 17 | 18 | @Override 19 | public PathParse getParseHandler(String operand) { 20 | try { 21 | Xpath xpath = Xpath.of(operand); 22 | return xpath; 23 | } catch (BizException e) { 24 | e.printStackTrace(); 25 | this.errorText = "返回值xml解析错误: " + operand; 26 | } 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/BaseNotificationServer.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | /** 4 | * @Author: luoyoujun 5 | * @Date: 2019/9/16 13:25. 6 | */ 7 | public interface BaseNotificationServer { 8 | 9 | void notification(NotificationRequest request) throws Exception; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/DingTalkNotificationHandler.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | 6 | /** 7 | * @Author: luoyoujun 8 | * @Date: 2019/9/16 15:04. 9 | */ 10 | @Service 11 | public class DingTalkNotificationHandler extends NotificationHandler{ 12 | @Autowired 13 | private DingTalkNotificationSererImpl dingTalkNotificationSererImpl; 14 | 15 | @Override 16 | public void notification() { 17 | DingTalkNotificationRequest dingTalkRequest = DingTalkNotificationRequest.builder() 18 | .msgtype(DingTalkNotificationSererImpl.MsgType.text) 19 | .text(DingTalkNotificationRequest.Text.builder().content("测试用例运行完毕,请检查!").build()) 20 | .build(); 21 | dingTalkNotificationSererImpl.notification(dingTalkRequest); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/DingTalkNotificationRequest.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Author: luoyoujun 12 | * @Date: 2019/9/12 15:57. 13 | */ 14 | @Data 15 | @Builder 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class DingTalkNotificationRequest extends NotificationRequest{ 19 | 20 | private DingTalkNotificationSererImpl.MsgType msgtype; 21 | 22 | private ActionCard actionCard; 23 | 24 | private At at; 25 | 26 | private FeedCard feedCard; 27 | 28 | private Link link; 29 | 30 | private Markdown markdown; 31 | 32 | private Text text; 33 | 34 | @Data 35 | @AllArgsConstructor 36 | @NoArgsConstructor 37 | @Builder 38 | public static class At { 39 | private List atMobiles; 40 | 41 | Boolean isAtAll; 42 | } 43 | 44 | @Data 45 | @AllArgsConstructor 46 | @NoArgsConstructor 47 | @Builder 48 | public static class Text { 49 | private String content; 50 | } 51 | 52 | @Data 53 | @AllArgsConstructor 54 | @NoArgsConstructor 55 | @Builder 56 | public static class Markdown { 57 | private String text; 58 | 59 | private String title; 60 | } 61 | 62 | @Data 63 | @AllArgsConstructor 64 | @NoArgsConstructor 65 | @Builder 66 | public static class Link { 67 | 68 | /** 69 | * 点击消息跳转的URL 70 | */ 71 | private String messageUrl; 72 | 73 | /** 74 | * 图片URL 75 | */ 76 | private String picUrl; 77 | 78 | /** 79 | * 消息内容。如果太长只会部分展示 80 | */ 81 | private String text; 82 | 83 | /** 84 | * 标题 85 | */ 86 | private String title; 87 | } 88 | 89 | @Data 90 | @Builder 91 | public static class FeedCard { 92 | } 93 | 94 | @Data 95 | @Builder 96 | public static class ActionCard { 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/DingTalkNotificationSererImpl.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import com.ly.core.restassured.RestassuredHttpHandleBuilder; 4 | import com.ly.core.utils.JSONSerializerUtil; 5 | import com.ly.headersfilter.RestAssuredLogFilter; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * @Author: luoyoujun 12 | * @Date: 2019/9/12 14:35. 13 | */ 14 | @Service 15 | @Slf4j 16 | public class DingTalkNotificationSererImpl implements BaseNotificationServer { 17 | @Value("${notification.dingtalk.url}") 18 | private String url; 19 | 20 | @Override 21 | public void notification(NotificationRequest request) { 22 | String requestJson = JSONSerializerUtil.serialize(request); 23 | log.info("钉钉通知============="); 24 | RestassuredHttpHandleBuilder.create().post(requestJson, url, new RestAssuredLogFilter()); 25 | } 26 | 27 | public enum MsgType { 28 | text,link,markdown,@Deprecated actionCard,@Deprecated feedCard 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/MailNotificationHandler.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * @Author: luoyoujun 9 | * @Date: 2019/9/16 15:00. 10 | */ 11 | @Service 12 | @Slf4j 13 | public class MailNotificationHandler extends NotificationHandler { 14 | @Autowired 15 | private MailNotificationServerImpl mailNotificationServer; 16 | 17 | @Override 18 | public void notification() { 19 | MailNotificationRequest mailNotificationRequest = MailNotificationRequest.builder() 20 | .subject("测试用例运行完毕") 21 | .context("测试用例运行完毕,请检查!") 22 | .build(); 23 | try { 24 | mailNotificationServer.notification(mailNotificationRequest); 25 | } catch (Exception e) { 26 | log.error("邮件发送失败", e); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/MailNotificationRequest.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @Author: luoyoujun 10 | * @Date: 2019/9/16 9:36. 11 | */ 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class MailNotificationRequest extends NotificationRequest{ 17 | /** 18 | * 主题 19 | */ 20 | private String subject; 21 | 22 | /** 23 | * 正文 24 | */ 25 | private String context; 26 | 27 | /** 28 | * 发送时间,为空即为立即发送 yyyy-MM-dd HH:mm:ss 29 | */ 30 | private String date; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/MailNotificationServerImpl.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import com.ly.core.exception.BizException; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.mail.MessagingException; 10 | import javax.mail.Session; 11 | import javax.mail.Transport; 12 | import javax.mail.internet.InternetAddress; 13 | import javax.mail.internet.MimeMessage; 14 | import java.text.ParseException; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Date; 17 | import java.util.Properties; 18 | 19 | /** 20 | * @Author: luoyoujun 21 | * @Date: 2019/9/16 9:42. 22 | */ 23 | @Service 24 | @Slf4j 25 | public class MailNotificationServerImpl implements BaseNotificationServer { 26 | @Value("${notification.mail.sendAddress}") 27 | private String sendAddress; 28 | 29 | @Value("${notification.mail.sendAccount}") 30 | private String sendAccount; 31 | 32 | @Value("${notification.mail.sendPassword}") 33 | private String sendPassword; 34 | 35 | @Value("${notification.mail.recipientAddress}") 36 | private String recipientAddress; 37 | 38 | @Value("${notification.mail.protocol}") 39 | private String protocol; 40 | 41 | @Value("${notification.mail.host}") 42 | private String host; 43 | 44 | @Override 45 | public void notification(NotificationRequest request) throws ParseException, MessagingException { 46 | if (request instanceof MailNotificationRequest) { 47 | Properties properties = new Properties(); 48 | //设置用户的认证方式 49 | properties.setProperty("mail.smtp.auth", "true"); 50 | //设置传输协议 51 | properties.setProperty("mail.transport.protocol", protocol); 52 | //设置发件人的SMTP服务器地址 53 | properties.setProperty("mail.smtp.host", host); 54 | //2、创建定义整个应用程序所需的环境信息的 Session 对象 55 | Session session = Session.getInstance(properties); 56 | 57 | //创建一封邮件的实例对象 58 | MimeMessage msg = new MimeMessage(session); 59 | //设置发件人地址 60 | msg.setFrom(new InternetAddress(sendAddress)); 61 | /** 62 | * 设置收件人地址(可以增加多个收件人、抄送、密送),即下面这一行代码书写多行 63 | * MimeMessage.RecipientType.TO:发送 64 | * MimeMessage.RecipientType.CC:抄送 65 | * MimeMessage.RecipientType.BCC:密送 66 | */ 67 | String[] recipients = recipientAddress.split(","); 68 | InternetAddress[] internetAddress = new InternetAddress[recipients.length]; 69 | for (int i = 0; i < recipients.length; i++) { 70 | internetAddress[i] = new InternetAddress(recipients[i]); 71 | } 72 | msg.setRecipients(MimeMessage.RecipientType.TO, internetAddress); 73 | //设置邮件主题 74 | 75 | msg.setSubject(((MailNotificationRequest) request).getSubject(),"UTF-8"); 76 | //设置邮件正文 77 | msg.setContent(((MailNotificationRequest) request).getContext(), "text/html;charset=UTF-8"); 78 | //设置邮件的发送时间,默认立即发送 79 | msg.setSentDate(StringUtils.isBlank(((MailNotificationRequest) request).getDate()) ? new Date() : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(((MailNotificationRequest) request).getDate())); 80 | 81 | //根据session对象获取邮件传输对象Transport 82 | Transport transport = session.getTransport(); 83 | //设置发件人的账户名和密码 84 | transport.connect(sendAccount, sendPassword); 85 | //发送邮件,并发送到所有收件人地址,message.getAllRecipients() 获取到的是在创建邮件对象时添加的所有收件人, 抄送人, 密送人 86 | transport.sendMessage(msg, msg.getAllRecipients()); 87 | log.info("邮件发送成功,to:{}", recipientAddress); 88 | 89 | //关闭邮件连接 90 | transport.close(); 91 | } else { 92 | throw new BizException("Mail请求参数错误"); 93 | } 94 | 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/NotificationContextHandler.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.ly.core.utils.SpringContextUtil; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class NotificationContextHandler { 11 | private static Map handlerMap = new HashMap<>(); 12 | 13 | static{ 14 | handlerMap.put(Channel.DingTalk.getType(), DingTalkNotificationHandler.class); 15 | handlerMap.put(Channel.Mail.getType(), MailNotificationHandler.class); 16 | } 17 | 18 | public NotificationHandler getInstance(String type) { 19 | Class clazz = handlerMap.get(type); 20 | 21 | if (clazz == null) { 22 | throw new IllegalArgumentException("not found handler for type:" + type); 23 | } 24 | 25 | return SpringContextUtil.getBean((Class) clazz); 26 | } 27 | 28 | public List getInstanceAll() { 29 | List handlers = Lists.newArrayList(); 30 | 31 | handlerMap.forEach((k,v) -> handlers.add(SpringContextUtil.getBean((Class) v))); 32 | 33 | return handlers; 34 | } 35 | 36 | public enum Channel { 37 | No("0"),DingTalk("1"), Mail("2"),ALL("9"); 38 | 39 | private String type; 40 | 41 | Channel(String type) { 42 | this.type = type; 43 | } 44 | 45 | public String getType() { 46 | return this.type; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/NotificationHandler.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | /** 4 | * @Author: luoyoujun 5 | * @Date: 2019/9/16 14:58. 6 | */ 7 | public abstract class NotificationHandler { 8 | public abstract void notification(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/notification/NotificationRequest.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.notification; 2 | 3 | /** 4 | * @Author: luoyoujun 5 | * @Date: 2019/9/16 13:26. 6 | */ 7 | public class NotificationRequest { 8 | /** 9 | * 内容 10 | */ 11 | private String context; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/BaseModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/3/31 17:38. 13 | */ 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Data 17 | public abstract class BaseModel { 18 | protected Map headers; 19 | protected String name; 20 | protected String description; 21 | protected String url; 22 | protected String method; 23 | protected Map> validate; 24 | protected List setup; 25 | protected List teardown; 26 | protected List onFailure; 27 | protected List> saveGlobal; 28 | protected List> saveMethod; 29 | protected List> saveClass; 30 | protected List> saveThread; 31 | 32 | public final Object getRequests() { 33 | return data(); 34 | } 35 | 36 | protected abstract Object data(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/CsvParse.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import au.com.bytecode.opencsv.CSVReader; 4 | import com.ly.core.utils.Utils; 5 | 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by luoy on 2019/6/17. 14 | */ 15 | public class CsvParse { 16 | public static Object[][] getParseFromCsv(String filePath) { 17 | try { 18 | return getParseFromCsv(filePath, "utf-8"); 19 | } catch (IOException e) { 20 | throw new RuntimeException( 21 | "No parameter values available for method: "+ e.getMessage() + "请检查csv文件是否存在,路径:" 22 | + filePath + "或格式是否正确"); 23 | } 24 | } 25 | 26 | public static Object[][] getParseFromCsv(String filePath, String encode) throws IOException { 27 | try (FileInputStream fis = new FileInputStream(filePath); 28 | InputStreamReader isr = new InputStreamReader(fis, encode); 29 | CSVReader reader = new CSVReader(isr)){ 30 | 31 | List result = new ArrayList<>(); 32 | Object[] nextLine ; 33 | 34 | while ((nextLine = reader.readNext()) != null) { 35 | result.add(nextLine); 36 | } 37 | 38 | if (null != reader) { 39 | reader.close(); 40 | } 41 | Object[][] objects = Utils.listToArray(result); 42 | if ((null == objects) || (objects.length == 0)) { 43 | throw new RuntimeException( 44 | "No parameter values available for method: " + "请检查csv文件是否存在,路径:" + filePath + "或格式是否正确"); 45 | } 46 | return objects; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/DataEntity.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/4/20 9:37. 13 | */ 14 | @Data 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Builder 18 | public class DataEntity { 19 | List testCase; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/FormModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/4/16 17:51. 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper=true) 16 | public class FormModel extends BaseModel { 17 | private Map formData; 18 | 19 | @Builder 20 | public FormModel(Map headers, String name, String description, String url, String method, Map> validate, List setup, List teardown, List onFailure, List> saveGlobal, List> saveMethod, List> saveClass, List> saveThread, Map formData) { 21 | super(headers, name, description, url, method, validate, setup, teardown, onFailure, saveGlobal, saveMethod, saveClass, saveThread); 22 | this.formData = formData; 23 | } 24 | 25 | @Override 26 | protected Object data() { 27 | return formData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/JsonModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/4/16 17:50. 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper=true) 16 | public class JsonModel extends BaseModel{ 17 | private String jsonData; 18 | 19 | @Builder 20 | public JsonModel(Map headers, String name, String description, String url, String method, Map> validate, List setup, List teardown, List onFailure, List> saveGlobal, List> saveMethod, List> saveClass, List> saveThread, String jsonData) { 21 | super(headers, name, description, url, method, validate, setup, teardown, onFailure, saveGlobal, saveMethod, saveClass, saveThread); 22 | this.jsonData = jsonData; 23 | } 24 | 25 | @Override 26 | protected Object data() { 27 | return jsonData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/JsonPath.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONException; 6 | import com.alibaba.fastjson.JSONObject; 7 | import com.alibaba.fastjson.JSONPath; 8 | 9 | /** 10 | * Created by luoy on 2019/7/25. 11 | * 值有小数点的时候,统一会解析为 BigDecimal类型 12 | */ 13 | public class JsonPath implements PathParse { 14 | private JSON json; 15 | 16 | private JsonPath(){} 17 | 18 | private JsonPath(String json) { 19 | try { 20 | this.json = JSONObject.parseObject(json); 21 | }catch (JSONException e) { 22 | this.json = JSONArray.parseArray(json); 23 | } 24 | } 25 | 26 | public static JsonPath create(String json) { 27 | return new JsonPath(json); 28 | } 29 | 30 | @Override 31 | public Object get(String path) { 32 | return JSONPath.eval(json, path); 33 | } 34 | 35 | @Override 36 | public boolean isExist(String key) { 37 | try{ 38 | return JSONPath.contains(json, key); 39 | } catch (Exception e) { 40 | return false; 41 | } 42 | 43 | } 44 | 45 | /** 46 | * 是否包含,path中是否存在对象 47 | * @param path 48 | * @return 49 | */ 50 | public boolean contains(String path) { 51 | boolean exits = isExist(path); 52 | if (!exits) { 53 | return exits; 54 | } 55 | return JSONPath.contains(json, path); 56 | } 57 | 58 | public boolean containsValue(String path, Object value) { 59 | boolean exits = isExist(path); 60 | if (!exits) { 61 | return exits; 62 | } 63 | return JSONPath.containsValue(json, path, value); 64 | } 65 | 66 | @Override 67 | public int size(String path) { 68 | return JSONPath.size(json, path); 69 | } 70 | 71 | public JsonPath set(String path, Object v) { 72 | JSONPath.set(json, path, v); 73 | return this; 74 | } 75 | 76 | public JsonPath arrayAdd(String path, Object...values) { 77 | JSONPath.arrayAdd(json, path, values); 78 | return this; 79 | } 80 | 81 | public String getString(){ 82 | return json.toJSONString(); 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/MapToXml.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import org.dom4j.Document; 4 | import org.dom4j.DocumentHelper; 5 | import org.dom4j.Element; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/11/27 10:50. 13 | */ 14 | public class MapToXml { 15 | public static String toXml(Map map) { 16 | final String statement = ""; 17 | final String defaultRootElement = "xml"; 18 | 19 | Document document = DocumentHelper.createDocument(); 20 | document.addElement(defaultRootElement); 21 | Element element = recursionMapToXml(document.getRootElement(), map); 22 | if (map.size() == 1) { 23 | return statement + element.element(map.keySet().stream().findFirst().get()).asXML(); 24 | } 25 | return statement + element.asXML(); 26 | } 27 | 28 | private static Element recursionMapToXml(Element element, Map map) { 29 | for (String key: map.keySet()) { 30 | Object value = map.get(key); 31 | if (value instanceof Map) { 32 | Element childElement = element.addElement(key); 33 | recursionMapToXml(childElement, (Map)value); 34 | } else if (value instanceof List) { 35 | Element childElement = element.addElement(key); 36 | for (Object l : (List) value) { 37 | if (l instanceof Map) { 38 | recursionMapToXml(childElement, (Map)l); 39 | } 40 | } 41 | } else { 42 | element.addElement(key).addText(value == null ? "" : String.valueOf(value)); 43 | } 44 | } 45 | return element; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/MultipleModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import com.ly.core.utils.AssertUtils; 4 | import lombok.Data; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/4/17 16:56. 11 | */ 12 | @Data 13 | public class MultipleModel { 14 | private Map data; 15 | 16 | public T getModel(String name) { 17 | T v = data.get(name); 18 | AssertUtils.notNull(v, "model不存在: " + name); 19 | return v; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/PathParse.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | /** 4 | * @Author: luoy 5 | * @Date: 2020/11/26 17:03. 6 | */ 7 | public interface PathParse { 8 | /** 9 | * 获取path值 10 | * @param path 11 | * @return 12 | */ 13 | Object get(String path); 14 | 15 | /** 16 | * path是否存在 17 | * @param path 18 | * @return 19 | */ 20 | boolean isExist(String path); 21 | 22 | int size(String path); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/RepresenterNotNull.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import org.yaml.snakeyaml.introspector.Property; 4 | import org.yaml.snakeyaml.nodes.NodeTuple; 5 | import org.yaml.snakeyaml.nodes.Tag; 6 | import org.yaml.snakeyaml.representer.Representer; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/12/25 15:29. 11 | */ 12 | public class RepresenterNotNull extends Representer { 13 | @Override 14 | protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, 15 | Object propertyValue, Tag customTag) { 16 | if( propertyValue != null) { 17 | return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag); 18 | } 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/TestCase.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * @Author: luoy 13 | * @Date: 2020/4/17 14:53. 14 | */ 15 | @Data 16 | @Builder 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class TestCase { 20 | private String name; 21 | private String description; 22 | private String type; 23 | private String url; 24 | private String method; 25 | private List setup; 26 | private List teardown; 27 | private List onFailure; 28 | private List> saveGlobal; 29 | private List> saveMethod; 30 | private List> saveClass; 31 | private List> saveThread; 32 | private Map headers; 33 | private Object requests; 34 | private Map> validate; 35 | private List parameters; 36 | 37 | @Data 38 | @Builder 39 | @NoArgsConstructor 40 | @AllArgsConstructor 41 | public static class Parameters { 42 | private String name; 43 | private String description; 44 | private Map headers; 45 | private Object requests; 46 | private Map> validate; 47 | } 48 | 49 | @Data 50 | @Builder 51 | @NoArgsConstructor 52 | @AllArgsConstructor 53 | public static class HookInfo { 54 | private String method; 55 | private String args; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/TextModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/4/16 17:50. 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper=true) 16 | public class TextModel extends BaseModel { 17 | private String textData; 18 | 19 | @Builder 20 | public TextModel(Map headers, String name, String description, String url, String method, Map> validate, List setup, List teardown, List onFailure, List> saveGlobal, List> saveMethod, List> saveClass, List> saveThread, String textData) { 21 | super(headers, name, description, url, method, validate, setup, teardown, onFailure, saveGlobal, saveMethod, saveClass, saveThread); 22 | this.textData = textData; 23 | } 24 | 25 | @Override 26 | protected Object data() { 27 | return textData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/XmlModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: luoy 12 | * @Date: 2020/4/16 17:51. 13 | */ 14 | @Data 15 | @EqualsAndHashCode(callSuper=true) 16 | public class XmlModel extends BaseModel { 17 | private String xmlData; 18 | 19 | @Builder 20 | public XmlModel(Map headers, String name, String description, String url, String method, Map> validate, List setup, List teardown, List onFailure, List> saveGlobal, List> saveMethod, List> saveClass, List> saveThread, String xmlData) { 21 | super(headers, name, description, url, method, validate, setup, teardown, onFailure, saveGlobal, saveMethod, saveClass, saveThread); 22 | this.xmlData = xmlData; 23 | } 24 | 25 | @Override 26 | protected Object data() { 27 | return xmlData; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/Xpath.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import com.ly.core.exception.BizException; 4 | import org.dom4j.Document; 5 | import org.dom4j.Node; 6 | import org.dom4j.io.SAXReader; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * @Author: luoy 14 | * @Date: 2020/11/26 17:10. 15 | */ 16 | public class Xpath implements PathParse { 17 | 18 | private Document document; 19 | 20 | private Xpath(String xml) { 21 | SAXReader reader = new SAXReader(); 22 | try { 23 | this.document = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8"))); 24 | } catch (Exception e) { 25 | throw new BizException("xml解析失败", e); 26 | } 27 | } 28 | 29 | public static Xpath of(String xml) { 30 | return new Xpath(xml); 31 | } 32 | 33 | @Override 34 | public String get(String path) { 35 | return document.selectSingleNode(path).getText(); 36 | } 37 | 38 | public List getList(String path) { 39 | List selectNodes = document.selectNodes(path); 40 | return selectNodes.stream().map(Node::getText).collect(Collectors.toList()); 41 | } 42 | 43 | @Override 44 | public boolean isExist(String path) { 45 | return document.selectSingleNode(path) != null; 46 | } 47 | 48 | @Override 49 | public int size(String path) { 50 | return getList(path).size(); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/core/parse/YmlParse.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.parse; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.ly.core.exception.BizException; 5 | import org.testng.collections.Maps; 6 | import org.yaml.snakeyaml.Yaml; 7 | 8 | import java.io.File; 9 | import java.io.FileInputStream; 10 | import java.io.FileNotFoundException; 11 | import java.net.URL; 12 | import java.util.Arrays; 13 | import java.util.Collection; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * 读取yaml 20 | * @Author: luoy 21 | * @Date: 2020/4/17 15:00. 22 | */ 23 | public class YmlParse { 24 | private static Map dataEntitys = Maps.newConcurrentMap(); 25 | 26 | private volatile static YmlParse ymlParse = null; 27 | private YmlParse(){} 28 | public static YmlParse getInstance() { 29 | if (ymlParse == null) { 30 | synchronized (YmlParse.class) { 31 | if (ymlParse == null) { 32 | ymlParse = new YmlParse(); 33 | } 34 | } 35 | } 36 | return ymlParse; 37 | } 38 | 39 | public YmlParse load(String fileName) { 40 | if (dataEntitys.containsKey(fileName)) { 41 | return this; 42 | } 43 | Yaml yaml = new Yaml(); 44 | String filePath = fileName; 45 | //先尝试加载下resource目录 46 | URL resource = this.getClass().getClassLoader().getResource(fileName); 47 | if (resource != null) { 48 | filePath = resource.getPath(); 49 | } 50 | 51 | File yamlFile = new File(filePath); 52 | try { 53 | DataEntity dataEntity = yaml.loadAs(new FileInputStream(yamlFile), DataEntity.class); 54 | dataEntitys.put(fileName, dataEntity); 55 | } catch (FileNotFoundException e) { 56 | throw new BizException("文件不存在:" + filePath, e); 57 | } 58 | checkRepeatabilityByName(); 59 | return this; 60 | } 61 | 62 | public YmlParse loads(String...paths) { 63 | for(String path : paths) { 64 | if(dataEntitys.containsKey(path)) { 65 | continue; 66 | } 67 | load(path); 68 | } 69 | return this; 70 | } 71 | 72 | public List getTestCases() { 73 | Collection values = dataEntitys.values(); 74 | List list = Lists.newArrayList(); 75 | values.forEach(dataEntity -> list.addAll(dataEntity.getTestCase())); 76 | return list; 77 | } 78 | 79 | public TestCase getTestCase(String name) { 80 | return getTestCases().stream().filter(testCase -> testCase.getName().equals(name)) 81 | .findAny().orElseThrow(() ->new BizException(name + ": yml文件未找到该name")); 82 | } 83 | 84 | private void checkRepeatabilityByName() { 85 | List names = getTestCases().stream().map(TestCase::getName).collect(Collectors.toList()); 86 | if(names.stream().distinct().count() != names.size()) { 87 | Map counterMap = names.stream().collect(Collectors.groupingBy(name -> name, Collectors.counting())); 88 | List distinctNames = counterMap.keySet().stream().filter(k -> counterMap.get(k) > 1).collect(Collectors.toList()); 89 | throw new BizException("yml中有重复name,请检查: " + Arrays.asList(distinctNames)); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/core/proxy/ProxyApi.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.proxy; 2 | 3 | import java.lang.reflect.Proxy; 4 | 5 | /** 6 | * 接口动态代理类 7 | * @Author: luoy 8 | * @Date: 2019/8/21 17:03. 9 | */ 10 | public class ProxyApi { 11 | public static T getProxyInstance(Class clazz) { 12 | 13 | return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, 14 | (proxy, method, args) -> { 15 | if(method.getName().equals(ProxyMethodName.doHttp.name())) { 16 | ProxySupport execute = new ProxySupport(clazz, method, args); 17 | return execute.service(); 18 | } else { 19 | ProxySupportBefore before = new ProxySupportBefore(clazz, method, args); 20 | return before.handle(); 21 | } 22 | }); 23 | } 24 | 25 | public enum ProxyMethodName{ 26 | doHttp 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/proxy/ProxyFactoryBean.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.proxy; 2 | 3 | import org.springframework.beans.factory.FactoryBean; 4 | import org.springframework.beans.factory.InitializingBean; 5 | 6 | /** 7 | * @Author: luoy 8 | * @Date: 2019/9/9 16:08. 9 | * InitializingBean:初始化时afterPropertiesSet被调用,生成clazz类型的代理类对象 10 | */ 11 | public class ProxyFactoryBean implements FactoryBean, InitializingBean { 12 | private Class clazz; 13 | 14 | private Object proxy; 15 | 16 | 17 | @Override 18 | public Object getObject() throws Exception { 19 | return proxy; 20 | } 21 | 22 | @Override 23 | public Class getObjectType() { 24 | return clazz; 25 | } 26 | 27 | @Override 28 | public boolean isSingleton() { 29 | return true; 30 | } 31 | 32 | @Override 33 | public void afterPropertiesSet() throws Exception { 34 | this.proxy = ProxyApi.getProxyInstance(clazz); 35 | } 36 | 37 | public void setClazz(Class clazz) { 38 | this.clazz = clazz; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/proxy/ProxySupportBefore.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.proxy; 2 | 3 | 4 | import com.ly.api.BaseHttpClient; 5 | import com.ly.core.base.Response; 6 | import com.ly.core.parse.BaseModel; 7 | import com.ly.core.utils.ContextDataStorage; 8 | import com.ly.core.utils.SpringContextUtil; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | import java.lang.reflect.Method; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * @Author: luoy 16 | * @Date: 2020/3/19 15:08. 17 | */ 18 | public class ProxySupportBefore implements BaseHttpClient{ 19 | 20 | private Class clazz; 21 | 22 | private Method method; 23 | 24 | private Object[] args; 25 | 26 | public ProxySupportBefore(Class clazz, Method method, Object[] args) { 27 | this.clazz = clazz; 28 | this.method = method; 29 | this.args = args; 30 | } 31 | 32 | public final T handle() { 33 | try { 34 | method.invoke(this, args); 35 | } catch (IllegalAccessException e) { 36 | e.printStackTrace(); 37 | } catch (InvocationTargetException e) { 38 | e.printStackTrace(); 39 | } 40 | return SpringContextUtil.getBean(clazz); 41 | } 42 | 43 | @Override 44 | public BaseHttpClient wait(TimeUnit unit, long interval) { 45 | try { 46 | unit.sleep(interval); 47 | } catch (InterruptedException e) { 48 | e.printStackTrace(); 49 | } 50 | return this; 51 | } 52 | 53 | @Override 54 | public BaseHttpClient saveAsk(String k, Object v) { 55 | ContextDataStorage.getInstance().setAskAttribute(k, v); 56 | return this; 57 | } 58 | 59 | @Override 60 | public BaseHttpClient saveMethod(String k, Object v) { 61 | ContextDataStorage.getInstance().setMethodAttribute(k, v); 62 | return this; 63 | } 64 | 65 | @Override 66 | public BaseHttpClient saveThread(String k, Object v) { 67 | ContextDataStorage.getInstance().setThreadAttribute(k, v); 68 | return this; 69 | } 70 | 71 | @Override 72 | public BaseHttpClient saveGlobal(String k, Object v) { 73 | ContextDataStorage.getInstance().setAttribute(k, v); 74 | return this; 75 | } 76 | 77 | @Override 78 | public Response doHttp(BaseModel model) { 79 | return null; 80 | } 81 | 82 | @Override 83 | public Response doHttp(String modelName) { 84 | return null; 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/core/redis/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.redis; 2 | 3 | import org.springframework.beans.factory.annotation.Qualifier; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Lazy; 6 | import org.springframework.context.annotation.Primary; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | 9 | /** 10 | * @Author: luoy 11 | * @Date: 2019/8/30 9:58. 12 | */ 13 | //@Configuration 14 | //懒加载,当第一次使用这个bean的时候才加载进容器,目的为2点, 15 | // 1:减少IOC容器启动时间 16 | @Lazy 17 | public class RedisConfig { 18 | @Bean("Standalone-redis") 19 | @Primary 20 | public RedisService createProRedis(@Qualifier("redisStandaloneTemplate")RedisTemplate redisTemplate) { 21 | return new RedisServiceImpl(redisTemplate); 22 | } 23 | 24 | @Bean("Cluster-redis") 25 | public RedisService createStandardRedis(@Qualifier("redisClusterTemplate")RedisTemplate redisTemplate) { 26 | return new RedisServiceImpl(redisTemplate); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/redis/RedisService.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.redis; 2 | 3 | import java.util.Map; 4 | 5 | public interface RedisService { 6 | 7 | /** 8 | * 写入redis 9 | * @param key 10 | * @param data 11 | * @return 12 | */ 13 | void set(String key, Object data); 14 | 15 | /** 16 | * 写入redis 设置过时时间 17 | * @param key 18 | * @param data 19 | * @param expireTime 20 | * @return 21 | */ 22 | void set(String key, Object data, Long expireTime); 23 | 24 | /** 25 | * 判断key是否存在 26 | * @param key 27 | * @return 28 | */ 29 | Boolean exists(String key); 30 | 31 | /** 32 | * 获取key过期时间 33 | * @param key 34 | * @return 35 | */ 36 | long getExpire(String key); 37 | 38 | /** 39 | * 设置过期时间 40 | * @param key 41 | * @param expireTime 42 | */ 43 | void setExpire(String key, long expireTime); 44 | 45 | /** 46 | * 获取数据 47 | * @param key 48 | * @return 49 | */ 50 | Object get(String key); 51 | 52 | /** 53 | * 删除key 54 | * @param key 55 | */ 56 | void del(String key); 57 | 58 | /** 59 | * 批量删除满足正则的key 60 | * @param kePattern 61 | */ 62 | void delPattern(String kePattern); 63 | 64 | /** 65 | * 删除多个key 66 | * @param keys 67 | */ 68 | void del(String... keys); 69 | 70 | /** 71 | * 添加hash 72 | * @param key 73 | * @param map 74 | */ 75 | void hmSet(String key, Map map); 76 | 77 | /** 78 | * 添加hash,设置过期时间 79 | * @param key 80 | * @param map 81 | * @param expireTime 82 | */ 83 | void hmSet(String key, Map map, Long expireTime); 84 | 85 | /** 86 | * 获取hash 87 | * @param key 88 | * @return Map 89 | */ 90 | Map hmGet(String key); 91 | 92 | /** 93 | * 向一张hash表中放入数据,如果不存在将创建 94 | * @param key 95 | * @param hashKey 96 | * @param v 97 | */ 98 | void hSet(String key, String hashKey, Object v); 99 | 100 | /** 101 | * 向一张hash表中放入数据,如果不存在将创建,设置过期时间,过期时间会覆盖原来设置过期时间 102 | * @param key 103 | * @param hashKey 104 | * @param v 105 | * @param expireTime 106 | */ 107 | void hSet(String key, String hashKey, Object v, Long expireTime); 108 | 109 | /** 110 | * 获取hash表中的key对应的value 111 | * @param key 112 | * @param hashKey 113 | * @return 114 | */ 115 | Object hGet(String key, String hashKey); 116 | 117 | /** 118 | * 删除hash表中对应的key 119 | * @param key 120 | * @param hashKey 121 | */ 122 | void hDel(String key, String... hashKey); 123 | 124 | /** 125 | * 判断hash表中是否存在该key 126 | * @param key 127 | * @param hashKey 128 | * @return 129 | */ 130 | boolean existsHashKey(String key, String hashKey); 131 | 132 | //list 133 | //set 134 | } 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/redis/RedisServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.redis; 2 | 3 | import org.springframework.data.redis.core.RedisTemplate; 4 | 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * @author luoy 11 | */ 12 | public class RedisServiceImpl implements RedisService{ 13 | private RedisTemplate redisTemplate; 14 | 15 | public RedisServiceImpl(RedisTemplate redisTemplate) { 16 | this.redisTemplate = redisTemplate; 17 | } 18 | 19 | @Override 20 | public void set(String key, Object data) { 21 | redisTemplate.opsForValue().set(key, data); 22 | } 23 | 24 | @Override 25 | public void set(String key, Object data, Long expireTime) { 26 | if(expireTime > 0) { 27 | this.set(key, data); 28 | this.setExpire(key, expireTime); 29 | } 30 | } 31 | 32 | @Override 33 | public long getExpire(String key) { 34 | return redisTemplate.getExpire(key,TimeUnit.SECONDS); 35 | } 36 | 37 | @Override 38 | public Boolean exists(String key) { 39 | return redisTemplate.hasKey(key); 40 | } 41 | 42 | @Override 43 | public Object get(String key) { 44 | return key == null ?null : redisTemplate.opsForValue().get(key); 45 | } 46 | 47 | @Override 48 | public void del(String key) { 49 | if(exists(key)) { 50 | redisTemplate.delete(key); 51 | } 52 | } 53 | 54 | @Override 55 | public void delPattern(String kePattern) { 56 | Set setKey = redisTemplate.keys(kePattern); 57 | 58 | if(setKey.size() > 0) { 59 | redisTemplate.delete(setKey); 60 | } 61 | } 62 | 63 | @Override 64 | public void setExpire(String key, long expireTime) { 65 | redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); 66 | } 67 | 68 | @Override 69 | public void del(String... keys) { 70 | for (String key : keys) { 71 | if(exists(key)) { 72 | redisTemplate.delete(key); 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | public void hmSet(String key, Map map) { 79 | redisTemplate.opsForHash().putAll(key, map); 80 | } 81 | 82 | @Override 83 | public void hmSet(String key, Map map, Long expireTime) { 84 | this.hmSet(key, map); 85 | if (expireTime > 0) { 86 | setExpire(key, expireTime); 87 | } 88 | } 89 | 90 | @Override 91 | public Map hmGet(String key) { 92 | return redisTemplate.opsForHash().entries(key); 93 | } 94 | 95 | @Override 96 | public void hSet(String key, String hashKey, Object v) { 97 | redisTemplate.opsForHash().put(key, hashKey, v); 98 | } 99 | 100 | @Override 101 | public void hSet(String key, String hashKey, Object v, Long expireTime) { 102 | redisTemplate.opsForHash().put(key, hashKey, v); 103 | if(expireTime > 0) { 104 | setExpire(key, expireTime); 105 | } 106 | } 107 | 108 | @Override 109 | public Object hGet(String key, String hashKey) { 110 | return redisTemplate.opsForHash().get(key, hashKey); 111 | } 112 | 113 | @Override 114 | public void hDel(String key, String...hashKey) { 115 | if (key != null && hashKey != null) { 116 | redisTemplate.opsForHash().delete(key, (Object)hashKey); 117 | } 118 | } 119 | 120 | @Override 121 | public boolean existsHashKey(String key, String hashKey) { 122 | return redisTemplate.opsForHash().hasKey(key, hashKey); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/AllureSetStepHttpPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.exception.BizException; 4 | import com.ly.core.parse.BaseModel; 5 | import io.qameta.allure.Allure; 6 | import io.qameta.allure.AllureLifecycle; 7 | import io.qameta.allure.model.Status; 8 | import io.qameta.allure.model.StepResult; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.UUID; 14 | 15 | /** 16 | * @Author: luoy 17 | * @Date: 2020/12/1 19:58. 18 | */ 19 | @Component 20 | public class AllureSetStepHttpPostProcessor extends TestNgLifeCyclePostProcessorAdapter implements HttpPostProcessor{ 21 | private static final InheritableThreadLocal LIFECYCLE 22 | = new InheritableThreadLocal() { 23 | @Override 24 | protected AllureLifecycle initialValue() { 25 | return Allure.getLifecycle(); 26 | } 27 | }; 28 | 29 | @Override 30 | public void requestsBeforePostProcessor(HttpContext context) { 31 | BaseModel baseModel = context.getBaseModel(); 32 | if(baseModel == null) { 33 | return; 34 | } 35 | doSetStartStep(getUrlPath(baseModel.getUrl()), baseModel.getDescription()); 36 | } 37 | 38 | @Override 39 | public void responseAfterPostProcessor(HttpContext context) { 40 | 41 | } 42 | 43 | @Override 44 | public void responseDonePostProcessor(HttpContext context) { 45 | if(!existStep()) { 46 | return; 47 | } 48 | Throwable ex = context.getThrowable(); 49 | if (ex != null) { 50 | LIFECYCLE.get().updateStep(s -> s.setStatus(Status.BROKEN)); 51 | } else { 52 | LIFECYCLE.get().updateStep(s -> s.setStatus(Status.PASSED)); 53 | } 54 | LIFECYCLE.get().stopStep(); 55 | } 56 | 57 | /** 58 | * 给每个接口设置allure @step注解 59 | */ 60 | private void doSetStartStep(String url, String description) { 61 | if(!existStep()) { 62 | return; 63 | } 64 | final String uuid = UUID.randomUUID().toString(); 65 | String stepTitle = "[%s] [%s]"; 66 | final StepResult result = new StepResult() 67 | .setName(String.format(stepTitle, url, description != null ? description : "")); 68 | LIFECYCLE.get().startStep(uuid, result); 69 | } 70 | 71 | private String getUrlPath(String url) { 72 | if (!url.trim().toLowerCase().startsWith("http")) { 73 | return url; 74 | } 75 | try { 76 | URL baseUrl = new URL(url); 77 | return baseUrl.getPath(); 78 | } catch (MalformedURLException e) { 79 | throw new BizException("url解析失败", e); 80 | } 81 | } 82 | 83 | private boolean existStep() { 84 | return Allure.getLifecycle().getCurrentTestCaseOrStep().isPresent(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/CacheLifeCycleByClassPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.utils.ContextDataStorage; 4 | import org.springframework.stereotype.Component; 5 | import org.testng.ITestClass; 6 | 7 | /** 8 | * class级别线程安全的缓存处理 9 | * @Author: luoy 10 | * @Date: 2020/12/8 16:16. 11 | */ 12 | @Component 13 | public class CacheLifeCycleByClassPostProcessor implements TestNgClassPostProcessor{ 14 | @Override 15 | public void onTestClassStartBeforePostProcessor(ITestClass iTestClass) { 16 | } 17 | 18 | @Override 19 | public void onTestClassFinishAfterPostProcessor(ITestClass iTestClass) { 20 | //class缓存清理 21 | ContextDataStorage.getInstance().removeAllClassAttribute(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/CacheLifeCyclePostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.utils.ContextDataStorage; 4 | import org.springframework.stereotype.Component; 5 | import org.testng.ITestContext; 6 | import org.testng.ITestResult; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/12/2 17:39. 11 | */ 12 | @Component 13 | public class CacheLifeCyclePostProcessor extends TestNgLifeCyclePostProcessorAdapter implements HttpPostProcessor{ 14 | 15 | @Override 16 | public void onTestMethodSuccessAfterPostProcessor(ITestResult result){ 17 | //method缓存清理 18 | ContextDataStorage.getInstance().removeAllMethodAttribute(); 19 | } 20 | 21 | @Override 22 | public void onTestMethodFailureAfterPostProcessor(ITestResult result){ 23 | //method缓存清理 24 | ContextDataStorage.getInstance().removeAllMethodAttribute(); 25 | } 26 | 27 | @Override 28 | public void onAllTestMethodFinishAfterPostProcessor(ITestContext context){ 29 | //线程缓存清理 30 | ContextDataStorage.getInstance().removeAllThreadAttribute(); 31 | } 32 | 33 | @Override 34 | public void requestsBeforePostProcessor(HttpContext context) { 35 | 36 | } 37 | 38 | @Override 39 | public void responseAfterPostProcessor(HttpContext context) { 40 | 41 | } 42 | 43 | @Override 44 | public void responseDonePostProcessor(HttpContext context) { 45 | //处理请求级别缓存清除 46 | ContextDataStorage.getInstance().removeAllAskAttribute(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/ConfigurationMethodSupportByClassPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.ly.core.annotation.ApiAfterClass; 5 | import com.ly.core.annotation.ApiBeforeClass; 6 | import org.assertj.core.util.Lists; 7 | import org.springframework.stereotype.Component; 8 | import org.testng.ITestClass; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * ApiBeforeClass ApiAfterClass 支持 16 | * 17 | * @Author: luoy 18 | * @Date: 2020/12/7 15:09. 19 | */ 20 | @Component 21 | public class ConfigurationMethodSupportByClassPostProcessor implements TestNgClassPostProcessor { 22 | private Map beforeClassMethods = Maps.newConcurrentMap(); 23 | 24 | @Override 25 | public void onTestClassStartBeforePostProcessor(ITestClass iTestClass) { 26 | Object[] instances = iTestClass.getInstances(false); 27 | collectBeforeClassMethod(instances); 28 | 29 | MethodDefinitionHolder methodDefinitionHolder = this.beforeClassMethods.get(iTestClass.getName()); 30 | List beforeClass = methodDefinitionHolder.byAnnotation(ApiBeforeClass.class); 31 | beforeClass.forEach(ConfigurationMethodSupportExcludeClassPostProcessor::runMethod); 32 | } 33 | 34 | @Override 35 | public void onTestClassFinishAfterPostProcessor(ITestClass iTestClass) { 36 | MethodDefinitionHolder methodDefinitionHolder = beforeClassMethods.get(iTestClass.getName()); 37 | List afterClass = methodDefinitionHolder.byAnnotation(ApiAfterClass.class); 38 | afterClass.forEach(ConfigurationMethodSupportExcludeClassPostProcessor::runMethod); 39 | } 40 | 41 | private void collectBeforeClassMethod(Object[] instances) { 42 | for (Object instance : instances) { 43 | List methodDefinitions = Lists.newArrayList(); 44 | Method[] methods = instance.getClass().getMethods(); 45 | for (Method method : methods) { 46 | if (method.getAnnotation(ApiAfterClass.class) != null || 47 | method.getAnnotation(ApiBeforeClass.class) != null) { 48 | Object[] args = ConfigurationMethodSupportExcludeClassPostProcessor.getArgs(method); 49 | methodDefinitions.add(MethodDefinition.builder().instance(instance).method(method).args(args).build()); 50 | } 51 | } 52 | beforeClassMethods.put(instance.getClass().getName(), MethodDefinitionHolder.builder().methodDefinitions(methodDefinitions).build()); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/HookHttpPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.config.DefaultConstantConfig; 4 | import com.ly.core.parse.BaseModel; 5 | import com.ly.core.parse.TestCase; 6 | import com.ly.core.utils.ReflectUtil; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | /** 14 | * @Author: luoy 15 | * @Date: 2020/12/1 19:58. 16 | */ 17 | @Component 18 | public class HookHttpPostProcessor implements HttpPostProcessor { 19 | @Override 20 | public void requestsBeforePostProcessor(HttpContext context) { 21 | BaseModel model = context.getBaseModel(); 22 | if(model == null) { 23 | return; 24 | } 25 | 26 | getSetupHook(context).forEach(hook -> { 27 | //此处处理${request}参数化, 把${request} => baseModel, yam文件其他地方请勿用${request} ${response}引用 28 | String[] args = StringUtils.isBlank(hook.getArgs()) ? new String[0] : hook.getArgs().split(","); 29 | Object[] obj = new Object[args.length]; 30 | for (int i = 0; i < args.length; i++) { 31 | if ("${request}".equals(args[i])) { 32 | obj[i] = model; 33 | } else { 34 | obj[i] = args[i]; 35 | } 36 | } 37 | ReflectUtil.reflectInvoke(DefaultConstantConfig.SETUP_METHOD_BY_CLASS, hook.getMethod(), obj); 38 | }); 39 | } 40 | 41 | @Override 42 | public void responseAfterPostProcessor(HttpContext context) { 43 | } 44 | 45 | @Override 46 | public void responseDonePostProcessor(HttpContext context) { 47 | BaseModel model = context.getBaseModel(); 48 | if(model == null) { 49 | return; 50 | } 51 | getTeardownHook(context).forEach( hook -> { 52 | //此处处理${response}参数化, 把${response} => com.ly.core.base.Response, yam文件其他地方请勿用${request} ${response}引用 53 | String[] args = StringUtils.isBlank(hook.getArgs()) ? new String[0] : hook.getArgs().split(","); 54 | Object[] obj = new Object[args.length]; 55 | for (int i = 0; i < args.length; i++) { 56 | if ("${response}".equals(args[i])) { 57 | obj[i] = context.getResponse(); 58 | } else { 59 | obj[i] = args[i]; 60 | } 61 | } 62 | ReflectUtil.reflectInvoke(DefaultConstantConfig.TEARDOWN_METHOD_BY_CLASS, hook.getMethod(), obj); 63 | }); 64 | } 65 | 66 | private List getSetupHook(HttpContext context) { 67 | List hooks = context.getBaseModel().getSetup(); 68 | if (hooks == null || hooks.size() <= 0) { 69 | return Collections.emptyList(); 70 | } 71 | return hooks; 72 | } 73 | 74 | private List getTeardownHook(HttpContext context) { 75 | List hooks = context.getBaseModel().getTeardown(); 76 | if (hooks == null || hooks.size() <= 0) { 77 | return Collections.emptyList(); 78 | } 79 | return hooks; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/HttpContext.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.base.Response; 4 | import com.ly.core.parse.BaseModel; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * http请求上下文 12 | * @Author: luoy 13 | * @Date: 2020/12/1 20:27. 14 | */ 15 | @Data 16 | @Builder 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class HttpContext { 20 | /** 请求 */ 21 | private BaseModel baseModel; 22 | 23 | /**响应 */ 24 | private Response response; 25 | 26 | /** response操作中的throw Throwable*/ 27 | private Throwable throwable; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/HttpPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.base.Response; 4 | 5 | /** 6 | * http请求后置处理器 7 | * @Author: luoy 8 | * @Date: 2020/12/1 18:08. 9 | */ 10 | public interface HttpPostProcessor extends PostProcessor{ 11 | 12 | /** 13 | * http请求之前处理器 14 | * @param context 15 | */ 16 | void requestsBeforePostProcessor(HttpContext context); 17 | 18 | /** 19 | * http请求之后处理器 20 | * @param context 21 | */ 22 | void responseAfterPostProcessor(HttpContext context); 23 | 24 | 25 | /** 26 | * http请求后 对response对象进行各种处理后的处理器 27 | * 在{@link Response done()}内调用 28 | * @param context 29 | */ 30 | void responseDonePostProcessor(HttpContext context); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/MethodDefinition.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/12/4 13:30. 11 | */ 12 | @Data 13 | @Builder 14 | public class MethodDefinition { 15 | private Method method; 16 | 17 | private Object[] args; 18 | 19 | private Object instance; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/MethodDefinitionHolder.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import lombok.Builder; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | /** 10 | * @Author: luoy 11 | * @Date: 2020/12/4 15:08. 12 | */ 13 | @Builder 14 | public class MethodDefinitionHolder { 15 | private List methodDefinitions; 16 | 17 | public List byAnnotation(Class annotation) { 18 | return methodDefinitions.stream().filter(methodDefinition -> methodDefinition.getMethod().getAnnotation(annotation) != null) 19 | .collect(Collectors.toList()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/NotificationPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.listener.LifeCycleListenerProcessorUtil; 4 | import org.springframework.stereotype.Component; 5 | import org.testng.ISuite; 6 | 7 | /** 8 | * @Author: luoy 9 | * @Date: 2020/12/2 17:39. 10 | */ 11 | @Component 12 | public class NotificationPostProcessor extends TestNgLifeCyclePostProcessorAdapter{ 13 | @Override 14 | public void onSuiteFinishAfterPostProcessor(ISuite suite){ 15 | LifeCycleListenerProcessorUtil.create().notificationOnFinish(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/Order.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | /** 4 | * 排序接口 5 | * @Author: luoy 6 | * @Date: 2020/12/2 17:50. 7 | */ 8 | public interface Order { 9 | /** 10 | * 数字越小优先级越高 11 | * @return 12 | */ 13 | int getOrder(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/PostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | /** 4 | * 各个阶段后置处理器, 直接注册进spring容器, 所以需要@Server @Component等注解 5 | * @Author: luoy 6 | * @Date: 2020/12/1 18:06. 7 | */ 8 | public interface PostProcessor { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/PostProcessorHolder.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.utils.SpringContextUtil; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | /** 12 | * @Author: luoy 13 | * @Date: 2020/12/1 18:15. 14 | */ 15 | public class PostProcessorHolder { 16 | private List postProcessors ; 17 | 18 | private volatile static PostProcessorHolder instance; 19 | 20 | private PostProcessorHolder() { 21 | this.postProcessors = scanningAndSortPostProcessor(); 22 | } 23 | 24 | public static PostProcessorHolder getInstance() { 25 | if (instance == null) { 26 | synchronized (PostProcessorHolder.class) { 27 | if (instance == null) { 28 | instance = new PostProcessorHolder(); 29 | } 30 | } 31 | } 32 | return instance; 33 | } 34 | 35 | public List getPostProcessor(Class clazz) { 36 | List collect = postProcessors.stream().filter(postProcessor -> clazz.isAssignableFrom(postProcessor.getClass())).collect(Collectors.toList()); 37 | return (List) collect; 38 | } 39 | 40 | /** 41 | * 在第一次调用的时候就直接排序 42 | */ 43 | private List scanningAndSortPostProcessor() { 44 | ApplicationContext applicationContext = SpringContextUtil.getApplicationContext(); 45 | Map beansOfType = applicationContext.getBeansOfType(PostProcessor.class); 46 | return beansOfType.values().stream().sorted(new OrderSort()).collect(Collectors.toList()); 47 | } 48 | 49 | static class OrderSort implements Comparator { 50 | @Override 51 | public int compare(Object o1, Object o2) { 52 | boolean c1 = o1 instanceof Order; 53 | boolean c2 = o2 instanceof Order; 54 | if (c1 && c2) { 55 | return Integer.compare(((Order) o1).getOrder(), ((Order) o2).getOrder()); 56 | } 57 | 58 | if (c1 && !c2) { 59 | return -1; 60 | } 61 | 62 | if (!c1 && c2) { 63 | return 1; 64 | } 65 | return 0; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/PrintLogPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.listener.LifeCycleListenerProcessorUtil; 4 | import org.springframework.stereotype.Component; 5 | import org.testng.ITestContext; 6 | import org.testng.ITestResult; 7 | 8 | /** 9 | * @Author: luoy 10 | * @Date: 2020/12/2 17:39. 11 | */ 12 | @Component 13 | public class PrintLogPostProcessor extends TestNgLifeCyclePostProcessorAdapter { 14 | @Override 15 | public void onAllTestMethodFinishAfterPostProcessor(ITestContext context){ 16 | LifeCycleListenerProcessorUtil.create().printLog(context); 17 | } 18 | 19 | @Override 20 | public void onConfigurationStartBeforePostProcessor(ITestResult tr){ 21 | LifeCycleListenerProcessorUtil.create().skipConfigurationStartPrintLog(tr); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/TestAnnotatinPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import com.ly.core.annotation.DataFile; 4 | import com.ly.core.annotation.DataModel; 5 | import com.ly.core.annotation.DataParams; 6 | import com.ly.core.listener.RetryAnalyzer; 7 | import com.ly.core.utils.AnnotationUtils; 8 | import org.springframework.stereotype.Component; 9 | import org.testng.ISuite; 10 | import org.testng.ITestNGMethod; 11 | import org.testng.annotations.Test; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * 在加载suite之前对@Test注解的一些操作 17 | * @Author: luoy 18 | * @Date: 2020/12/2 17:39. 19 | */ 20 | @Component 21 | public class TestAnnotatinPostProcessor extends TestNgLifeCyclePostProcessorAdapter { 22 | @Override 23 | public void onSuiteStartBeforePostProcessor(ISuite suite){ 24 | List allMethods = suite.getAllMethods(); 25 | allMethods.forEach(method -> { 26 | Test annotation = method.getConstructorOrMethod().getMethod().getAnnotation(Test.class); 27 | //获取运行的testNg方法 28 | //获取方法上的Test注解 29 | if (annotation != null) { 30 | Class retryAnalyzer = annotation.retryAnalyzer(); 31 | //设置重试 32 | if (retryAnalyzer == null) { 33 | AnnotationUtils.updateAnnotationValue(annotation, "retryAnalyzer", RetryAnalyzer.class); 34 | } 35 | 36 | //注解为@DataParams, 设置@Test的dataProvider为loadDataParams 37 | DataParams dataParams = method.getConstructorOrMethod().getMethod().getAnnotation(DataParams.class); 38 | if (dataParams != null) { 39 | AnnotationUtils.updateAnnotationValue(annotation, "dataProvider", "loadDataParams"); 40 | } 41 | 42 | //注解为@DataFile, 设置@Test的dataProvider为loadDataFile 43 | DataFile dataFile = method.getConstructorOrMethod().getMethod().getAnnotation(DataFile.class); 44 | if (dataFile != null) { 45 | AnnotationUtils.updateAnnotationValue(annotation, "dataProvider", "loadDataFile"); 46 | } 47 | 48 | //注解为@DataModel, 设置@Test的dataProvider为loadDataModel 49 | DataModel dataModel = method.getConstructorOrMethod().getMethod().getAnnotation(DataModel.class); 50 | if (dataModel != null) { 51 | AnnotationUtils.updateAnnotationValue(annotation, "dataProvider", "loadDataModel"); 52 | } 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/TestNgClassPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import org.testng.ITestClass; 4 | 5 | /** 6 | * http请求后置处理器 7 | * @Author: luoy 8 | * @Date: 2020/12/1 18:08. 9 | */ 10 | public interface TestNgClassPostProcessor extends PostProcessor{ 11 | /** 12 | * 测试类运行前执行 13 | * @param iTestClass 14 | */ 15 | void onTestClassStartBeforePostProcessor(ITestClass iTestClass); 16 | 17 | /** 18 | * 测试类运行后执行 19 | * @param iTestClass 20 | */ 21 | void onTestClassFinishAfterPostProcessor(ITestClass iTestClass); 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/TestNgLifeCyclePostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import org.testng.ISuite; 4 | import org.testng.ITestContext; 5 | import org.testng.ITestResult; 6 | 7 | /** 8 | * http请求后置处理器 9 | * @Author: luoy 10 | * @Date: 2020/12/1 18:08. 11 | */ 12 | public interface TestNgLifeCyclePostProcessor extends PostProcessor{ 13 | 14 | /** 15 | * 测试方法执行前执行 16 | * @param result 17 | */ 18 | void onTestMethodStartBeforePostProcessor(ITestResult result); 19 | 20 | /** 21 | * 测试方法执行成功后执行 22 | * @param result 23 | */ 24 | void onTestMethodSuccessAfterPostProcessor(ITestResult result); 25 | 26 | 27 | /** 28 | * 测试方法执行失败后执行 29 | * @param result 30 | */ 31 | void onTestMethodFailureAfterPostProcessor(ITestResult result); 32 | 33 | /** 34 | * 跳过测试方法后执行 35 | * @param result 36 | */ 37 | void onTestMethodSkippedAfterPostProcessor(ITestResult result); 38 | 39 | /** 40 | * 在实例化测试类之后且在调用任何配置方法之前调用 41 | * @param context 42 | */ 43 | void onTestClassInstantiationAfterPostProcessor(ITestContext context); 44 | 45 | /** 46 | * 在运行所有测试并调用其所有配置方法之后调用 47 | * @param context 48 | */ 49 | void onAllTestMethodFinishAfterPostProcessor(ITestContext context); 50 | 51 | /** 52 | * suite执行之前执行 对应test.xml suite标签 53 | * @param suite 54 | */ 55 | void onSuiteStartBeforePostProcessor(ISuite suite); 56 | 57 | /** 58 | * suite执行后执行 对应test.xml suite标签 59 | * @param suite 60 | */ 61 | void onSuiteFinishAfterPostProcessor(ISuite suite); 62 | 63 | /** 64 | * 配置方法运行前执行(配置方法: beforeTest,AfterTest等) 65 | * @param result 66 | */ 67 | void onConfigurationStartBeforePostProcessor(ITestResult result); 68 | 69 | /** 70 | * 配置方法执行成功时执行 71 | * @param result 72 | */ 73 | void onConfigurationSuccessAfterPostProcessor(ITestResult result); 74 | 75 | /** 76 | * 配置方法执行失败时执行 77 | * @param result 78 | */ 79 | void onConfigurationFailureAfterPostProcessor(ITestResult result); 80 | 81 | /** 82 | * 配置方法跳过时执行 83 | * @param result 84 | */ 85 | void onConfigurationSkipAfterPostProcessor(ITestResult result); 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/support/TestNgLifeCyclePostProcessorAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.support; 2 | 3 | import org.testng.ISuite; 4 | import org.testng.ITestContext; 5 | import org.testng.ITestResult; 6 | 7 | /** 8 | * 适配器 9 | * @Author: luoy 10 | * @Date: 2020/12/2 17:12. 11 | */ 12 | public abstract class TestNgLifeCyclePostProcessorAdapter implements TestNgLifeCyclePostProcessor{ 13 | @Override 14 | public void onTestMethodStartBeforePostProcessor(ITestResult result) { 15 | 16 | } 17 | 18 | @Override 19 | public void onTestMethodSuccessAfterPostProcessor(ITestResult result) { 20 | 21 | } 22 | 23 | @Override 24 | public void onTestMethodFailureAfterPostProcessor(ITestResult result) { 25 | 26 | } 27 | 28 | @Override 29 | public void onTestMethodSkippedAfterPostProcessor(ITestResult result) { 30 | 31 | } 32 | 33 | @Override 34 | public void onTestClassInstantiationAfterPostProcessor(ITestContext context) { 35 | 36 | } 37 | 38 | @Override 39 | public void onAllTestMethodFinishAfterPostProcessor(ITestContext context) { 40 | 41 | } 42 | 43 | @Override 44 | public void onSuiteStartBeforePostProcessor(ISuite suite) { 45 | 46 | } 47 | 48 | @Override 49 | public void onSuiteFinishAfterPostProcessor(ISuite suite) { 50 | 51 | } 52 | 53 | @Override 54 | public void onConfigurationStartBeforePostProcessor(ITestResult result) { 55 | 56 | } 57 | 58 | @Override 59 | public void onConfigurationSuccessAfterPostProcessor(ITestResult result) { 60 | 61 | } 62 | 63 | @Override 64 | public void onConfigurationFailureAfterPostProcessor(ITestResult result) { 65 | 66 | } 67 | 68 | @Override 69 | public void onConfigurationSkipAfterPostProcessor(ITestResult result) { 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/AnnotationUtils.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.InvocationHandler; 6 | import java.lang.reflect.Proxy; 7 | import java.util.Map; 8 | 9 | /** 10 | * @Author: luoy 11 | * @Date: 2020/11/9 15:56. 12 | */ 13 | public class AnnotationUtils { 14 | 15 | /** 16 | * 通过反射的方式修改注解字段的值 17 | * @param instance 注解实例 18 | * @param propertyKey 注解属性名 19 | * @param propertyValue 修改后的值 20 | */ 21 | public static void updateAnnotationValue(Annotation instance, String propertyKey, Object propertyValue) { 22 | if (instance == null) { 23 | return; 24 | } 25 | try{ 26 | //获取@test注解这个代理实例所持有的 InvocationHandler 27 | InvocationHandler h = Proxy.getInvocationHandler(instance); 28 | // 获取 AnnotationInvocationHandler 的 memberValues 字段 29 | Field hField = h.getClass().getDeclaredField("memberValues"); 30 | // 因为这个字段事 private final 修饰,所以要打开权限 31 | hField.setAccessible(true); 32 | // 获取 memberValues 33 | Map memberValues = (Map) hField.get(h); 34 | // 修改 value 属性值 35 | memberValues.put(propertyKey, propertyValue); 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/JSONSerializerUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.alibaba.fastjson.serializer.SerializerFeature; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | public class JSONSerializerUtil { 9 | 10 | /**深拷贝*/ 11 | public static T copy(T t, Class clazz) { 12 | return unSerialize(serializeExistNull(t), clazz); 13 | } 14 | 15 | /** 16 | * 过滤value为null的key 17 | * @param t 18 | * @return 19 | */ 20 | public static String serialize(T t) { 21 | if (t == null) { 22 | return ""; 23 | } else { 24 | return JSON.toJSONString(t); 25 | } 26 | } 27 | 28 | public static Object serializeObj(T t) { 29 | if (t == null) { 30 | return ""; 31 | } else { 32 | return JSONObject.toJSON(t); 33 | } 34 | } 35 | 36 | public static T unSerialize(String json, Class clazz) { 37 | if (StringUtils.isBlank(json)) { 38 | return null; 39 | } else { 40 | return JSON.parseObject(json, clazz); 41 | } 42 | } 43 | 44 | /** 45 | * 不过滤value为null的key 46 | * @param t 47 | * @return 48 | */ 49 | public static String serializeExistNull(T t) { 50 | if (t == null) { 51 | return ""; 52 | } else { 53 | return JSON.toJSONString(t, SerializerFeature.WriteMapNullValue); 54 | } 55 | } 56 | 57 | /** 58 | * entity -> jsonobject 59 | * @param t 60 | * @param 61 | * @return 62 | */ 63 | public static JSONObject serializeToJsonobject(T t) { 64 | if (t instanceof JSONObject) { 65 | return (JSONObject) t; 66 | } 67 | 68 | return (JSONObject) JSON.toJSON(t); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/MapTool.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by luoy on 2019/7/17. 8 | */ 9 | public class MapTool { 10 | private Map map = new HashMap<>(); 11 | 12 | private MapTool put(String k, String v) { 13 | map.put(k, v); 14 | return this; 15 | } 16 | 17 | public static Builder builder() { 18 | return new Builder (); 19 | } 20 | 21 | public static class Builder { 22 | private MapTool mapTool = new MapTool(); 23 | public Builder put(String k, String v) { 24 | mapTool.put(k, v); 25 | return this; 26 | } 27 | 28 | public Map build() { 29 | return mapTool.map; 30 | } 31 | } 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/ParameterizationUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * @Author: luoy 8 | * @Date: 2020/4/22 14:47. 9 | */ 10 | public class ParameterizationUtil { 11 | 12 | public static String wildcardMatcherString(String data) { 13 | return PatternUtil.replace(data); 14 | } 15 | 16 | public static void wildcardMatcherHeadersAndRequests(Map data) { 17 | if (data == null || data.size() == 0) { 18 | return; 19 | } 20 | for (String key : data.keySet()) { 21 | if (data.get(key) instanceof String) { 22 | data.put(key, PatternUtil.replace((String) data.get(key))); 23 | } 24 | } 25 | } 26 | 27 | public static void wildcardMatcherValidate(Map> validate) { 28 | if (validate == null || validate.size() == 0) { 29 | return; 30 | } 31 | for (String key : validate.keySet()) { 32 | List lists = validate.get(key); 33 | if (lists != null && lists.size() > 0) { 34 | for (int i = 0; i < lists.size(); i++) { 35 | if (lists.get(i) instanceof Map) { 36 | Map map = (Map) lists.get(i); 37 | for (String mapKey : map.keySet()) { 38 | if (map.get(mapKey) instanceof String) { 39 | map.put(mapKey, PatternUtil.replace((String) map.get(mapKey))); 40 | } 41 | } 42 | } 43 | 44 | if (lists.get(i) instanceof String) { 45 | String arg = PatternUtil.replace((String) lists.get(i)); 46 | lists.set(i, arg); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/PatternUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.ly.core.exception.BizException; 5 | import com.ly.core.matches.ValidateUtils; 6 | import org.assertj.core.util.Lists; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | import static java.util.regex.Pattern.compile; 14 | 15 | /** 16 | * @Author: luoy 17 | * @Date: 2019/9/19 14:48. 18 | */ 19 | public class PatternUtil { 20 | private static Pattern TAG_PATTERN = Pattern.compile("(?<=\\$\\{)(.+?)(?=\\})"); 21 | private static Pattern METHOD_AGES = compile(".+?(\\(.+?\\))"); 22 | 23 | /** 24 | * 返回匹配${}中的字符串 25 | * @param s 26 | * @return 27 | */ 28 | public static List getPatterns(String s) { 29 | List patterns = Lists.newArrayList(); 30 | Matcher m = TAG_PATTERN.matcher(s); 31 | while(m.find()){ 32 | patterns.add(m.group()); 33 | } 34 | return patterns; 35 | } 36 | 37 | public static String createKey(String key) { 38 | return "${" + key + "}"; 39 | } 40 | 41 | /** 42 | * ${}通配符 43 | * @param arg 44 | * @return 45 | */ 46 | public static String replace(String arg) { 47 | List patterns = PatternUtil.getPatterns(arg); 48 | if (patterns == null || patterns.size() == 0) { 49 | return arg; 50 | } 51 | for(String str : patterns) { 52 | String[] data = str.split(","); 53 | if (data.length > 2) { 54 | throw new BizException("%s包含多个逗号,无法确认默认值", arg); 55 | } 56 | ContextDataStorage instance = ContextDataStorage.getInstance(); 57 | String v = (String) instance.getAttr(data[0].trim()); 58 | if (data.length < 2) { 59 | AssertUtils.notNull(v, PatternUtil.createKey(data[0]) + ":缓存内未找到值"); 60 | arg = arg.replace(createKey(data[0].trim()), v); 61 | } 62 | 63 | if (data.length == 2) { 64 | arg = v == null ? arg.replace(createKey(str), data[1].trim()) : arg.replace(createKey(str), v); 65 | } 66 | } 67 | return arg; 68 | } 69 | 70 | /** 71 | * 反射方法名与参数提取 72 | * @param msg 73 | * @return 74 | */ 75 | public static Map extractHookByRegular(String msg){ 76 | Map rex = Maps.newHashMap(); 77 | if(msg.indexOf("(") <= 0) { 78 | rex.put(ValidateUtils.METHOD_NAME_KEY, msg.trim()); 79 | return rex; 80 | } else { 81 | String args = ""; 82 | 83 | Matcher m = METHOD_AGES.matcher(msg); 84 | while(m.find()){ 85 | args = m.group(1); 86 | } 87 | 88 | String methodName = msg.substring(0, msg.indexOf(args)); 89 | 90 | rex.put(ValidateUtils.METHOD_ARGS_KEY, args.substring(1, args.length() - 1)); 91 | rex.put(ValidateUtils.METHOD_NAME_KEY, methodName); 92 | return rex; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import com.ly.core.exception.BizException; 4 | import com.ly.core.matches.ValidateUtils; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | import java.util.Map; 10 | 11 | /** 12 | * @Author: luoy 13 | * @Date: 2020/9/28 10:35. 14 | */ 15 | public class ReflectUtil { 16 | public static Object reflectInvoke(Class clazz, String methodName, String argsString) { 17 | String[] args = StringUtils.isBlank(argsString) ? new String[0] : argsString.split(","); 18 | return reflectInvoke(clazz, methodName, args); 19 | } 20 | 21 | public static Object reflectInvoke(Class clazz, String methodName, Object[] args) { 22 | try { 23 | if (StringUtils.isBlank(methodName)) { 24 | return null; 25 | } 26 | 27 | T obj = SpringContextUtil.getBean(clazz) != null ? SpringContextUtil.getBean(clazz) : clazz.newInstance(); 28 | Method[] declaredMethods = obj.getClass().getDeclaredMethods(); 29 | boolean isMethodExist = false; 30 | for(Method method : declaredMethods) { 31 | if (methodName.equals(method.getName())) { 32 | isMethodExist = true; 33 | Class[] parameterTypes = method.getParameterTypes(); 34 | AssertUtils.eqInt(args.length, parameterTypes.length, "%s,该方法入参不匹配", methodName); 35 | Object[] invokeArgs = new Object[parameterTypes.length]; 36 | for (int i = 0; i < parameterTypes.length; i++) { 37 | if (args[i] instanceof String) { 38 | String arg = ParameterizationUtil.wildcardMatcherString((String) args[i]); 39 | arg = arg.trim(); 40 | String simpleName = parameterTypes[i].getSimpleName(); 41 | if ("String".equals(simpleName)) { 42 | invokeArgs[i] = String.valueOf(arg); 43 | } else if ("Integer".equals(simpleName) || "int".equals(simpleName)) { 44 | invokeArgs[i] = Integer.valueOf(arg); 45 | } else if ("Boolean".equals(simpleName) || "boolean".equals(simpleName)) { 46 | invokeArgs[i] = Boolean.valueOf(arg); 47 | } else if ("Float".equals(simpleName) || "float".equals(simpleName)) { 48 | invokeArgs[i] = Float.valueOf(arg); 49 | } else if ("Double".equals(simpleName) || "double".equals(simpleName)) { 50 | invokeArgs[i] = Double.valueOf(arg); 51 | } else { 52 | throw new BizException("%s类,方法: %s,入参类型: %s,不支持该类型", clazz.getName(), methodName, simpleName); 53 | } 54 | } else { 55 | invokeArgs[i] = args[i]; 56 | } 57 | } 58 | try { 59 | return method.invoke(obj, invokeArgs); 60 | } catch (InvocationTargetException e) { 61 | if (e.getTargetException() instanceof BizException){ 62 | throw (BizException) e.getTargetException(); 63 | } 64 | throw new BizException("%s类, 方法: %s,异常: %s", clazz.getName(), methodName, e.getTargetException()); 65 | } catch (IllegalArgumentException e) { 66 | throw new BizException("%s类, 方法: %s,参数不匹配", clazz.getName(), methodName); 67 | } 68 | } 69 | } 70 | if (!isMethodExist) { 71 | throw new BizException("%s类, 方法: %s未找到", clazz.getName(), methodName); 72 | } 73 | } catch (InstantiationException e) { 74 | throw new BizException("%s, 实例化异常,该类是一个抽象类或接口", clazz.getName()); 75 | } catch (IllegalAccessException e) { 76 | throw new BizException("%s,非法访问异常,请设置setAccessible(true)", clazz.getName()); 77 | } 78 | return null; 79 | } 80 | 81 | public static Object[] hooksCall(String[] hooks, Class clazz) { 82 | if (hooks == null || hooks.length <= 0) { 83 | return new Object[0]; 84 | } 85 | Object[] res = new Object[hooks.length]; 86 | for (int i = 0; i < hooks.length; i++) { 87 | Map methodAndArgs = PatternUtil.extractHookByRegular(hooks[i]); 88 | String methodName = methodAndArgs.get(ValidateUtils.METHOD_NAME_KEY); 89 | String args = methodAndArgs.containsKey(ValidateUtils.METHOD_ARGS_KEY) ? methodAndArgs.get(ValidateUtils.METHOD_ARGS_KEY) : null; 90 | Object invokeRes = ReflectUtil.reflectInvoke(clazz, methodName, args); 91 | res[i] = invokeRes; 92 | } 93 | return res; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/ResourcesUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import org.springframework.core.env.Environment; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2019/9/16 15:52. 8 | */ 9 | public class ResourcesUtil { 10 | private static Environment environment = SpringContextUtil.getBean(Environment.class); 11 | 12 | public static String getProp(String key) { 13 | return environment.getProperty(key); 14 | } 15 | 16 | public static Boolean getPropBoolean(String key) { 17 | return Boolean.valueOf(environment.getProperty(key)); 18 | } 19 | 20 | public static int getPropInt(String key) { 21 | return Integer.valueOf(environment.getProperty(key)); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/Source2Yaml.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import com.ly.core.parse.Har2Yaml; 4 | import com.ly.core.parse.SwaggerParse; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * 其他源转换成yaml 9 | * @Author: luoy 10 | * @Date: 2020/12/28 10:13. 11 | */ 12 | @Slf4j 13 | public class Source2Yaml { 14 | /** 15 | * har文件生成yaml 16 | * 指定生成yml路径与名字,且每次都是往yml中最后追加 17 | * @param harPath 18 | * @param toYamlPath 19 | * @param isReplace 是否覆盖 true: 覆盖, false: 追加 20 | */ 21 | public static void harToYaml(String harPath, String toYamlPath, boolean isReplace) { 22 | Har2Yaml.toYaml(harPath, toYamlPath, isReplace); 23 | log.info("harToYml success!"); 24 | } 25 | 26 | /** 27 | * har文件生成yaml 28 | * 生成路径和名字直接在har文件同级目录和名字,且每次执行都是覆盖 29 | * @param harPath 30 | */ 31 | public static void harToYaml(String harPath) { 32 | Har2Yaml.toYaml(harPath); 33 | log.info("harToYml success!"); 34 | } 35 | 36 | /** 37 | * swagger生成yaml 38 | * @param isReplace 是否覆盖 true: 覆盖, false: 追加 39 | * @param swaggerPath 兼容swaggerUrl, localSwaggerJson, file 40 | * @param writePath 41 | */ 42 | public static void swaggerToYaml(String swaggerPath, String writePath, boolean isReplace) { 43 | new SwaggerParse().parse(swaggerPath, writePath, isReplace); 44 | log.info("swaggerToYml success!"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/SpringContextUtil.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class SpringContextUtil implements ApplicationContextAware { 10 | private static ApplicationContext applicationContext; 11 | 12 | @Override 13 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 14 | SpringContextUtil.applicationContext = applicationContext; 15 | } 16 | 17 | public static ApplicationContext getApplicationContext() { 18 | return applicationContext; 19 | } 20 | 21 | public static Object getBean(String beanName) { 22 | if (applicationContext == null) { 23 | return null; 24 | } 25 | return applicationContext.getBean(beanName); 26 | } 27 | 28 | public static T getBean(Class clazz) { 29 | if (applicationContext == null) { 30 | return null; 31 | } 32 | return applicationContext.getBean(clazz); 33 | } 34 | 35 | public static boolean contains(String name) { 36 | if (applicationContext == null) { 37 | return false; 38 | } 39 | return applicationContext.containsBean(name); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/TestCase2BaseModel.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import com.ly.core.exception.BizException; 4 | import com.ly.core.parse.FormModel; 5 | import com.ly.core.parse.JsonModel; 6 | import com.ly.core.parse.MapToXml; 7 | import com.ly.core.parse.TestCase; 8 | import com.ly.core.parse.TextModel; 9 | import com.ly.core.parse.XmlModel; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Objects; 15 | 16 | /** 17 | * 深拷贝 18 | * 19 | * @Author: luoy 20 | * @Date: 2020/5/14 17:55. 21 | */ 22 | public class TestCase2BaseModel { 23 | 24 | public static JsonModel toJsonModel(TestCase testCase) { 25 | if (Objects.nonNull(testCase.getRequests())) { 26 | if (!(testCase.getRequests() instanceof Map || testCase.getRequests() instanceof List)) { 27 | throw new BizException("Requests格式错误,当type为json时必须为键值对或者数组, 错误modelName:%s", testCase.getName()); 28 | } 29 | } 30 | return JSONSerializerUtil.copy(JsonModel.builder() 31 | .headers(testCase.getHeaders()) 32 | .name(testCase.getName()) 33 | .url(testCase.getUrl()) 34 | .description(testCase.getDescription()) 35 | .method(testCase.getMethod()) 36 | .jsonData(JSONSerializerUtil.serialize(testCase.getRequests())) 37 | .validate(testCase.getValidate()) 38 | .setup(testCase.getSetup()) 39 | .saveMethod(testCase.getSaveMethod()) 40 | .saveClass(testCase.getSaveClass()) 41 | .saveGlobal(testCase.getSaveGlobal()) 42 | .saveThread(testCase.getSaveThread()) 43 | .onFailure(testCase.getOnFailure()) 44 | .teardown(testCase.getTeardown()) 45 | .build(), JsonModel.class); 46 | } 47 | 48 | public static XmlModel toXmlModel(TestCase testCase) { 49 | if (Objects.nonNull(testCase.getRequests())) { 50 | if (!(testCase.getRequests() instanceof Map)) { 51 | throw new BizException("Requests格式错误,当type为xml时必须为键值对, 错误modelName:%s", testCase.getName()); 52 | } 53 | } 54 | return JSONSerializerUtil.copy(XmlModel.builder() 55 | .headers(testCase.getHeaders()) 56 | .name(testCase.getName()) 57 | .url(testCase.getUrl()) 58 | .description(testCase.getDescription()) 59 | .method(testCase.getMethod()) 60 | .xmlData(MapToXml.toXml((Map) testCase.getRequests())) 61 | .validate(testCase.getValidate()) 62 | .setup(testCase.getSetup()) 63 | .saveMethod(testCase.getSaveMethod()) 64 | .saveClass(testCase.getSaveClass()) 65 | .saveGlobal(testCase.getSaveGlobal()) 66 | .saveThread(testCase.getSaveThread()) 67 | .onFailure(testCase.getOnFailure()) 68 | .teardown(testCase.getTeardown()) 69 | .build(), XmlModel.class); 70 | } 71 | 72 | public static FormModel toFormModel(TestCase testCase) { 73 | if (Objects.nonNull(testCase.getRequests())) { 74 | if (!(testCase.getRequests() instanceof Map)) { 75 | //form请求数组格式? 76 | throw new BizException("Requests格式错误,当type为form时必须为键值对, 错误modelName:%s", testCase.getName()); 77 | } 78 | } 79 | return JSONSerializerUtil.copy(FormModel.builder() 80 | .headers(testCase.getHeaders()) 81 | .name(testCase.getName()) 82 | .url(testCase.getUrl()) 83 | .description(testCase.getDescription()) 84 | .method(testCase.getMethod()) 85 | .formData((Map) testCase.getRequests()) 86 | .validate(testCase.getValidate()) 87 | .setup(testCase.getSetup()) 88 | .saveMethod(testCase.getSaveMethod()) 89 | .saveClass(testCase.getSaveClass()) 90 | .saveGlobal(testCase.getSaveGlobal()) 91 | .saveThread(testCase.getSaveThread()) 92 | .onFailure(testCase.getOnFailure()) 93 | .teardown(testCase.getTeardown()) 94 | .build(), FormModel.class); 95 | } 96 | 97 | public static TextModel toTextModel(TestCase testCase) { 98 | String textData = null; 99 | if (Objects.nonNull(testCase.getRequests())) { 100 | if (testCase.getRequests() instanceof List || testCase.getRequests() instanceof Collection) { 101 | textData = JSONSerializerUtil.serialize(testCase.getRequests()); 102 | } else { 103 | textData = String.valueOf(testCase.getRequests()); 104 | } 105 | } 106 | return JSONSerializerUtil.copy(TextModel.builder() 107 | .headers(testCase.getHeaders()) 108 | .name(testCase.getName()) 109 | .url(testCase.getUrl()) 110 | .description(testCase.getDescription()) 111 | .method(testCase.getMethod()) 112 | .textData(textData) 113 | .validate(testCase.getValidate()) 114 | .setup(testCase.getSetup()) 115 | .saveMethod(testCase.getSaveMethod()) 116 | .saveClass(testCase.getSaveClass()) 117 | .saveGlobal(testCase.getSaveGlobal()) 118 | .saveThread(testCase.getSaveThread()) 119 | .onFailure(testCase.getOnFailure()) 120 | .teardown(testCase.getTeardown()) 121 | .build(), TextModel.class); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/ThreadPool.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.concurrent.*; 6 | 7 | /** 8 | * 线程池工具 9 | * 执行顺序:corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略 10 | */ 11 | public class ThreadPool { 12 | /** 13 | * 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。 14 | * 当前机器核数 * 2 15 | */ 16 | public static final int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 ; 17 | 18 | /** 19 | * 线程数的上限 20 | */ 21 | public static final int maximumPoolSize = 50; 22 | 23 | /** 24 | * 超过corePoolSize的线程的idle时长, 25 | * 超过这个时间,多余的线程会被回收 26 | * 单位unit 27 | */ 28 | public static final long keepAliveTime = 60; 29 | 30 | public static final TimeUnit unit = TimeUnit.SECONDS; 31 | 32 | /** 33 | * 任务的排队队列 34 | * ArrayBlockingQueue 35 | * 有界队列 36 | * 先进先出队列(队列头的是最先进队的元素;队列尾的是最后进队的元素) 37 | * 有界队列(即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作) 38 | * 队列不支持空元素 39 | *

40 | * LinkedBlockingQueue 41 | * 如果不指定容量,默认为Integer.MAX_VALUE,也就是无界队列 42 | *

43 | * PriorityBlockingQueue 44 | * 是一个基于数组实现的线程安全的无界队列 45 | *

46 | * SynchronizedQueue 47 | * 无界的FIFO同步队列 48 | */ 49 | public static final BlockingQueue workQueue = new LinkedBlockingQueue<>(20); 50 | 51 | /** 52 | * 拒绝策略 53 | * AbortPolicy 抛出RejectedExecutionException 54 | * DiscardPolicy 什么也不做,直接忽略 55 | * DiscardOldestPolicy 丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置 56 | * CallerRunsPolicy 直接由提交任务者执行这个任务 57 | */ 58 | public static final RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy(); 59 | 60 | public static ThreadPoolExecutor threadPool; 61 | 62 | /** 63 | * 无返回值直接执行 64 | * 65 | * @param runnable 66 | */ 67 | public static void execute(Runnable runnable) { 68 | createThreadPool().execute(runnable); 69 | } 70 | 71 | /** 72 | * 返回值直接执行,获取返回值 Future.get() 73 | * 74 | * @param callable 75 | */ 76 | public static Future submit(Callable callable) { 77 | return createThreadPool().submit(callable); 78 | } 79 | 80 | /** 81 | * 多个task提交 82 | * @param callables 83 | * @param 84 | * @return 85 | */ 86 | public static List> inVokeAll(Collection> callables){ 87 | try { 88 | return createThreadPool().invokeAll(callables); 89 | } catch (InterruptedException e) { 90 | e.printStackTrace(); 91 | } 92 | return null; 93 | } 94 | 95 | private static ThreadPoolExecutor createThreadPool() { 96 | if (threadPool != null && !threadPool.isShutdown()) { 97 | return threadPool; 98 | } else { 99 | synchronized (ThreadPool.class) { 100 | if (threadPool == null || threadPool.isShutdown()) { 101 | threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, 102 | workQueue, r -> { 103 | Thread thread = new Thread(r); 104 | thread.setUncaughtExceptionHandler(new ThreadUncaughtExceptionHandler()); 105 | return thread; 106 | }, handler); 107 | } 108 | System.out.println(threadPool.getActiveCount() + ","+ threadPool.getCorePoolSize()+ ","+ threadPool.getMaximumPoolSize() + ","+ threadPool.getPoolSize() + ","+ threadPool.getTaskCount()); 109 | return threadPool; 110 | } 111 | } 112 | } 113 | 114 | public static void shutdown() { 115 | if(threadPool != null && !threadPool.isShutdown()) { 116 | threadPool.shutdown(); 117 | } 118 | } 119 | 120 | /** 121 | * 关闭后所有任务是否都已完成 122 | * while (!isTerminated()) 123 | */ 124 | public static void isTerminated(){ 125 | threadPool.isTerminated(); 126 | } 127 | 128 | 129 | 130 | public static void main(String[] args) { 131 | ThreadPool.execute(() -> System.out.println(Thread.currentThread().getName())); 132 | shutdown(); 133 | System.out.println(ThreadPool.threadPool.isShutdown()); 134 | ThreadPool.execute(() -> System.out.println(Thread.currentThread().getName())); 135 | System.out.println(ThreadPool.threadPool.isShutdown()); 136 | // for (int i = 0; i < 10; i++) { 137 | //// ThreadPool.execute(() -> System.out.println(Thread.currentThread().getName())); 138 | // 139 | // List> list = new ArrayList<>(); 140 | //// list.add(ThreadPool.submit(() -> { 141 | //// System.out.println(Thread.currentThread().getName()); 142 | //// return "ok"; 143 | //// })); 144 | // 145 | // list.forEach(l -> { 146 | // try { 147 | // System.out.println(l.get()); 148 | // } catch (InterruptedException e) { 149 | // e.printStackTrace(); 150 | // } catch (ExecutionException e) { 151 | // e.printStackTrace(); 152 | // } 153 | // }); 154 | // } 155 | } 156 | } -------------------------------------------------------------------------------- /src/main/java/com/ly/core/utils/ThreadUncaughtExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.ly.core.utils; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2019/12/18 15:17. 8 | */ 9 | @Slf4j 10 | public class ThreadUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { 11 | @Override 12 | public void uncaughtException(Thread t, Throwable e) { 13 | log.error("线程:" + t.getName() + "异常", e); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ly/example/TestExpressionOne.java: -------------------------------------------------------------------------------- 1 | package com.ly.example; 2 | 3 | import com.ly.core.annotation.ApiAfterTest; 4 | import com.ly.core.annotation.ApiBeforeTest; 5 | import com.ly.core.base.BaseTestCase; 6 | import com.ly.core.utils.ContextDataStorage; 7 | import io.qameta.allure.Description; 8 | import io.qameta.allure.Feature; 9 | import io.qameta.allure.Severity; 10 | import io.qameta.allure.SeverityLevel; 11 | import io.qameta.allure.Story; 12 | import org.testng.annotations.Test; 13 | 14 | /** 15 | * @Author: luoy 16 | * @Date: 2019/8/7 15:17. 17 | */ 18 | @Feature("ces") 19 | @Story("测试one") 20 | public class TestExpressionOne extends BaseTestCase { 21 | @ApiBeforeTest 22 | public void testTest() { 23 | System.out.println("===================TestExpressionOne- beforeTest ============="); 24 | } 25 | 26 | @ApiAfterTest 27 | public void testTest1() { 28 | System.out.println("===================TestExpressionOne- afterTest ============="); 29 | } 30 | 31 | 32 | 33 | @Severity(SeverityLevel.CRITICAL) 34 | @Description("thread test") 35 | @Test(groups = "test-groups-1", description = "test") 36 | public void testMethodThree() { 37 | String name = Thread.currentThread().getName(); 38 | ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 39 | System.out.println("method testMethodThree thread id:" + name); 40 | } 41 | // @Severity(SeverityLevel.CRITICAL) 42 | // @Description("thread test") 43 | // @Test(groups = "test-groups-1", description = "test") 44 | // public void testMethodThree1() { 45 | // String name = Thread.currentThread().getName(); 46 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 47 | // System.out.println("method testMethodThree thread id:" + name); 48 | // } 49 | // @Severity(SeverityLevel.CRITICAL) 50 | // @Description("thread test") 51 | // @Test(groups = "test-groups-1", description = "test") 52 | // public void testMethodThree2() { 53 | // String name = Thread.currentThread().getName(); 54 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 55 | // System.out.println("method testMethodThree thread id:" + name); 56 | // } 57 | // @Severity(SeverityLevel.CRITICAL) 58 | // @Description("thread test") 59 | // @Test(groups = "test-groups-1", description = "test") 60 | // public void testMethodThree3() { 61 | // String name = Thread.currentThread().getName(); 62 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 63 | // System.out.println("method testMethodThree thread id:" + name); 64 | // } 65 | // @Severity(SeverityLevel.CRITICAL) 66 | // @Description("thread test") 67 | // @Test(groups = "test-groups-1", description = "test") 68 | // public void testMethodThree4() { 69 | // String name = Thread.currentThread().getName(); 70 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 71 | // System.out.println("method testMethodThree thread id:" + name); 72 | // } 73 | // @Severity(SeverityLevel.CRITICAL) 74 | // @Description("thread test") 75 | // @Test(groups = "test-groups-1", description = "test") 76 | // public void testMethodThree5() { 77 | // String name = Thread.currentThread().getName(); 78 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 79 | // System.out.println("method testMethodThree thread id:" + name); 80 | // } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/ly/example/TestExpressionRetry.java: -------------------------------------------------------------------------------- 1 | package com.ly.example; 2 | 3 | import com.ly.core.annotation.RetryCount; 4 | import io.qameta.allure.Description; 5 | import io.qameta.allure.Feature; 6 | import io.qameta.allure.Severity; 7 | import io.qameta.allure.SeverityLevel; 8 | import io.qameta.allure.Story; 9 | import org.testng.Assert; 10 | import org.testng.annotations.Test; 11 | 12 | /** 13 | * @Author: luoy 14 | * @Date: 2019/8/7 17:14. 15 | */ 16 | @Feature("ces") 17 | @Story("测试retry") 18 | @RetryCount(count = 4) 19 | public class TestExpressionRetry { 20 | 21 | @Severity(SeverityLevel.CRITICAL) 22 | @Description("thread test") 23 | @Test(groups = "test-groups-1") 24 | @RetryCount(count = 2) 25 | public void testRetryOne() { 26 | long id = Thread.currentThread().getId(); 27 | System.out.println("method testMethodThree thread id:" + id); 28 | Assert.assertNotNull(null); 29 | } 30 | 31 | @Severity(SeverityLevel.CRITICAL) 32 | @Description("thread test") 33 | @Test(groups = "test-groups-2") 34 | public void testRetryTwo() { 35 | long id = Thread.currentThread().getId(); 36 | System.out.println("method testMethodThree thread id:" + id); 37 | Assert.assertNotNull(null); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/ly/example/TestExpressionTwo.java: -------------------------------------------------------------------------------- 1 | package com.ly.example; 2 | 3 | import com.ly.core.annotation.ApiAfterTest; 4 | import com.ly.core.annotation.ApiBeforeTest; 5 | import com.ly.core.base.BaseTestCase; 6 | import com.ly.core.utils.ContextDataStorage; 7 | import io.qameta.allure.Description; 8 | import io.qameta.allure.Feature; 9 | import io.qameta.allure.Severity; 10 | import io.qameta.allure.SeverityLevel; 11 | import io.qameta.allure.Story; 12 | import org.testng.annotations.Test; 13 | 14 | /** 15 | * @Author: luoy 16 | * @Date: 2019/8/7 15:17. 17 | */ 18 | @Feature("ces") 19 | @Story("测试two") 20 | public class TestExpressionTwo extends BaseTestCase { 21 | 22 | @ApiBeforeTest 23 | public void testTest() { 24 | System.out.println("===================TestExpressionTwo- beforeTest ============="); 25 | } 26 | 27 | @ApiAfterTest 28 | public void testTest1() { 29 | System.out.println("===================TestExpressionTwo- afterTest ============="); 30 | } 31 | 32 | 33 | @Severity(SeverityLevel.CRITICAL) 34 | @Description("thread test") 35 | @Test(groups = "test-groups-2") 36 | public void testMethodOne() { 37 | String name = Thread.currentThread().getName(); 38 | System.out.println("method testMethodOne thread id:" + name); 39 | } 40 | 41 | @Severity(SeverityLevel.CRITICAL) 42 | @Description("thread test") 43 | @Test(groups = "test-groups-1") 44 | public void testMethodTwo() { 45 | String name = Thread.currentThread().getName(); 46 | ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 47 | System.out.println("method testMethodTwo thread id:" + name); 48 | } 49 | // @Severity(SeverityLevel.CRITICAL) 50 | // @Description("thread test") 51 | // @Test(groups = "test-groups-1") 52 | // public void testMethodTwo1() { 53 | // String name = Thread.currentThread().getName(); 54 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 55 | // System.out.println("method testMethodTwo thread id:" + name); 56 | // } 57 | // @Severity(SeverityLevel.CRITICAL) 58 | // @Description("thread test") 59 | // @Test(groups = "test-groups-1") 60 | // public void testMethodTwo2() { 61 | // String name = Thread.currentThread().getName(); 62 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 63 | // System.out.println("method testMethodTwo thread id:" + name); 64 | // } 65 | // @Severity(SeverityLevel.CRITICAL) 66 | // @Description("thread test") 67 | // @Test(groups = "test-groups-1") 68 | // public void testMethodTwo3() { 69 | // String name = Thread.currentThread().getName(); 70 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 71 | // System.out.println("method testMethodTwo thread id:" + name); 72 | // } 73 | // @Severity(SeverityLevel.CRITICAL) 74 | // @Description("thread test") 75 | // @Test(groups = "test-groups-1") 76 | // public void testMethodTwo4() { 77 | // String name = Thread.currentThread().getName(); 78 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 79 | // System.out.println("method testMethodTwo thread id:" + name); 80 | // } 81 | // @Severity(SeverityLevel.CRITICAL) 82 | // @Description("thread test") 83 | // @Test(groups = "test-groups-1") 84 | // public void testMethodTwo5() { 85 | // String name = Thread.currentThread().getName(); 86 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 87 | // System.out.println("method testMethodTwo thread id:" + name); 88 | // } 89 | // @Severity(SeverityLevel.CRITICAL) 90 | // @Description("thread test") 91 | // @Test(groups = "test-groups-1") 92 | // public void testMethodTwo6() { 93 | // String name = Thread.currentThread().getName(); 94 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 95 | // System.out.println("method testMethodTwo thread id:" + name); 96 | // } 97 | // @Severity(SeverityLevel.CRITICAL) 98 | // @Description("thread test") 99 | // @Test(groups = "test-groups-1") 100 | // public void testMethodTwo7() { 101 | // String name = Thread.currentThread().getName(); 102 | // ContextDataStorage.getInstance().setThreadAttribute("testSuite", name); 103 | // System.out.println("method testMethodTwo thread id:" + name); 104 | // } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/ly/failurecallback/TestFailHandle.java: -------------------------------------------------------------------------------- 1 | package com.ly.failurecallback; 2 | 3 | import com.ly.core.base.BaseFailHandle; 4 | 5 | /** 6 | * @Author: luoy 7 | * @Date: 2020/5/6 14:20. 8 | */ 9 | public class TestFailHandle implements BaseFailHandle { 10 | 11 | @Override 12 | public void handle(Object o) { 13 | System.out.println("this is onFailHandle"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ly/headersfilter/RestAssuredLogFilter.java: -------------------------------------------------------------------------------- 1 | package com.ly.headersfilter; 2 | 3 | import com.ly.core.utils.JSONSerializerUtil; 4 | import io.restassured.filter.Filter; 5 | import io.restassured.filter.FilterContext; 6 | import io.restassured.http.Headers; 7 | import io.restassured.response.Response; 8 | import io.restassured.specification.FilterableRequestSpecification; 9 | import io.restassured.specification.FilterableResponseSpecification; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.util.Map; 13 | 14 | /** 15 | * @Author: luoy 16 | * @Date: 2019/8/16 17:11. 17 | */ 18 | @Slf4j 19 | public class RestAssuredLogFilter implements Filter { 20 | @Override 21 | public Response filter(FilterableRequestSpecification requestSpec, FilterableResponseSpecification responseSpec, FilterContext ctx) { 22 | Response response = ctx.next(requestSpec, responseSpec); 23 | 24 | String url = requestSpec.getURI(); 25 | String method = requestSpec.getMethod(); 26 | Headers headers = requestSpec.getHeaders(); 27 | 28 | Map reqFormParams = requestSpec.getFormParams(); 29 | String requestsStr = reqFormParams != null && reqFormParams.size() > 0 ? JSONSerializerUtil.serialize(reqFormParams) 30 | : JSONSerializerUtil.serialize(requestSpec.getBody()); 31 | String responseStr = response.asString(); 32 | log.info("url: [{}]", url); 33 | log.info("method: [{}]", method); 34 | log.info("requestHeaders: {}", headers.asList()); 35 | log.info("request: [{}]", requestsStr); 36 | log.info("response: [{}]", responseStr); 37 | log.info("===========================done============================"); 38 | return response; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ly/headersfilter/TestHeadersFilter.java: -------------------------------------------------------------------------------- 1 | package com.ly.headersfilter; 2 | 3 | import com.ly.core.listener.HeadersFilterAdapter; 4 | import com.ly.core.utils.MapTool; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * @Author: luoy 11 | * @Date: 2019/8/16 17:11. 12 | */ 13 | @Slf4j 14 | public class TestHeadersFilter extends HeadersFilterAdapter { 15 | @Override 16 | public Map defHeaders() { 17 | return MapTool.builder() 18 | .put("Content-Type", "application/json;charset=utf-8") 19 | .build(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ly/plugin/PluginSupport.java: -------------------------------------------------------------------------------- 1 | package com.ly.plugin; 2 | import com.ly.api.TestApiClient; 3 | import com.ly.core.base.Response; 4 | import com.ly.core.exception.BizException; 5 | import com.ly.core.parse.BaseModel; 6 | import com.ly.core.parse.FormModel; 7 | import com.ly.core.parse.JsonModel; 8 | import com.ly.core.parse.TestCase; 9 | import com.ly.core.parse.YmlParse; 10 | import com.ly.core.redis.RedisService; 11 | import com.ly.core.utils.AssertUtils; 12 | import com.ly.core.utils.ContextDataStorage; 13 | import com.ly.core.utils.JSONSerializerUtil; 14 | import com.ly.core.utils.TestCase2BaseModel; 15 | import com.ly.core.utils.Utils; 16 | import org.apache.commons.lang3.StringUtils; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.stereotype.Component; 19 | 20 | import javax.annotation.Resource; 21 | import java.math.BigDecimal; 22 | import java.util.Map; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | /** 26 | * 该类所有方法都可加入data.yml中validate中的plugin 27 | * plugin支持jsonpath与${} 28 | * @Author: luoy 29 | * @Date: 2020/4/26 16:13. 30 | */ 31 | @Component 32 | public class PluginSupport { 33 | @Autowired 34 | private TestApiClient testApiClient; 35 | 36 | private ContextDataStorage saveData = ContextDataStorage.getInstance(); 37 | 38 | 39 | public boolean isTest(){ 40 | return false; 41 | } 42 | 43 | public void createTimestamp() { 44 | saveData.setMethodAttribute("timestamp", String.valueOf(System.currentTimeMillis())); 45 | } 46 | 47 | public void setUptest(BaseModel model) { 48 | System.out.println("setUptest:" + model.getRequests()); 49 | } 50 | 51 | public void teardowm(String arg1, String arg2) { 52 | System.out.println("teardowm:" + arg1 + "," + arg2); 53 | } 54 | 55 | public void teardowm1(Response response, String arg2) { 56 | System.out.println("teardowm1:" + arg2); 57 | System.out.println("teardowm1-url:" + response.getRequests().getUrl()); 58 | } 59 | 60 | /** 61 | * 生成length位随机数,存入缓存 62 | * @param length 63 | */ 64 | public void randomInt(int length) { 65 | saveData.setAttribute("Random10", Utils.getRandomInt(length)); 66 | } 67 | 68 | /** 69 | * 生成orderid,存入缓存 70 | */ 71 | public void randomAmapOrderId() { 72 | saveData.setMethodAttribute("randomOrderId", Utils.getRandomInt(10)); 73 | } 74 | 75 | /** 76 | * 生成当前localDateTime,存入缓存 77 | */ 78 | public void localDateTime() { 79 | saveData.setAttribute("LocalDateTime", Utils.getLocalDataTime()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/ly/processorcallback/TestProcessorCallback.java: -------------------------------------------------------------------------------- 1 | package com.ly.processorcallback; 2 | 3 | import com.ly.core.base.BaseProcessorHandle; 4 | 5 | /** 6 | * test 7 | * @Author: luoy 8 | * @Date: 2020/5/7 13:18. 9 | */ 10 | public class TestProcessorCallback implements BaseProcessorHandle { 11 | @Override 12 | public void processor(String o) { 13 | System.out.println("this is test Processor"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ly/testcase/ExampleApiTestCase.java: -------------------------------------------------------------------------------- 1 | package com.ly.testcase; 2 | 3 | import com.ly.api.BaseTestApiTestCase; 4 | import com.ly.core.annotation.ApiAfterClass; 5 | import com.ly.core.annotation.ApiAfterMethod; 6 | import com.ly.core.annotation.ApiAfterSuite; 7 | import com.ly.core.annotation.ApiBeforeClass; 8 | import com.ly.core.annotation.ApiBeforeMethod; 9 | import com.ly.core.annotation.ApiBeforeSuite; 10 | import com.ly.core.annotation.DataModel; 11 | import com.ly.core.parse.BaseModel; 12 | import com.ly.core.parse.MultipleModel; 13 | import io.qameta.allure.Description; 14 | import io.qameta.allure.Severity; 15 | import io.qameta.allure.SeverityLevel; 16 | import io.qameta.allure.Story; 17 | import org.testng.annotations.Test; 18 | 19 | /** 20 | * @Author: luoy 21 | * @Date: 2019/8/22 10:17. 22 | */ 23 | @Story("登录模块接口") 24 | public class ExampleApiTestCase extends BaseTestApiTestCase { 25 | 26 | @DataModel(value = {"login-not-found"}, 27 | format = DataModel.Format.SINGLE, 28 | path = {"example.yml"}) 29 | @ApiBeforeClass 30 | public void beforeClass(BaseModel model) { 31 | System.out.println("===========beforeClass============: " + model); 32 | } 33 | 34 | @DataModel(value = {"login-not-found"}, 35 | format = DataModel.Format.SINGLE, 36 | path = {"example.yml"}) 37 | @ApiBeforeMethod 38 | public void beforeMethod(BaseModel model) { 39 | System.out.println("===========beforeMethod============: " + model); 40 | } 41 | 42 | @DataModel(value = {"login-not-found"}, 43 | format = DataModel.Format.SINGLE, 44 | path = {"example.yml"}) 45 | @ApiBeforeSuite 46 | public void beforeSuite(BaseModel model) { 47 | System.out.println("=============beforeSuite==========" + model); 48 | } 49 | 50 | @DataModel(value = {"login-not-found"}, 51 | format = DataModel.Format.SINGLE, 52 | path = {"example.yml"}) 53 | @ApiAfterSuite 54 | public void afterSuite(BaseModel model) { 55 | System.out.println("=============afterSuite==========" + model); 56 | } 57 | 58 | @DataModel(value = {"login-not-found"}, 59 | format = DataModel.Format.SINGLE, 60 | path = {"example.yml"}) 61 | @ApiAfterClass 62 | public void afterClass(BaseModel model) { 63 | System.out.println("=============afterClass==========" + model); 64 | } 65 | 66 | @DataModel(value = {"login-not-found"}, 67 | format = DataModel.Format.SINGLE, 68 | path = {"example.yml"}) 69 | @ApiAfterMethod 70 | public void afterMethod(BaseModel model) { 71 | System.out.println("=============afterMethod==========" + model); 72 | } 73 | 74 | 75 | 76 | @Severity(SeverityLevel.CRITICAL) 77 | @Description("登录") 78 | @Test(groups = "example") 79 | @DataModel(value = {"login-not-found"}, 80 | format = DataModel.Format.SINGLE, 81 | path = {"example.yml"}) 82 | public void login(BaseModel model) { 83 | testApiClient.doHttp(model).auto(); 84 | } 85 | 86 | @Severity(SeverityLevel.CRITICAL) 87 | @Description("获取司机信息") 88 | @Test(groups = "example") 89 | @DataModel(format = DataModel.Format.MULTIPLE, path = "example.yml") 90 | public void test(MultipleModel model) { 91 | testApiClient.doHttp("login-not-found").auto(); 92 | 93 | testApiClient.doHttp("GetSeriesGames").auto(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/resources/allure.properties: -------------------------------------------------------------------------------- 1 | allure.results.directory=target/allure-results -------------------------------------------------------------------------------- /src/main/resources/application-qa.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | httpurl: 3 | test.url: http://appapi2.gamersky.com 4 | datasource: 5 | # 配置多数据源 6 | qa: 7 | driverClassName: com.mysql.cj.jdbc.Driver 8 | jdbc-url: jdbc:mysql://xxxxxxxx?characterEncoding=utf-8 9 | username: xx 10 | password: xxxxxxxxxxx 11 | uat: 12 | driverClassName: com.mysql.cj.jdbc.Driver 13 | jdbc-url: jdbc:mysql://xxxxxxxx?characterEncoding=utf-8 14 | username: xx 15 | password: xxxxxxxxxxx 16 | 17 | redis: 18 | # Redis服务器集群地址 19 | cluster: 20 | nodes: 127.0.0.1:6379,127.0.0.1:6380 21 | timeout: 5000 22 | password: 123 23 | standalone: 24 | # 连接那个数据库 25 | database: 1 26 | # Redis服务器ip 27 | host: 127.0.0.1 28 | # Redis服务器连接端口 29 | port: 6379 30 | # Redis服务器连接密码(默认为空) 31 | password: 1234 32 | # 连接超时时间(毫秒) 33 | timeout: 5000 -------------------------------------------------------------------------------- /src/main/resources/application-release.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | httpurl: 3 | test.url: https://test-release.com.cn/test-api 4 | datasource: 5 | # 配置多数据源 6 | qa: 7 | driverClassName: com.mysql.cj.jdbc.Driver 8 | jdbc-url: jdbc:mysql://xxxxxxxx?characterEncoding=utf-8 9 | username: xx 10 | password: xxxxxxxxxxx 11 | uat: 12 | driverClassName: com.mysql.cj.jdbc.Driver 13 | jdbc-url: jdbc:mysql://xxxxxxxx?characterEncoding=utf-8 14 | username: xx 15 | password: xxxxxxxxxxx 16 | 17 | redis: 18 | # Redis服务器集群地址 19 | cluster: 20 | nodes: 127.0.0.1:6379,127.0.0.1:6380 21 | timeout: 5000 22 | password: 123 23 | standalone: 24 | # 连接那个数据库 25 | database: 1 26 | # Redis服务器ip 27 | host: 127.0.0.1 28 | # Redis服务器连接端口 29 | port: 6379 30 | # Redis服务器连接密码(默认为空) 31 | password: 1234 32 | # 连接超时时间(毫秒) 33 | timeout: 5000 -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: #env# 4 | autoconfigure: 5 | #因为多redis数据源, 排除掉springBoot自动装配redisTemplate 6 | exclude: 7 | - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration 8 | - org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration 9 | 10 | notification: 11 | dingtalk: 12 | # 群机器人url 13 | url: https://oapi.dingtalk.com/robot/send?access_token=185d523a8527280a24936cd2cc716c980a793d7ac2c75b7dc2a907332test 14 | mail: 15 | # 发送邮件地址 16 | sendAddress: test@sina.com 17 | # 发送邮件账号 18 | sendAccount: test@sina.com 19 | # 发送邮件密码(授权码) 20 | sendPassword: 75470cca2d1ctest 21 | # 收件人地址多个用,分隔 22 | recipientAddress: 123321123@qq.com,test@163.com 23 | # 传输协议,默认SMTP 24 | protocol: smtp 25 | # 协议服务器地址 26 | host: smtp.sina.com 27 | 28 | #提醒渠道 0:不提醒, 1:钉钉, 2:邮件 9:所有 29 | channel: 0 30 | 31 | #错误重试 32 | retry: 33 | #是否错误重试 34 | #如果方法和类上没增加@RetryConut注解,则取该配置 35 | #优先级: 36 | # 1.取方法@RetryConut重试次数 37 | # 2.取类上@RetryConut重试次数 38 | # 3.取该配置 39 | retryable: false 40 | #重试次数 41 | count: 1 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/main/resources/example.yml: -------------------------------------------------------------------------------- 1 | testCase: 2 | - name: GetSeriesGames 3 | description: 查询系列游戏 4 | type: json 5 | url: /v5/GetSeriesGames 6 | method: POST 7 | setup: 8 | - method: createTimestamp 9 | - method: setUptest 10 | args: ${request} 11 | headers: 12 | requests: 13 | { 14 | "deviceType":"iPhone12,1", 15 | "deviceId":"7423619B-5E7C-46E9-A884-58F7ACDF4B3", 16 | "os":"iOS", 17 | "osVersion":"13.6", 18 | "app":"GSApp(iOS)", 19 | "appVersion":"5.12.3", 20 | "request":{ 21 | "gameId":"343620", 22 | "cacheMinutes":10 23 | } 24 | } 25 | validate: 26 | notNull: [result] 27 | eq: [errorCode: 0] 28 | teardown: 29 | - method: teardowm 30 | args: args1111, args222 31 | 32 | 33 | - name: login-not-found 34 | description: 用户名不存在 35 | type: json 36 | url: /v2/TwoLogin 37 | method: POST 38 | headers: 39 | requests: 40 | { 41 | "deviceType":"iPhone12,1", 42 | "deviceId":"7423619B-5E7C-46E9-A884-58F7ACDF4B3", 43 | "os":"iOS", 44 | "osVersion":"13.6", 45 | "app":"GSApp(iOS)", 46 | "appVersion":"5.12.3", 47 | "request":{ 48 | "userInfo":"13000000001", 49 | "passWord":"aaaaa", 50 | "veriCode": "" 51 | } 52 | } 53 | validate: 54 | eq: ["errorCode": 2] 55 | parameters: 56 | - name: login-passWord-is-null 57 | description: 密码不能为空 58 | requests: 59 | "request": 60 | { 61 | "userInfo":"13000000001", 62 | "passWord":"", 63 | "veriCode": "" 64 | } 65 | validate: 66 | eq: ["errorCode": 2] -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ${printPattern} 11 | UTF-8 12 | 13 | 14 | 15 | 16 | 17 | ${LOG_HOME}/api-test-info.log 18 | 19 | ${LOG_HOME}/api-test.%d{yyyy-MM-dd}.log.zip 20 | 30 21 | 3GB 22 | 23 | 24 | ${outPutPattern} 25 | UTF-8 26 | 27 | 28 | 29 | 30 | 31 | ERROR 32 | 33 | ${LOG_HOME}/api-test-error.log 34 | 35 | ${LOG_HOME}/api-test-error.%d{yyyy-MM-dd}.log.zip 36 | 30 37 | 3GB 38 | 39 | 40 | ${outPutPattern} 41 | UTF-8 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /testng-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /testng-thread.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------