Testcontainers-Primarily based Load Testing Bench – DZone – Uplaza

Utilizing Testcontainers has radically improved the method of working with take a look at situations. Because of this software, creating environments for integration assessments has turn out to be less complicated (see the article Isolation in Testing with Kafka). Now we will simply launch containers with completely different variations of databases, message brokers, and different companies. For integration assessments, Testcontainers has confirmed indispensable. Though load testing is much less frequent than practical testing, it may be far more pleasing. Finding out graphs and analyzing the efficiency of a specific service can convey actual pleasure. Such duties are uncommon, however they’re particularly thrilling for me.

The aim of this text is to exhibit an strategy to making a setup for load testing in the identical approach that common integration assessments are written: within the type of Spock assessments utilizing Testcontainers in a Gradle challenge surroundings. Load-testing utilities corresponding to Gatling, WRK, and Yandex.Tank are used.

Making a Load Testing Atmosphere

Toolset: Gradle + Spock Framework + Testcontainers. The implementation variant is a separate Gradle module. Load testing utilities used are Gatling, WRK, and Yandex.Tank.

There are two approaches to working with the take a look at object:

  • Testing revealed photos;
  • Constructing photos from the challenge’s supply code and testing.

Within the first case, we have now a set of load assessments which might be unbiased of the challenge’s model and modifications. This strategy is simpler to take care of sooner or later, however it’s restricted to testing solely revealed photos. We will, after all, manually construct these photos regionally, however that is much less automated and reduces reproducibility. When operating in CI/CD with out the mandatory photos, the assessments will fail.

Within the second case, the assessments are run on the newest model of the service. This enables for integrating load assessments into CI and acquiring efficiency knowledge modifications between service variations. Nevertheless, load assessments often take longer than unit assessments. The choice to incorporate such assessments in CI as a part of the standard gate is as much as you.

This text considers the primary strategy. Because of Spock, we will run assessments on a number of variations of the service for comparative evaluation:

the place:
picture                  | _
'avvero/sandbox:1.0.0' | _
'avvero/sandbox:1.1.0' | _

You will need to notice that the purpose of this text is to exhibit the group of the testing house, not full-scale load testing.

Goal Service

For the testing object, let’s take a easy HTTP service named Sandbox, which publishes an endpoint and makes use of knowledge from an exterior supply to deal with requests. The service has a database.

The supply code of the service, together with the Dockerfile, is accessible within the challenge repository spring-sandbox.

Module Construction Overview

As we delve into the main points later within the article, I need to begin with a quick overview of the construction of the load-tests Gradle module to supply an understanding of its composition:

load-tests/
|-- src/
|   |-- gatling/
|   |   |-- scala/
|   |   |   |-- MainSimulation.scala         # Fundamental Gatling simulation file
|   |   |-- sources/
|   |   |   |-- gatling.conf                 # Gatling configuration file
|   |   |   |-- logback-test.xml             # Logback configuration for testing
|   |-- take a look at/
|   |   |-- groovy/
|   |   |   |-- pw.avvero.spring.sandbox/
|   |   |   |   |-- GatlingTests.groovy      # Gatling load take a look at file
|   |   |   |   |-- WrkTests.groovy          # Wrk load take a look at file
|   |   |   |   |-- YandexTankTests.groovy   # Yandex.Tank load take a look at file
|   |   |-- java/
|   |   |   |-- pw.avvero.spring.sandbox/
|   |   |   |   |-- FileHeadLogConsumer.java # Helper class for logging to a file
|   |   |-- sources/
|   |   |   |-- wiremock/
|   |   |   |   |-- mappings/                # WireMock setup for mocking exterior companies
|   |   |   |   |   |-- well being.json          
|   |   |   |   |   |-- forecast.json
|   |   |   |-- yandex-tank/                 # Yandex.Tank load testing configuration
|   |   |   |   |-- ammo.txt
|   |   |   |   |-- load.yaml
|   |   |   |   |-- make_ammo.py
|   |   |   |-- wrk/                         # LuaJIT scripts for Wrk
|   |   |   |   |-- scripts/                 
|   |   |   |   |   |-- getForecast.lua
|-- construct.gradle

Challenge repository.

Atmosphere

From the outline above, we see that the service has two dependencies: the service https://external-weather-api.com and a database. Their description shall be offered under, however let’s begin by enabling all elements of the scheme to speak in a Docker surroundings — we’ll describe the community:

def community = Community.newNetwork()

And supply community aliases for every element. That is extraordinarily handy and permits us to statically describe the combination parameters.

