Skip to content

Commit

Permalink
Add support for response assertions
Browse files Browse the repository at this point in the history
Additionally:
- rename MultiScopedTestElement to MultiLevelTestElement to avoid confusion with existing JMeter concept of ScopedTestElement
- remove methods from class diagram to reduce complexity and make it easier to maintain
- reorder use-cases document to put most used cases at the beginning to avoid unnecessary scroll in most cases
- extract common logic for ScopedTestElements to DslScopedTestElement for reusability.
  • Loading branch information
rabelenda-abstracta committed Dec 1, 2020
1 parent 03eaa5c commit 1b3ed12
Show file tree
Hide file tree
Showing 15 changed files with 651 additions and 203 deletions.
134 changes: 63 additions & 71 deletions docs/classes.puml
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,7 @@ hide empty members
hide circle

class JmeterDsl {
DslTestPlan testPlan(TestPlanChild[] children)
DslThreadGroup threadGroup(int threads, int iterations, ThreadGroupChild[] children)
DslThreadGroup threadGroup(String name, int threads, int iterations, ThreadGroupChild[] children)
DslThreadGroup threadGroup(int threads, Duration duration, ThreadGroupChild[] children)
DslThreadGroup threadGroup(String name, int threads, Duration duration, ThreadGroupChild[] children)
DslHttpSampler httpSampler(String url)
DslHttpSampler httpSampler(Function<PreProcessorVars, String> urlSupplier)
DslHttpSampler httpSampler(String name, String url)
DslHttpSampler httpSampler(String name, Function<PreProcessorVars, String> urlSupplier)
HttpHeaders httpHeaders()
DslJsr223PreProcessor jsr223PreProcessor(String script)
DslJsr223PreProcessor jsr223PreProcessor(String name, String script)
DslJsr223PreProcessor jsr223PreProcessor(PreProcessorScript script)
DslJsr223PreProcessor jsr223PreProcessor(String name, PreProcessorScript script)
DslRegexExtractor regexExtractor(String variableName, String regex)
DslJsr223PostProcessor jsr223PostProcessor(String script)
DslJsr223PostProcessor jsr223PostProcessor(String name, String script)
DslJsr223PostProcessor jsr223PostProcessor(PostProcessorScript script)
DslJsr223PostProcessor jsr223PostProcessor(String name, PostProcessorScript script)
JtlWriter jtlWriter(String jtlFile)
InfluxDbBackendListener influxDbListener(String url)
HtmlReporter htmlReporter(String reportDirectory)
..
}

