| <?xml version="1.0"?> |
| <!-- |
| ~ Licensed to the Apache Software Foundation (ASF) under one or more |
| ~ contributor license agreements. See the NOTICE file distributed with |
| ~ this work for additional information regarding copyright ownership. |
| ~ The ASF licenses this file to you under the Apache License, Version 2.0 |
| ~ (the "License"); you may not use this file except in compliance with |
| ~ the License. You may obtain a copy of the License at |
| ~ |
| ~ http://www.apache.org/licenses/LICENSE-2.0 |
| ~ |
| ~ Unless required by applicable law or agreed to in writing, software |
| ~ distributed under the License is distributed on an "AS IS" BASIS, |
| ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| ~ See the License for the specific language governing permissions and |
| ~ limitations under the License. |
| --> |
| <!DOCTYPE document> |
| <document prev="build-jms-topic-test-plan.html" next="listeners.html" id="$Id$"> |
| |
| <properties> |
| <title>User's Manual: Building a Test Plan Programmatically</title> |
| </properties> |
| |
| <body> |
| |
| <section name="Building a Test Plan Programmatically" anchor="building"> |
| <note> |
| JMeter 5.6 brings experimental classes and methods to build test plans programmatically, so please feel free to provide your feedback. |
| </note> |
| <p>In this section, you will learn how to create a <a href="build-test-plan.html">Test Plan</a> with JMeter APIs.</p> |
| |
| <p>The Test Plan is a collection of elements arranged in a tree-like manner. However, in JMeter APIs, the elements do not form a tree. |
| Parent-child relationships are stored in a separate structure: <code>ListedHashTree</code>. |
| </p> |
| </section> |
| |
| <section name="Creating a plan with low-level APIs" anchor="low_level_api"> |
| <p>Let us create <code>Test Plan => Thread Group => Debug Sampler</code> plan</p> |
| |
| <p> |
| <source> |
| ListedHashTree root = new ListedHashTree(); // (1) |
| TestPlan testPlan = new TestPlan(); |
| ListedHashTree testPlanSubtree = root.add(testPlan); // (2) |
| TestPlan threadGroup = new ThreadGroup(); |
| threadGroup.setName("Search Order Thread Group"); |
| ListedHashTree threadGroupSubtree = testPlanSubtree.add(threadGroup); // (3) |
| DebugSampler debugSampler = new DebugSampler(); |
| threadGroupSubtree.add(debugSampler);</source> |
| <ul> |
| <li>Firstly, we create the tree at <code>(1)</code></li> |
| <li>Then we create elements, and add them to the tree in <code>(2)</code></li> |
| <li>Note how adding element returns the subtree, so we add <code>threadGroup</code> under <code>testPlan</code> in <code>(2)</code></li> |
| </ul> |
| <note> |
| Don't confuse <code>ListedHashTree</code> with <code>HashTree</code>. <code>HashTree</code> does not honour element order, so the generated elements might shuffle unexpectedly. |
| </note> |
| </p> |
| </section> |
| |
| <section name="Generating code from UI" anchor="generating_code"> |
| <p>To aid with creating code, JMeter implements <code>Copy Code</code> context action, so you could |
| generate code for any element in the plan. It would generate code for the element and its children.</p> |
| |
| <figure width="380" height="258" image="copy_code/http_sampler_copy_code.png">Copy Code context action</figure> |
| |
| <p>Here's the generated code (Kotlin DSL): |
| <source> |
| org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy::class { |
| props { |
| it[arguments] = org.apache.jmeter.config.Arguments().apply { |
| props { |
| it[arguments] = listOf( |
| org.apache.jmeter.protocol.http.util.HTTPArgument().apply { |
| props { |
| it[value] = "World" |
| it[metadata] = "=" |
| it[useEquals] = true |
| it[argumentName] = "user" |
| } |
| }, |
| org.apache.jmeter.protocol.http.util.HTTPArgument().apply { |
| props { |
| it[alwaysEncode] = true |
| it[value] = "test_value" |
| it[metadata] = "=" |
| it[useEquals] = true |
| it[argumentName] = "test" |
| } |
| }, |
| ) |
| it[name] = "User Defined Variables" |
| it[guiClass] = "org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel" |
| it[testClass] = "org.apache.jmeter.config.Arguments" |
| } |
| } |
| it[domain] = "example.com" |
| it[path] = "/api/v1/login" |
| it[method] = "GET" |
| it[followRedirects] = true |
| it[useKeepalive] = true |
| it[proxy.scheme] = "https" |
| it[proxy.host] = "localhost" |
| it[proxy.port] = "8080" |
| it[proxy.username] = "secret" |
| it[proxy.password] = "password1" |
| it[name] = "/login" |
| it[guiClass] = "org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui" |
| } |
| |
| org.apache.jmeter.extractor.RegexExtractor::class { |
| props { |
| it[guiClass] = "org.apache.jmeter.extractor.gui.RegexExtractorGui" |
| it[name] = "extract user id" |
| it[referenceName] = "regexVar" |
| it[regularExpression] = "hello\\s+?world" |
| it[template] = "\$1\$" |
| } |
| } |
| |
| org.apache.jmeter.protocol.http.control.HeaderManager::class { |
| props { |
| it[headers] = listOf( |
| org.apache.jmeter.protocol.http.control.Header().apply { |
| props { |
| it[headerName] = "Accept" |
| it[value] = "text/plain" |
| } |
| }, |
| org.apache.jmeter.protocol.http.control.Header().apply { |
| props { |
| it[headerName] = "User-Agent" |
| it[value] = "JMeter" |
| } |
| }, |
| org.apache.jmeter.protocol.http.control.Header().apply { |
| props { |
| it[headerName] = "X-JMeter-Thread" |
| it[value] = "Thread \${__threadNum}" |
| } |
| }, |
| ) |
| it[guiClass] = "org.apache.jmeter.protocol.http.gui.HeaderPanel" |
| it[name] = "HTTP Header Manager" |
| } |
| } |
| } |
| </source> |
| </p> |
| </section> |
| |
| <section name="Creating a plan with Kotlin DSL" anchor="treebuilder_kotlin_dsl"> |
| <p>JMeter 5.6 introduces Kotlin DSL which might make it easier to create and maintain test plans as the structure of the code |
| would resemble the structure of the generated test plan tree</p> |
| |
| <p> |
| <source> |
| import org.apache.jmeter.sampler.DebugSampler |
| import org.apache.jmeter.testelement.TestPlan |
| import org.apache.jmeter.threads.ThreadGroup |
| import org.apache.jmeter.treebuilder.dsl.testTree |
| |
| val root = testTree { // (1) |
| TestPlan::class { // (2) |
| ThreadGroup::class { |
| name = "Search Order Thread Group" |
| +DebugSampler::class // (3) |
| +DebugSampler() // (4) |
| } |
| } |
| }</source> |
| <ul> |
| <li>Firstly, we create a <code>TreeBuilder</code> at <code>(1)</code></li> |
| <li>Then we add elements to the tree in <code>(2)</code>, and populate its children</li> |
| <li>Note how adding element returns the subtree, so we add <code>threadGroup</code> under <code>testPlan</code> in <code>(2)</code></li> |
| <li>If no children needed, the element can be appended to the tree with a unary plus operator as in <code>(3)</code></li> |
| <li>By default, JMeter uses no-argument constructors to create elements, however, you can add <code>TestElement</code> instances to the tree as well, see <code>(4)</code></li> |
| </ul> |
| </p> |
| </section> |
| |
| <section name="Extending Kotlin DSL" anchor="extending_treebuilder_kotlin_dsl"> |
| <p>As you use the DSL for test plan generation, you might want to factor out the common patterns. |
| For instance, imagine you want factor out <code>Thread Group</code> creation so it always has a <code>Summariser</code> element.</p> |
| |
| <p> |
| <source> |
| import kotlin.time.Duration.Companion.seconds |
| import org.apache.jmeter.sampler.DebugSampler |
| import org.apache.jmeter.testelement.TestPlan |
| import org.apache.jmeter.threads.ThreadGroup |
| import org.apache.jmeter.treebuilder.dsl.testTree |
| |
| fun TreeBuilder.threadGroup( // (1) |
| name: String, |
| numThreads: Int = 10, |
| rampUp: Duration = 3.seconds, |
| body: Action<ThreadGroup> |
| ) { |
| ThreadGroup::class { // (2) |
| this.name = name |
| this.numThreads = numThreads |
| this.rampUp = rampUp.inWholeSeconds.toInt() |
| +Summariser::class |
| body(this) // (3) |
| } |
| } |
| |
| fun buildTree() { |
| val root = testTree { |
| TestPlan::class { |
| threadGroup(name = "Search Order Thread Group", rampUp = 1.seconds) { // (4) |
| +DebugSampler::class |
| } |
| } |
| }</source> |
| <ul> |
| <li>Firstly, you can factor test element creation logic as an extension function for <code>TreeBuilder</code> as in <code>(1)</code>. |
| It uses regular DSL to add an element (see <code>(2)</code>), and then it calls the lambda body in <code>(3)</code> to fill thread group children.</li> |
| <li>You can use the extension by calling it when you need it in the test plan, see <code>(4)</code></li> |
| <li>Note how named parameters, and default values keep the code readable</li> |
| </ul> |
| </p> |
| </section> |
| |
| <section name="Creating a plan with Java DSL" anchor="treebuilder_java_dsl"> |
| <p>JMeter 5.6 introduces Java DSL which might make it easier to create and maintain test plans as the structure of the code |
| would resemble the structure of the generated test plan tree</p> |
| |
| <p> |
| <source> |
| import org.apache.jmeter.sampler.DebugSampler |
| import org.apache.jmeter.testelement.TestPlan |
| import org.apache.jmeter.threads.ThreadGroup |
| import static org.apache.jmeter.treebuilder.dsl.TreeBuilders.testTree |
| |
| ListedHashTree root = testTree(b -> { // (1) |
| b.add(TestPlan.class, tp -> { // (2) |
| b.add(ThreadGroup.class, tg -> { |
| tg.setName("Search Order Thread Group"); |
| b.add(DebugSampler.class); // (3) |
| b.add(new DebugSampler()); // (4) |
| }); |
| }); |
| });</source> |
| <ul> |
| <li>Firstly, we create a <code>TreeBuilder</code> at <code>(1)</code>. |
| Note how this builder reference should be used to append all the elements</li> |
| <li>Then we add elements to the tree in <code>(2)</code>, and populate its children. |
| The lambda parameters correspond to the added elements, so you can configure their properties</li> |
| <li>Note how adding element returns the subtree, so we add <code>threadGroup</code> under <code>testPlan</code> in <code>(2)</code></li> |
| <li>If no children needed, you could omit the lambda parameter as in <code>(3)</code></li> |
| <li>By default, JMeter uses no-argument constructors to create elements, however, you can add <code>TestElement</code> instances to the tree as well, see <code>(4)</code></li> |
| </ul> |
| </p> |
| </section> |
| </body> |
| </document> |