Dependencies corresponding to WireMock and the load testing utilities themselves require configuration to work. These might be parameters that may be handed to the container or whole information and directories that must be mounted to the containers. As well as, we have to retrieve the outcomes of their work from the containers. To resolve these duties, we have to present two units of directories:

  • workingDirectory — the module’s useful resource listing, immediately at load-tests/.
  • reportDirectory — the listing for the outcomes of the work, together with metrics and logs. Extra on this shall be within the part on experiences.

Database

The Sandbox service makes use of Postgres as its database. Let’s describe this dependency as follows:

def postgres = new PostgreSQLContainer("postgres:15-alpine")
        .withNetwork(community)
        .withNetworkAliases("postgres")
        .withUsername("sandbox")
        .withPassword("sandbox")
        .withDatabaseName("sandbox")

The declaration specifies the community alias postgres, which the Sandbox service will use to connect with the database. To finish the combination description with the database, the service must be supplied with the next parameters:

'spring.datasource.url'                         : 'jdbc:postgresql://postgres:5432/sandbox',
'spring.datasource.username'                    : 'sandbox',
'spring.datasource.password'                    : 'sandbox',
'spring.jpa.properties.hibernate.default_schema': 'sandbox'

The database construction is managed by the applying itself utilizing Flyway, so no extra database manipulations are wanted within the take a look at.

Mocking Requests to https://external-weather-api.com

If we do not have the likelihood, necessity, or want to run the precise element in a container, we will present a mock for its API. For the service https://external-weather-api.com, WireMock is used.

The declaration of the WireMock container will seem like this:

def wiremock = new GenericContainer("wiremock/wiremock:3.5.4")
        .withNetwork(community)
        .withNetworkAliases("wiremock")
        .withFileSystemBind("${workingDirectory}/src/test/resources/wiremock/mappings", "/home/wiremock/mappings", READ_WRITE)
        .withCommand("--no-request-journal")
        .waitingFor(new LogMessageWaitStrategy().withRegEx(".*https://wiremock.io/cloud.*"))
wiremock.begin()

WireMock requires mock configuration. The withFileSystemBind instruction describes the file system binding between the native file path and the trail contained in the Docker container. On this case, the listing "${workingDirectory}/src/test/resources/wiremock/mappings" on the native machine shall be mounted to /house/wiremock/mappings contained in the WireMock container. Beneath is a further a part of the challenge construction to grasp the file composition within the listing:

load-tests/
|-- src/
|   |-- take a look at/
|   |   |-- sources/
|   |   |   |-- wiremock/
|   |   |   |   |-- mappings/               
|   |   |   |   |   |-- well being.json
|   |   |   |   |   |-- forecast.json

To make sure that the mock configuration information are appropriately loaded and accepted by WireMock, you should use a helper container:

helper.execInContainer("wget", "-O", "-", "http://wiremock:8080/health").getStdout() == "Ok"

The helper container is described as follows:

def helper = new GenericContainer("alpine:3.17")
        .withNetwork(community)
        .withCommand("top")

By the way in which, IntelliJ IDEA model 2024.1 launched help for WireMock, and the IDE gives strategies when forming mock configuration information.

Goal Service Launch Configuration

The declaration of the Sandbox service container appears as follows:

(picture)
.withNetwork(community)
.withNetworkAliases(“sandbox”)
.withFileSystemBind(“${reportDirectory}/logs”, “/tmp/gc”, READ_WRITE)
.withFileSystemBind(“${reportDirectory}/jfr”, “/tmp/jfr”, READ_WRITE)
.withEnv([
‘JAVA_OPTS’ : javaOpts,
‘app.weather.url’ : ‘http://wiremock:8080’,
‘spring.datasource.url’ : ‘jdbc:postgresql://postgres:5432/sandbox’,
‘spring.datasource.username’ : ‘sandbox’,
‘spring.datasource.password’ : ‘sandbox’,
‘spring.jpa.properties.hibernate.default_schema’: ‘sandbox’
])
.waitingFor(new LogMessageWaitStrategy().withRegEx(“.*Started SandboxApplication.*”))
.withStartupTimeout(Period.ofSeconds(10))
sandbox.begin()” data-lang=”text/x-java”>
def javaOpts=" -Xloggc:/tmp/gc/gc.log -XX:+PrintGCDetails" +
        ' -XX:+UnlockDiagnosticVMOptions' +
        ' -XX:+FlightRecorder' +
        ' -XX:StartFlightRecording:settings=default,dumponexit=true,disk=true,length=60s,filename=/tmp/jfr/flight.jfr'
