diff --git a/docs/classes.puml b/docs/classes.puml index 054b48c3..b1cf0b42 100644 --- a/docs/classes.puml +++ b/docs/classes.puml @@ -176,6 +176,10 @@ package core { class DslWhileController extends DslController + class ForLoopController extends DslController { + int count + } + DslIfController --> PropertyScriptBuilder DslWhileController --> PropertyScriptBuilder diff --git a/docs/guide/README.md b/docs/guide/README.md index 3f617fd7..167d1bff 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -884,11 +884,13 @@ ifController(s -> s.vars.get("ACCOUNT_ID") != null, Using java code (lambdas) will only work with embedded JMeter engine (no support for saving to JMX and running it in JMeter GUI, or running it with BlazeMeter). Use the first option to avoid such limitations. ::: -Check [DslIfController](../../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslIfController.java) for more details. +Check [DslIfController](../../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslIfController.java) and [JMeter Component documentation](https://jmeter.apache.org/usermanual/component_reference.html#If_Controller) for more details. ### Loops -If at any time you want to execute a given part of a test plan, inside a thread iteration, while a condition is met, then you can use `whileController` like in following example: +#### While Controller + +If at any time you want to execute a given part of a test plan, inside a thread iteration, while a condition is met, then you can use `whileController` (internally using [JMeter While Controller](https://jmeter.apache.org/usermanual/component_reference.html#While_Controller)) like in following example: ```java import static org.assertj.core.api.Assertions.assertThat; @@ -956,6 +958,44 @@ Default name for while controller, when not specified, is `while`. Check [DslWhileController](../../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslWhileController.java) for more details. +#### For Loop Controller + +In simple scenarios where you just want to execute a fixed number of times, within a thread group iteration, a given part of the test plan, you can just use `forLoopController` (which uses [JMeter Loop Controller component](https://jmeter.apache.org/usermanual/component_reference.html#Loop_Controller)) as in following example: + +```java +import static org.assertj.core.api.Assertions.assertThat; +import static us.abstracta.jmeter.javadsl.JmeterDsl.*; + +import java.io.IOException; +import java.time.Duration; +import org.junit.jupiter.api.Test; +import us.abstracta.jmeter.javadsl.core.TestPlanStats; + +public class PerformanceTest { + + @Test + public void testPerformance() throws IOException { + TestPlanStats stats = testPlan( + threadGroup(2, 10, + forLoopController(5, + httpSampler("http://my.service/accounts") + ) + ) + ).run(); + assertThat(stats.overall().sampleTimePercentile99()).isLessThan(Duration.ofSeconds(5)); + } + +} +``` + +This will result in 10 * 5 = 50 requests to the given URL for each thread in the thread group. + +::: tip +JMeter automatically generates a variable `__jm____idx` with the current index of while iteration (starting with 0) which you can use in children elements. +::: + +Check [ForLoopController](../../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopController.java) for more details. + ### Provide Request Parameters Programmatically per Request With the standard DSL you can provide static values to request parameters, such as a body. However, you may also want to be able to modify your requests for each call. This is common in cases where your request creates something that must have unique values. diff --git a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/JmeterDsl.java b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/JmeterDsl.java index 39a2c22b..8d71d633 100644 --- a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/JmeterDsl.java +++ b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/JmeterDsl.java @@ -9,11 +9,13 @@ import us.abstracta.jmeter.javadsl.core.DslTestPlan.TestPlanChild; import us.abstracta.jmeter.javadsl.core.DslThreadGroup; import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild; +import us.abstracta.jmeter.javadsl.core.PropertyScriptBuilder.PropertyScript; import us.abstracta.jmeter.javadsl.core.assertions.DslResponseAssertion; import us.abstracta.jmeter.javadsl.core.configs.DslCsvDataSet; import us.abstracta.jmeter.javadsl.core.controllers.DslIfController; import us.abstracta.jmeter.javadsl.core.controllers.DslTransactionController; import us.abstracta.jmeter.javadsl.core.controllers.DslWhileController; +import us.abstracta.jmeter.javadsl.core.controllers.ForLoopController; import us.abstracta.jmeter.javadsl.core.controllers.PercentController; import us.abstracta.jmeter.javadsl.core.listeners.DslViewResultsTree; import us.abstracta.jmeter.javadsl.core.listeners.HtmlReporter; @@ -28,7 +30,6 @@ import us.abstracta.jmeter.javadsl.core.preprocessors.DslJsr223PreProcessor.PreProcessorVars; import us.abstracta.jmeter.javadsl.core.threadgroups.RpsThreadGroup; import us.abstracta.jmeter.javadsl.core.timers.DslUniformRandomTimer; -import us.abstracta.jmeter.javadsl.core.PropertyScriptBuilder.PropertyScript; import us.abstracta.jmeter.javadsl.http.DslCacheManager; import us.abstracta.jmeter.javadsl.http.DslCookieManager; import us.abstracta.jmeter.javadsl.http.DslHttpSampler; @@ -326,6 +327,43 @@ public static DslWhileController whileController(String name, PropertyScript con return new DslWhileController(name, condition, Arrays.asList(children)); } + /** + * Builds a Loop Controller that allows to run specific number of times the given children in each + * thread group iteration. + * + * Eg: if a thread group iterates 3 times and the Loop Controller is configured to 5, then the + * children elements will run {@code 3*5=15} times for each thread. + * + * @param count specifies the number of times to execute the children elements in each thread + * group iteration. + * @param children contains the test plan elements to execute the given number of times in each + * thread group iteration. + * @return the controller instance for further configuration and usage. + * @see ForLoopController + * @since 0.27 + */ + public static ForLoopController forLoopController(int count, ThreadGroupChild... children) { + return new ForLoopController(null, count, Arrays.asList(children)); + } + + /** + * Same as {@link #forLoopController(int, ThreadGroupChild...)} but allowing to set a name which + * defines autogenerated variable created by JMeter containing iteration index. + * + * @param count specifies the number of times to execute the children elements in each thread + * group iteration. + * @param children contains the test plan elements to execute the given number of times in each + * thread group iteration. + * @return the controller instance for further configuration and usage. + * @see ForLoopController + * @see #forLoopController(int, ThreadGroupChild...) + * @since 0.27 + */ + public static ForLoopController forLoopController(String name, int count, + ThreadGroupChild... children) { + return new ForLoopController(name, count, Arrays.asList(children)); + } + /** * Builds a Percent Controller to execute children only a given percent of times. * diff --git a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslIfController.java b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslIfController.java index c58e84d9..da7546d6 100644 --- a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslIfController.java +++ b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslIfController.java @@ -4,8 +4,8 @@ import org.apache.jmeter.control.IfController; import org.apache.jmeter.control.gui.IfControllerPanel; import org.apache.jmeter.testelement.TestElement; -import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild; import us.abstracta.jmeter.javadsl.core.DslScriptBuilder; +import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild; import us.abstracta.jmeter.javadsl.core.PropertyScriptBuilder; import us.abstracta.jmeter.javadsl.core.PropertyScriptBuilder.PropertyScript; diff --git a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslWhileController.java b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslWhileController.java index 9b3735e4..b100e467 100644 --- a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslWhileController.java +++ b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/DslWhileController.java @@ -4,8 +4,8 @@ import org.apache.jmeter.control.WhileController; import org.apache.jmeter.control.gui.WhileControllerGui; import org.apache.jmeter.testelement.TestElement; -import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild; import us.abstracta.jmeter.javadsl.core.DslScriptBuilder; +import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild; import us.abstracta.jmeter.javadsl.core.PropertyScriptBuilder; import us.abstracta.jmeter.javadsl.core.PropertyScriptBuilder.PropertyScript; @@ -29,15 +29,15 @@ public DslWhileController(String name, String condition, List this.conditionBuilder = new PropertyScriptBuilder(condition); } - private static String solveName(String name) { - return name != null ? name : "while"; - } - public DslWhileController(String name, PropertyScript script, List children) { super(solveName(name), WhileControllerGui.class, children); this.conditionBuilder = new PropertyScriptBuilder(script); } + private static String solveName(String name) { + return name != null ? name : "while"; + } + @Override protected TestElement buildTestElement() { WhileController ret = new WhileController(); diff --git a/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopController.java b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopController.java new file mode 100644 index 00000000..6b862b6a --- /dev/null +++ b/jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopController.java @@ -0,0 +1,35 @@ +package us.abstracta.jmeter.javadsl.core.controllers; + +import java.util.List; +import org.apache.jmeter.control.LoopController; +import org.apache.jmeter.control.gui.ForeachControlPanel; +import org.apache.jmeter.testelement.TestElement; +import us.abstracta.jmeter.javadsl.core.DslThreadGroup.ThreadGroupChild; + +/** + * Allows running part of a test plan a given number of times inside one thread group iteration. + * + * Internally this uses JMeter Loop Controller. + * + * JMeter automatically creates a variable named {@code __jm____idx} which contains + * the index of the iteration starting with zero. + * + * @since 0.27 + */ +public class ForLoopController extends DslController { + + private final int count; + + public ForLoopController(String name, int count, List children) { + super(name != null ? name : "for", ForeachControlPanel.class, children); + this.count = count; + } + + @Override + protected TestElement buildTestElement() { + LoopController ret = new LoopController(); + ret.setLoops(count); + return ret; + } + +} diff --git a/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopControllerTest.java b/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopControllerTest.java new file mode 100644 index 00000000..ca1b8ecb --- /dev/null +++ b/jmeter-java-dsl/src/test/java/us/abstracta/jmeter/javadsl/core/controllers/ForLoopControllerTest.java @@ -0,0 +1,50 @@ +package us.abstracta.jmeter.javadsl.core.controllers; + +import static org.assertj.core.api.Assertions.assertThat; +import static us.abstracta.jmeter.javadsl.JmeterDsl.forLoopController; +import static us.abstracta.jmeter.javadsl.JmeterDsl.httpSampler; +import static us.abstracta.jmeter.javadsl.JmeterDsl.testPlan; +import static us.abstracta.jmeter.javadsl.JmeterDsl.threadGroup; + +import com.github.tomakehurst.wiremock.client.WireMock; +import org.junit.jupiter.api.Test; +import us.abstracta.jmeter.javadsl.JmeterDslTest; +import us.abstracta.jmeter.javadsl.core.TestPlanStats; + +public class ForLoopControllerTest extends JmeterDslTest { + + private static final int LOOP_ITERATIONS = 3; + + @Test + public void shouldExecuteExpectedNumberOfTimesWhenLoopControllerInPlan() throws Exception { + TestPlanStats stats = testPlan( + threadGroup(1, TEST_ITERATIONS, + forLoopController(LOOP_ITERATIONS, + httpSampler(wiremockUri) + ) + ) + ).run(); + assertThat(stats.overall().samplesCount()).isEqualTo(TEST_ITERATIONS * LOOP_ITERATIONS); + } + + @Test + public void shouldExposeJMeterVariableWithControllerNameWhenLoopControllerInPlan() + throws Exception { + String loopName = "COUNT"; + TestPlanStats stats = testPlan( + threadGroup(1, 1, + forLoopController(loopName, LOOP_ITERATIONS, + httpSampler(wiremockUri + "/${__jm__" + loopName + "__idx}") + ) + ) + ).run(); + verifyRequestMadeForPath("/0"); + verifyRequestMadeForPath("/1"); + verifyRequestMadeForPath("/2"); + } + + private void verifyRequestMadeForPath(String path) { + wiremockServer.verify(WireMock.getRequestedFor(WireMock.urlPathEqualTo(path))); + } + +}