package core {
Expand Down Expand Up @@ -63,6 +42,7 @@ package core {
class DslThreadGroup extends TestElementContainer implements TestPlanChild {
int threads
int iterations
Duration duration
}

interface ThreadGroupChild extends DslTestElement
Expand All @@ -71,35 +51,12 @@ package core {

interface SamplerChild extends DslTestElement

interface MultiScopedTestElement extends TestPlanChild, ThreadGroupChild, SamplerChild

package listeners {

class JtlWriter extends BaseTestElement implements MultiScopedTestElement {
String jtlFilePath
}

class HtmlReporter extends BaseTestElement implements MultiScopedTestElement {
File reportDirectory
}

class InfluxDbBackendListener extends BaseTestElement implements MultiScopedTestElement {
String url
String token
String title
int queueSize
InfluxDbBackendListener token(String token)
InfluxDbBackendListener title(String token)
InfluxDbBackendListener queueSize(int queueSize)
}

}
interface MultiLevelTestElement extends TestPlanChild, ThreadGroupChild, SamplerChild

abstract class DslJsr223TestElement extends BaseTestElement {
String script
String language
DslJsr223TestElement language(String language)
abstract DslJsr223TestElement buildJsr223TestElement()
String script
String language
abstract DslJsr223TestElement buildJsr223TestElement()
}

interface Jsr223Script {
Expand All @@ -115,17 +72,27 @@ package core {
String Label
}

package preprocessors {

class DslJsr223PreProcessor extends DslJsr223TestElement implements MultiLevelTestElement

interface PreProcessorScript extends Jsr223Script

class PreProcessorVars extends Jsr223ScriptVars

}

package postprocessors {

class DslJsr223PostProcessor extends DslJsr223TestElement implements MultiScopedTestElement
class DslJsr223PostProcessor extends DslJsr223TestElement implements MultiLevelTestElement

interface PostProcessorScript extends Jsr223Script

class PostProcessorVars extends Jsr223ScriptVars {
SampleResult prev
}

class DslRegexExtractor extends BaseTestElement implements MultiScopedTestElement {
class DslRegexExtractor extends BaseTestElement implements MultiLevelTestElement {
String variableName
String regex
int matchNumber
Expand All @@ -134,12 +101,6 @@ package core {
TargetField fieldToCheck
Scope scope
String scopeVariable
DslRegexExtractor matchNumber(int matchNumber)
DslRegexExtractor template(String template)
DslRegexExtractor defaultValue(String defaultValue)
DslRegexExtractor fieldToCheck(TargetField fieldToCheck)
DslRegexExtractor scope(Scope scope)
DslRegexExtractor scopeVariable(String scopeVariable)
}

enum TargetField {
Expand All @@ -164,13 +125,52 @@ package core {

}

package preprocessors {
package assertions {
class DslResponseAssertion extends BaseTestElement implements MultiLevelTestElement {
TargetField fieldToTest
boolean ignoreStatus
String[] testStrings
TestStringStrategy testStrategy
boolean invertCheck
boolean anyMatch
}

class DslJsr223PreProcessor extends DslJsr223TestElement implements MultiScopedTestElement
enum TargetField {
RESPONSE_BODY
RESPONSE_BODY_AS_DOCUMENT
RESPONSE_CODE
RESPONSE_MESSAGE
RESPONSE_HEADERS
REQUEST_HEADERS
REQUEST_URL
REQUEST_BODY
}

interface PreProcessorScript extends Jsr223Script
enum TestStringStrategy {
SUBSTRING
EQUALS
CONTAINS_REGEX
MATCHES_REGEX
}

class PreProcessorVars extends Jsr223ScriptVars
}

package listeners {

class JtlWriter extends BaseTestElement implements MultiLevelTestElement {
String jtlFilePath
}

class HtmlReporter extends BaseTestElement implements MultiLevelTestElement {
File reportDirectory
}

class InfluxDbBackendListener extends BaseTestElement implements MultiLevelTestElement {
String url
String token
String title
int queueSize
}

}

Expand All @@ -182,18 +182,10 @@ package http {
String url
HttpMethod method
String body
DslHttpSampler post(String body, Type contentType)
DslHttpSampler post(Function<PreProcessorVars, String> bodySupplier, Type contentType)
DslHttpSampler method(HttpMethod method)
DslHttpSampler body(String body)
DslHttpSampler body(Function<PreProcessorVars, String> bodySupplier)
DslHttpSampler header(String name, String value)
DslHttpSampler header(String name, Function<PreProcessorVars, String> valueSupplier)
DslHttpSampler children(SamplerChild[] children)
}

class HttpHeaders extends BaseTestElement implements MultiScopedTestElement {
HttpHeaders header(String name, String value)
class HttpHeaders extends BaseTestElement implements MultiLevelTestElement {
Map<String, String> headers
}

}
Expand Down
121 changes: 80 additions & 41 deletions docs/use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,82 @@ Check [BlazeMeterEngine](../jmeter-java-dsl-blazemeter/src/main/java/us/abstract
> **Warning:** If you use JSR223 Pre or Post processors with Java code (lambdas) instead of strings, or use one of the HTTP Sampler methods which receive a function as parameter, then BlazeMeter execution won't work. You can migrate them to use jsrPreProcessor with string scripts instead. Check for these methods documentation for more details.
## Check for expected response
By default, JMeter marks any HTTP request with a fail response code (4xx or 5xx) as failed, which allows you to easily identify when some request unexpectedly fails. But in many cases this is not enough or desirable, and you need to check for response body (or some other field) to contain (or not contain) certain string.
This is usually accomplished with the usage of Response Assertions, which provides an easy and fast way to verify that you get the proper response for each step of the test plan, marking the request as failure when such condition is not met.
Here is an example on how to specify a response assertion in jmeter-java-dsl:
```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.eclipse.jetty.http.MimeTypes.Type;
import org.junit.jupiter.api.Test;
public class PerformanceTest {
@Test
public void testPerformance() throws IOException {
TestPlanStats stats = testPlan(
threadGroup(2, 10,
httpSampler("http://my.service")
.children(
responseAssertion().containsSubstrings("OK")
)
)
).run();
assertThat(stats.overall().elapsedTimePercentile99()).isLessThan(Duration.ofSeconds(5));
}
}
```
Check [Response Assertion](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/assertions/DslResponseAssertion.java) for more details and additional options

For more complex scenarios you can use (for the time being) [later mentioned JSR223 Post processor](#change-sample-result-statuses-with-custom-logic).

## Use part of a response in a following request

It is a usual requirement while creating a test plan for an application, to be able to use part of a response (e.g.: a generated ID, token, etc) in a subsequent request. This can be easily achieved using JMeter extractors and variables. Here is an example with jmeter-java-dsl:

```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.eclipse.jetty.http.MimeTypes.Type;
import org.junit.jupiter.api.Test;

public class PerformanceTest {

@Test
public void testPerformance() throws IOException {
TestPlanStats stats = testPlan(
threadGroup(2, 10,
httpSampler("http://my.service/accounts")
.post("{\"name\": \"John Doe\"}", Type.APPLICATION_JSON)
.children(
regexExtractor("ACCOUNT_ID", "\"id\":\"([^\"]+)\"")
),
httpSampler("http://my.service/accounts/${ACCOUNT_ID}")
)
).run();
assertThat(stats.overall().elapsedTimePercentile99()).isLessThan(Duration.ofSeconds(5));
}

}
```

Check [DslRegexExtractor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslRegexExtractor.java) for more details and additional options.

For more complex scenarios you can use [later mentioned JSR223 Post processor](#change-sample-result-statuses-with-custom-logic).

## Save as JMX

In case you want to load a test plan in JMeter GUI, you can save it just invoking `saveAsJMX` method in the test plan as in following example:
Expand Down Expand Up @@ -142,9 +218,9 @@ This can be used to just run existing JMX files, or when DSL has no support for
> ```
## Publish test metrics to [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) and visualizing them in [Grafana](https://grafana.com/)
When running tests with JMeter (and in particular with jmeter-java-dsl) a usual requirement is to be able to store such test runs in a persistent database to later on review such metrics, and compare different test runs. Additionally, jmeter-java-dsl only provides some summary data of test run in the console while it is running, but, since it doesn't provide any sort of UI, doesn't allow to easily analyze such information as it can be done in JMeter GUI.
To overcome these limitations you can use provided support for publishing JMeter test run metrics to InfluxDB, which allows keeping record of all run statistics and, through Grafana, get some nice dashboards like the following one:
![grafana](influxdb/grafana.png)
Expand Down Expand Up @@ -269,8 +345,8 @@ jsr223PostProcessor(s -> {
Check [DslJsr223PostProcessor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslJsr223PostProcessor.java) for more details and additional options.

JSR223PostProcessor is a very powerful tool, but is not the only, nor the best, alternative for many cases where JMeter already provides a better and simpler alternative (eg: asserting response bodies contain some string). Currently, jmeter-java-dsl does not support all the features JMeter provides. So, if you need something already provided by JMeter, please create an issue in GitHub requesting such a feature or submit a pull request with the required support.
**Note:** JSR223PostProcessor is a very powerful tool, but is not the only, nor the best, alternative for many cases where JMeter already provides a better and simpler alternative. For instance, previously mentioned might be implemented with previously presented [Response Assertion](#check-for-expected-response).

## 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.
Expand Down Expand Up @@ -329,40 +405,3 @@ post(s -> buildRequestBody(s.vars), Type.TEXT_PLAIN)
> **WARNING:** 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 [DslJsr223PreProcessor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/preprocessors/DslJsr223PreProcessor.java) & [DslHttpSampler](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/http/DslHttpSampler.java) for more details and additional options.

## Use part of a response in a following request

It is a usual requirement while creating a test plan for an application, to be able to use part of a response (e.g.: a generated ID, token, etc) in a subsequent request. This can be easily achieved using JMeter extractors and variables. Here is an example with jmeter-java-dsl:

```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.eclipse.jetty.http.MimeTypes.Type;
import org.junit.jupiter.api.Test;

public class PerformanceTest {

@Test
public void testPerformance() throws IOException {
TestPlanStats stats = testPlan(
threadGroup(2, 10,
httpSampler("http://my.service/accounts")
.post("{\"name\": \"John Doe\"}", Type.APPLICATION_JSON)
.children(
regexExtractor("ACCOUNT_ID", "\"id\":\"([^\"]+)\"")
),
httpSampler("http://my.service/accounts/${ACCOUNT_ID}")
)
).run();
assertThat(stats.overall().elapsedTimePercentile99()).isLessThan(Duration.ofSeconds(5));
}

}
```

Check [DslRegexExtractor](../jmeter-java-dsl/src/main/java/us/abstracta/jmeter/javadsl/core/postprocessors/DslRegexExtractor.java) for more details and additional options.

For more complex scenarios you can use [previously mentioned JSR223 Post processor](#change-sample-result-statuses-with-custom-logic).
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ public class BlazeMeterEngine implements DslJmeterEngine {
private boolean useDebugRun;

/**
* @param authToken is the authentication token to be used to access BlazeMeter API.<p> It follows
* the following format: &lt;Key ID&gt;:&lt;Key Secret&gt;.<p> Check <a
* href="https://guide.blazemeter.com/hc/en-us/articles/115002213289-BlazeMeter-API-keys-">BlazeMeter
* @param authToken is the authentication token to be used to access BlazeMeter API.
* <p>
* It follows the following format: &lt;Key ID&gt;:&lt;Key Secret&gt;.
* <p>
* Check <a href="https://guide.blazemeter.com/hc/en-us/articles/115002213289-BlazeMeter-API-keys-">BlazeMeter
* API keys</a> for instructions on how to generate them.
*/
public BlazeMeterEngine(String authToken) {
Expand Down
Loading

0 comments on commit 1b3ed12

Please sign in to comment.