def sandbox = new GenericContainer(picture)
        .withNetwork(community)
        .withNetworkAliases("sandbox")
        .withFileSystemBind("${reportDirectory}/logs", "/tmp/gc", READ_WRITE)
        .withFileSystemBind("${reportDirectory}/jfr", "/tmp/jfr", READ_WRITE)
        .withEnv([
                'JAVA_OPTS'                                     : javaOpts,
                'app.weather.url'                               : 'http://wiremock:8080',
                'spring.datasource.url'                         : 'jdbc:postgresql://postgres:5432/sandbox',
                'spring.datasource.username'                    : 'sandbox',
                'spring.datasource.password'                    : 'sandbox',
                'spring.jpa.properties.hibernate.default_schema': 'sandbox'
        ])
        .waitingFor(new LogMessageWaitStrategy().withRegEx(".*Started SandboxApplication.*"))
        .withStartupTimeout(Period.ofSeconds(10))
sandbox.begin()

Notable parameters and JVM settings embody:

  • Assortment of rubbish assortment occasion data.
  • Use of Java Flight Recorder (JFR) to report JVM efficiency knowledge.

Moreover, directories are configured for saving the diagnostic outcomes of the service.

Logging

If you should see the logs of any container to a file, which is probably going mandatory throughout the take a look at state of affairs writing and configuration stage, you should use the next directions when describing the container:

.withLogConsumer(new FileHeadLogConsumer("${reportDirectory}/logs/${alias}.log"))

On this case, the FileHeadLogConsumer class is used, which permits writing a restricted quantity of logs to a file. That is accomplished as a result of the whole log is probably going not wanted in load-testing situations, and a partial log shall be ample to evaluate whether or not the service is functioning appropriately.

Implementation of Load Assessments

There are numerous instruments for load testing. On this article, I suggest to think about using three of them: Gatling, Wrk, and Yandex.Tank. All three instruments can be utilized independently of one another.

Gatling

Gatling is an open-source load-testing software written in Scala. It permits the creation of advanced testing situations and gives detailed experiences. The principle simulation file of Gatling is related as a Scala useful resource to the module, making it handy to work with utilizing the complete vary of help from IntelliJ IDEA, together with syntax highlighting and navigation by means of strategies for documentation reference.

The container configuration for Gatling is as follows:

def gatling = new GenericContainer("denvazh/gatling:3.2.1")
        .withNetwork(community)
        .withFileSystemBind("${reportDirectory}/gatling-results", "/opt/gatling/results", READ_WRITE)
        .withFileSystemBind("${workingDirectory}/src/gatling/scala", "/opt/gatling/user-files/simulations", READ_WRITE)
        .withFileSystemBind("${workingDirectory}/src/gatling/resources", "/opt/gatling/conf", READ_WRITE)
        .withEnv("SERVICE_URL", "http://sandbox:8080")
        .withCommand("-s", "MainSimulation")
        .waitingFor(new LogMessageWaitStrategy()
                .withRegEx(".*Please open the following file: /opt/gatling/results.*")
                .withStartupTimeout(Period.ofSeconds(60L * 2))
        );
gatling.begin()

The setup is nearly similar to different containers:

  • Mount the listing for experiences from reportDirectory.
  • Mount the listing for configuration information from workingDirectory.
  • Mount the listing for simulation information from workingDirectory.

Moreover, parameters are handed to the container:

  • The SERVICE_URL surroundings variable with the URL worth for the Sandbox service. Nevertheless, as talked about earlier, utilizing community aliases permits hardcoding the URL immediately within the state of affairs code.
  • The -s MainSimulation command to run a selected simulation.

Here’s a reminder of the challenge supply file construction to grasp what’s being handed and the place:

load-tests/
|-- src/
|   |-- gatling/
|   |   |-- scala/
|   |   |   |-- MainSimulation.scala         # Fundamental Gatling simulation file
|   |   |-- sources/
|   |   |   |-- gatling.conf                 # Gatling configuration file
|   |   |   |-- logback-test.xml             # Logback configuration for testing

Since that is the ultimate container, and we anticipate to get outcomes upon its completion, we set the expectation .withRegEx(".*Please open the following file: /opt/gatling/results.*"). The take a look at will finish when this message seems within the container logs or after `60 * 2` seconds.

I can’t delve into the DSL of this software’s situations. You possibly can take a look at the code of the used state of affairs within the challenge repository.

Wrk

Wrk is an easy and quick load-testing software. It may well generate a big load with minimal sources. Key options embody:

  • Assist for Lua scripts to configure requests.
  • Excessive efficiency on account of multithreading.
  • Ease of use with minimal dependencies.

The container configuration for Wrk is as follows:

def wrk = new GenericContainer("ruslanys/wrk")
        .withNetwork(community)
        .withFileSystemBind("${workingDirectory}/src/test/resources/wrk/scripts", "/tmp/scripts", READ_WRITE)
        .withCommand("-t10", "-c10", "-d60s", "--latency", "-s", "/tmp/scripts/getForecast.lua", "http://sandbox:8080/weather/getForecast")
        .waitingFor(new LogMessageWaitStrategy()
                .withRegEx(".*Transfer/sec.*")
                .withStartupTimeout(Period.ofSeconds(60L * 2))
        )
wrk.begin()

To make Wrk work with requests to the Sandbox service, the request description by way of a Lua script is required, so we mount the script listing from workingDirectory. Utilizing the command, we run Wrk, specifying the script and the URL of the goal service methodology. Wrk writes a report back to the log primarily based on its outcomes, which can be utilized to set expectations.

Yandex.Tank

Yandex.Tank is a load testing software developed by Yandex. It helps varied load-testing engines, corresponding to JMeter and Phantom. For storing and displaying load take a look at outcomes, you should use the free service Overload.

Right here is the container configuration:

copyFiles("${workingDirectory}/src/test/resources/yandex-tank", "${reportDirectory}/yandex-tank")
def tank = new GenericContainer("yandex/yandex-tank")
        .withNetwork(community)
        .withFileSystemBind("${reportDirectory}/yandex-tank", "/var/loadtest", READ_WRITE)
        .waitingFor(new LogMessageWaitStrategy()
                .withRegEx(".*Phantom done its work.*")
                .withStartupTimeout(Period.ofSeconds(60L * 2))
        )
tank.begin()

The load testing configuration for Sandbox is represented by two information: load.yaml and ammo.txt. As a part of the container description, configuration information are copied to the reportDirectory, which shall be mounted because the working listing. Right here is the construction of the challenge supply information to grasp what’s being handed and the place:

load-tests/
|-- src/
|   |-- take a look at/
|   |   |-- sources/
|   |   |   |-- yandex-tank/                
|   |   |   |   |-- ammo.txt
|   |   |   |   |-- load.yaml
|   |   |   |   |-- make_ammo.py

Stories

Take a look at outcomes, together with JVM efficiency recordings and logs, are saved within the listing construct/${timestamp}, the place ${timestamp} represents the timestamp of every take a look at run.

The next experiences shall be out there for evaluate:

  • Rubbish Collector logs.
  • WireMock logs.
  • Goal service logs.
  • Wrk logs.
  • JFR (Java Flight Recording).

If Gatling was used:

  • Gatling report.
  • Gatling logs.

If Wrk was used:

If Yandex.Tank was used:

  • Yandex.Tank end result information, with a further add to [Overload](https://overload.yandex.web/).
  • Yandex.Tank logs.

The listing construction for the experiences is as follows:

load-tests/
|-- construct/
|   |-- ${timestamp}/
|   |   |-- gatling-results/
|   |   |-- jfr/
|   |   |-- yandex-tank/
|   |   |-- logs/
|   |   |   |-- sandbox.log
|   |   |   |-- gatling.log
|   |   |   |-- gc.log
|   |   |   |-- wiremock.log
|   |   |   |-- wrk.log
|   |   |   |-- yandex-tank.log
|   |-- ${timestamp}/
|   |-- ...

Conclusion

Load testing is a vital section within the software program growth lifecycle. It helps assess the efficiency and stability of an software underneath varied load situations. This text offered an strategy to making a load testing surroundings utilizing Testcontainers, which permits for a simple and environment friendly setup of the testing surroundings.

Testcontainers considerably simplify the creation of environments for integration assessments, offering flexibility and isolation. For load testing, this software permits the deployment of mandatory containers with completely different variations of companies and databases, making it simpler to conduct assessments and enhance end result reproducibility.

The offered configuration examples for Gatling, Wrk, and Yandex.Tank, together with container setup, demonstrates easy methods to successfully combine varied instruments and handle testing parameters. Moreover, the method of logging and saving take a look at outcomes was described, which is important for analyzing and enhancing software efficiency. This strategy might be expanded sooner or later to help extra advanced situations and integration with different monitoring and evaluation instruments.

Thanks in your consideration to this text, and good luck in your endeavor to put in writing helpful assessments!

Share This Article
Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Exit mobile version