google-site-verification: google61fe8ba583a51912.html
Locust with Java Backend Integration

Comprehensive Locust Integration Guide for Java Applications

1. Locust Test Configuration and Setup

Maven Dependencies

<!-- pom.xml --> <properties> <locust4j.version>1.0.7</locust4j.version> <testcontainers.version>1.19.3</testcontainers.version> <micrometer.version>1.11.5</micrometer.version> </properties> <dependencies> <!-- Locust4J - Java client for Locust --> <dependency> <groupId>com.github.myzhan</groupId> <artifactId>locust4j</artifactId> <version>${locust4j.version}</version> </dependency> <!-- Test Containers for Locust --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>${testcontainers.version}</version> <scope>test</scope> </dependency> <!-- HTTP Client --> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Metrics --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> <version>${micrometer.version}</version> </dependency> </dependencies>

2. Locust Test Runner Service

// LocustTestRunner.java package com.example.locust.runner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @Service public class LocustTestRunner { private static final Logger logger = LoggerFactory.getLogger(LocustTestRunner.class); @Value("${locust.scripts.directory:src/test/locust}") private String scriptsDirectory; @Value("${locust.results.directory:target/locust-results}") private String resultsDirectory; @Value("${locust.python.path:python3}") private String pythonPath; private final ExecutorService executorService = Executors.newCachedThreadPool(); public TestExecutionResult runLocustTest(String testScript, LocustOptions options) { try { Path scriptPath = Paths.get(scriptsDirectory, testScript); Path resultsPath = Paths.get(resultsDirectory, testScript.replace(".py", "-" + System.currentTimeMillis())); Files.createDirectories(resultsPath.getParent()); List<String> command = buildLocustCommand(scriptPath, resultsPath, options); logger.info("Executing Locust test: {}", String.join(" ", command)); ProcessBuilder processBuilder = new ProcessBuilder(command); processBuilder.directory(new File(".")); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); // Capture output String output = captureOutput(process.getInputStream()); // Wait for process with timeout boolean completed = process.waitFor(options.getTimeoutMinutes(), TimeUnit.MINUTES); TestExecutionResult result = new TestExecutionResult(); result.setScriptName(testScript); result.setOutput(output); result.setResultsDirectory(resultsPath.toString()); if (completed) { int exitCode = process.exitValue(); result.setExitCode(exitCode); result.setSuccess(exitCode == 0); logger.info("Locust test completed with exit code: {}", exitCode); } else { process.destroyForcibly(); result.setExitCode(-1); result.setSuccess(false); result.setTimeout(true); logger.warn("Locust test timed out after {} minutes", options.getTimeoutMinutes()); } return result; } catch (IOException | InterruptedException e) { logger.error("Failed to execute Locust test: {}", testScript, e); throw new RuntimeException("Locust test execution failed", e); } } public CompletableFuture<TestExecutionResult> runLocustTestAsync(String testScript, LocustOptions options) { return CompletableFuture.supplyAsync(() -> runLocustTest(testScript, options), executorService); } public TestExecutionResult runLocustWithDocker(String testScript, LocustOptions options) { try { List<String> command = new ArrayList<>(); command.add("docker"); command.add("run"); command.add("--rm"); command.add("-v"); command.add(Paths.get(scriptsDirectory).toAbsolutePath() + ":/locust-scripts"); command.add("-v"); command.add(Paths.get(resultsDirectory).toAbsolutePath() + ":/locust-results"); command.add("locustio/locust"); command.add("-f"); command.add("/locust-scripts/" + testScript); command.add("--host=" + options.getHost()); command.add("--users=" + options.getNumUsers()); command.add("--spawn-rate=" + options.getSpawnRate()); command.add("--run-time=" + options.getRunTime()); command.add("--headless"); command.add("--csv=/locust-results/" + testScript.replace(".py", "")); if (options.isWebUI()) { command.add("--web-host=0.0.0.0"); command.add("--web-port=8089"); } logger.info("Executing Locust test with Docker: {}", String.join(" ", command)); ProcessBuilder processBuilder = new ProcessBuilder(command); processBuilder.directory(new File(".")); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); String output = captureOutput(process.getInputStream()); boolean completed = process.waitFor(options.getTimeoutMinutes(), TimeUnit.MINUTES); TestExecutionResult result = new TestExecutionResult(); result.setScriptName(testScript); result.setOutput(output); if (completed) { result.setExitCode(process.exitValue()); result.setSuccess(result.getExitCode() == 0); } else { process.destroyForcibly(); result.setExitCode(-1); result.setSuccess(false); result.setTimeout(true); } return result; } catch (IOException | InterruptedException e) { logger.error("Failed to execute Locust test with Docker: {}", testScript, e); throw new RuntimeException("Locust Docker test execution failed", e); } } private List<String> buildLocustCommand(Path scriptPath, Path resultsPath, LocustOptions options) { List<String> command = new ArrayList<>(); command.add(pythonPath); command.add("-m"); command.add("locust"); command.add("-f"); command.add(scriptPath.toString()); command.add("--host=" + options.getHost()); command.add("--users=" + options.getNumUsers()); command.add("--spawn-rate=" + options.getSpawnRate()); command.add("--run-time=" + options.getRunTime()); command.add("--headless"); command.add("--csv=" + resultsPath.toString()); command.add("--html=" + resultsPath + ".html"); command.add("--json=" + resultsPath + ".json"); if (options.isOnlySummary()) { command.add("--only-summary"); } return command; } private String captureOutput(InputStream inputStream) throws IOException { StringBuilder output = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { String line; while ((line = reader.readLine()) != null) { output.append(line).append("\n"); logger.debug("Locust output: {}", line); } } return output.toString(); } public static class LocustOptions { private String host = "http://localhost:8080"; private int numUsers = 100; private int spawnRate = 10; private String runTime = "5m"; private boolean webUI = false; private boolean onlySummary = false; private int timeoutMinutes = 10; // Getters and setters public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getNumUsers() { return numUsers; } public void setNumUsers(int numUsers) { this.numUsers = numUsers; } public int getSpawnRate() { return spawnRate; } public void setSpawnRate(int spawnRate) { this.spawnRate = spawnRate; } public String getRunTime() { return runTime; } public void setRunTime(String runTime) { this.runTime = runTime; } public boolean isWebUI() { return webUI; } public void setWebUI(boolean webUI) { this.webUI = webUI; } public boolean isOnlySummary() { return onlySummary; } public void setOnlySummary(boolean onlySummary) { this.onlySummary = onlySummary; } public int getTimeoutMinutes() { return timeoutMinutes; } public void setTimeoutMinutes(int timeoutMinutes) { this.timeoutMinutes = timeoutMinutes; } } public static class TestExecutionResult { private String scriptName; private boolean success; private int exitCode; private String output; private String resultsDirectory; private boolean timeout; // Getters and setters public String getScriptName() { return scriptName; } public void setScriptName(String scriptName) { this.scriptName = scriptName; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public int getExitCode() { return exitCode; } public void setExitCode(int exitCode) { this.exitCode = exitCode; } public String getOutput() { return output; } public void setOutput(String output) { this.output = output; } public String getResultsDirectory() { return resultsDirectory; } public void setResultsDirectory(String resultsDirectory) { this.resultsDirectory = resultsDirectory; } public boolean isTimeout() { return timeout; } public void setTimeout(boolean timeout) { this.timeout = timeout; } } }

3. Locust Test Generator

// LocustTestGenerator.java package com.example.locust.generator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @Component public class LocustTestGenerator { private static final Logger logger = LoggerFactory.getLogger(LocustTestGenerator.class); @Value("${locust.scripts.directory:src/test/locust}") private String scriptsDirectory; public String generateLocustTestScript(LocustTestConfig config) { StringBuilder script = new StringBuilder(); // Import statements script.append("from locust import HttpUser, task, between, TaskSet\n"); script.append("import json\n"); script.append("import random\n\n"); // Test data if (config.hasTestData()) { script.append(generateTestData(config)); } // Task sets for (TaskSetConfig taskSet : config.getTaskSets()) { script.append(generateTaskSet(taskSet)); } // User classes for (UserClassConfig userClass : config.getUserClasses()) { script.append(generateUserClass(userClass)); } return script.toString(); } private String generateTestData(LocustTestConfig config) { StringBuilder data = new StringBuilder(); data.append("# Test Data\n"); if (config.getUserCredentials() != null && !config.getUserCredentials().isEmpty()) { data.append("USER_CREDENTIALS = [\n"); for (Map<String, String> cred : config.getUserCredentials()) { data.append(String.format(" {'username': '%s', 'password': '%s'},\n", cred.get("username"), cred.get("password"))); } data.append("]\n\n"); } if (config.getProductIds() != null && !config.getProductIds().isEmpty()) { data.append("PRODUCT_IDS = ").append(config.getProductIds()).append("\n\n"); } return data.toString(); } private String generateTaskSet(TaskSetConfig taskSet) { StringBuilder taskSetCode = new StringBuilder(); taskSetCode.append(String.format("class %s(TaskSet):\n", taskSet.getName())); if (taskSet.getWaitTime() != null) { taskSetCode.append(String.format(" wait_time = %s\n\n", taskSet.getWaitTime())); } for (TaskConfig task : taskSet.getTasks()) { taskSetCode.append(generateTask(task)).append("\n"); } taskSetCode.append("\n"); return taskSetCode.toString(); } private String generateTask(TaskConfig task) { StringBuilder taskCode = new StringBuilder(); taskCode.append(String.format(" @task(%d)\n", task.getWeight())); taskCode.append(String.format(" def %s(self):\n", task.getName())); // Generate request code based on method switch (task.getMethod().toUpperCase()) { case "GET": taskCode.append(generateGetRequest(task)); break; case "POST": taskCode.append(generatePostRequest(task)); break; case "PUT": taskCode.append(generatePutRequest(task)); break; case "DELETE": taskCode.append(generateDeleteRequest(task)); break; } // Add validation if (task.getExpectedStatus() > 0) { taskCode.append(String.format(" assert response.status_code == %d, \\\n", task.getExpectedStatus())); taskCode.append(String.format(" f\"Expected status %d, got {response.status_code}\"\n", task.getExpectedStatus())); } return taskCode.toString(); } private String generateGetRequest(TaskConfig task) { StringBuilder request = new StringBuilder(); request.append(" with self.client.get(\""); request.append(task.getUrl()); request.append("\""); if (task.getHeaders() != null && !task.getHeaders().isEmpty()) { request.append(", headers="); request.append(convertMapToPythonDict(task.getHeaders())); } if (task.getName() != null) { request.append(", name=\""); request.append(task.getName()); request.append("\""); } request.append(", catch_response=True) as response:\n"); if (task.getExpectedBody() != null) { request.append(" if \""); request.append(task.getExpectedBody()); request.append("\" not in response.text:\n"); request.append(" response.failure(\"Expected content not found in response\")\n"); } return request.toString(); } private String generatePostRequest(TaskConfig task) { StringBuilder request = new StringBuilder(); request.append(" with self.client.post(\""); request.append(task.getUrl()); request.append("\""); if (task.getBody() != null) { request.append(", json="); request.append(task.getBody()); } if (task.getHeaders() != null && !task.getHeaders().isEmpty()) { request.append(", headers="); request.append(convertMapToPythonDict(task.getHeaders())); } if (task.getName() != null) { request.append(", name=\""); request.append(task.getName()); request.append("\""); } request.append(", catch_response=True) as response:\n"); return request.toString(); } private String generatePutRequest(TaskConfig task) { // Similar to POST but with PUT method StringBuilder request = new StringBuilder(); request.append(" with self.client.put(\""); request.append(task.getUrl()); request.append("\""); if (task.getBody() != null) { request.append(", json="); request.append(task.getBody()); } if (task.getHeaders() != null && !task.getHeaders().isEmpty()) { request.append(", headers="); request.append(convertMapToPythonDict(task.getHeaders())); } request.append(", catch_response=True) as response:\n"); return request.toString(); } private String generateDeleteRequest(TaskConfig task) { StringBuilder request = new StringBuilder(); request.append(" with self.client.delete(\""); request.append(task.getUrl()); request.append("\""); if (task.getHeaders() != null && !task.getHeaders().isEmpty()) { request.append(", headers="); request.append(convertMapToPythonDict(task.getHeaders())); } request.append(", catch_response=True) as response:\n"); return request.toString(); } private String generateUserClass(UserClassConfig userClass) { StringBuilder userClassCode = new StringBuilder(); userClassCode.append(String.format("class %s(HttpUser):\n", userClass.getName())); userClassCode.append(String.format(" wait_time = %s\n", userClass.getWaitTime())); if (userClass.getTaskSet() != null) { userClassCode.append(String.format(" tasks = [%s]\n", userClass.getTaskSet())); } else if (!userClass.getTasks().isEmpty()) { userClassCode.append(" tasks = [\n"); for (String task : userClass.getTasks()) { userClassCode.append(String.format(" %s,\n", task)); } userClassCode.append(" ]\n"); } if (userClass.isSetup()) { userClassCode.append("\n def on_start(self):\n"); userClassCode.append(" # User setup code here\n"); userClassCode.append(" self.login()\n\n"); userClassCode.append(" def login(self):\n"); userClassCode.append(" credentials = random.choice(USER_CREDENTIALS)\n"); userClassCode.append(" response = self.client.post(\"/api/auth/login\", \n"); userClassCode.append(" json=credentials)\n"); userClassCode.append(" if response.status_code == 200:\n"); userClassCode.append(" self.token = response.json().get('token')\n"); userClassCode.append(" self.client.headers = {'Authorization': f'Bearer {self.token}'}\n"); } userClassCode.append("\n"); return userClassCode.toString(); } private String convertMapToPythonDict(Map<String, String> map) { if (map == null || map.isEmpty()) { return "{}"; } StringBuilder dict = new StringBuilder("{"); for (Map.Entry<String, String> entry : map.entrySet()) { dict.append(String.format("'%s': '%s', ", entry.getKey(), entry.getValue())); } dict.append("}"); return dict.toString(); } public void generateAndSaveTestScript(LocustTestConfig config, String filename) { try { String script = generateLocustTestScript(config); Path scriptPath = Paths.get(scriptsDirectory, filename); Files.createDirectories(scriptPath.getParent()); Files.writeString(scriptPath, script); logger.info("Generated Locust test script: {}", scriptPath); } catch (IOException e) { logger.error("Failed to generate Locust test script", e); throw new RuntimeException("Failed to generate Locust test script", e); } } // Configuration classes public static class LocustTestConfig { private List<TaskSetConfig> taskSets = new ArrayList<>(); private List<UserClassConfig> userClasses = new ArrayList<>(); private List<Map<String, String>> userCredentials = new ArrayList<>(); private List<Integer> productIds = Arrays.asList(1, 2, 3, 4, 5); public boolean hasTestData() { return !userCredentials.isEmpty() || !productIds.isEmpty(); } // Getters and setters public List<TaskSetConfig> getTaskSets() { return taskSets; } public List<UserClassConfig> getUserClasses() { return userClasses; } public List<Map<String, String>> getUserCredentials() { return userCredentials; } public List<Integer> getProductIds() { return productIds; } } public static class TaskSetConfig { private String name; private String waitTime = "between(1, 5)"; private List<TaskConfig> tasks = new ArrayList<>(); public TaskSetConfig(String name) { this.name = name; } // Getters and setters public String getName() { return name; } public String getWaitTime() { return waitTime; } public List<TaskConfig> getTasks() { return tasks; } } public static class TaskConfig { private String name; private int weight = 1; private String method = "GET"; private String url; private String body; private Map<String, String> headers; private int expectedStatus = 200; private String expectedBody; // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public Map<String, String> getHeaders() { return headers; } public void setHeaders(Map<String, String> headers) { this.headers = headers; } public int getExpectedStatus() { return expectedStatus; } public void setExpectedStatus(int expectedStatus) { this.expectedStatus = expectedStatus; } public String getExpectedBody() { return expectedBody; } public void setExpectedBody(String expectedBody) { this.expectedBody = expectedBody; } } public static class UserClassConfig { private String name; private String waitTime = "between(1, 5)"; private String taskSet; private List<String> tasks = new ArrayList<>(); private boolean setup = false; public UserClassConfig(String name) { this.name = name; } // Getters and setters public String getName() { return name; } public String getWaitTime() { return waitTime; } public String getTaskSet() { return taskSet; } public void setTaskSet(String taskSet) { this.taskSet = taskSet; } public List<String> getTasks() { return tasks; } public boolean isSetup() { return setup; } public void setSetup(boolean setup) { this.setup = setup; } } }

4. Locust4J Integration (Java-based Load Testing)

// Locust4JTestRunner.java package com.example.locust.runner; import io.github.zhangweihao.locus4j.client.LocustClient; import io.github.zhangweihao.locus4j.client.LocustTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; @Service public class Locust4JTestRunner { private static final Logger logger = LoggerFactory.getLogger(Locust4JTestRunner.class); @Value("${locust.master.host:localhost}") private String masterHost; @Value("${locust.master.port:8089}") private int masterPort; private final AtomicInteger userCount = new AtomicInteger(0); public void runDistributedTest(List<LocustTask> tasks, int numUsers, int hatchRate) { try { LocustClient client = new LocustClient(masterHost, masterPort); // Register tasks for (LocustTask task : tasks) { client.registerTask(task); } // Start the test client.startTest(numUsers, hatchRate); logger.info("Started distributed Locust test with {} users and hatch rate {}", numUsers, hatchRate); } catch (Exception e) { logger.error("Failed to run distributed Locust test", e); throw new RuntimeException("Distributed Locust test failed", e); } } public List<LocustTask> createApiTasks(String baseUrl) { List<LocustTask> tasks = new ArrayList<>(); // Health check task tasks.add(new LocustTask("health_check", 1, () -> { try { // Simulate HTTP request to health endpoint Thread.sleep(100); return true; } catch (Exception e) { return false; } })); // User API tasks tasks.add(new LocustTask("get_users", 2, () -> { try { // Simulate GET /api/users Thread.sleep(200); return Math.random() > 0.05; // 5% error rate } catch (Exception e) { return false; } })); // Product API tasks tasks.add(new LocustTask("get_products", 3, () -> { try { // Simulate GET /api/products Thread.sleep(150); return Math.random() > 0.02; // 2% error rate } catch (Exception e) { return false; } })); // Order API tasks tasks.add(new LocustTask("create_order", 1, () -> { try { // Simulate POST /api/orders Thread.sleep(300); return Math.random() > 0.1; // 10% error rate for orders } catch (Exception e) { return false; } })); return tasks; } public CompletableFuture<Void> runBackgroundLoadTest(int durationMinutes) { return CompletableFuture.runAsync(() -> { try { List<LocustTask> tasks = createApiTasks("http://localhost:8080"); runDistributedTest(tasks, 50, 5); // Run for specified duration Thread.sleep(durationMinutes * 60 * 1000); // Stop the test // Note: You would need to implement stop functionality } catch (InterruptedException e) { Thread.currentThread().interrupt(); logger.warn("Background load test interrupted"); } }); } }

5. Sample Locust Test Scripts

Basic API Load Test

# src/test/locust/basic_api_test.py from locust import HttpUser, task, between import random import json # Test Data USER_CREDENTIALS = [ {"username": "[email protected]", "password": "password123"}, {"username": "[email protected]", "password": "password123"}, {"username": "[email protected]", "password": "password123"}, ] PRODUCT_IDS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] class ApiUser(HttpUser): wait_time = between(1, 3) def on_start(self): """Called when a user starts the test""" self.login() def login(self): credentials = random.choice(USER_CREDENTIALS) response = self.client.post("/api/auth/login", json=credentials) if response.status_code == 200: self.token = response.json().get('token') self.headers = {'Authorization': f'Bearer {self.token}'} else: self.token = None self.headers = {} @task(3) def get_products(self): with self.client.get("/api/products", headers=self.headers, name="Get Products") as response: if response.status_code != 200: response.failure(f"Get products failed: {response.status_code}") @task(2) def get_product_details(self): product_id = random.choice(PRODUCT_IDS) with self.client.get(f"/api/products/{product_id}", headers=self.headers, name="Get Product Details") as response: if response.status_code != 200: response.failure(f"Get product details failed: {response.status_code}") @task(1) def create_order(self): order_data = { "productId": random.choice(PRODUCT_IDS), "quantity": random.randint(1, 5), "notes": f"Test order from virtual user" } with self.client.post("/api/orders", json=order_data, headers=self.headers, name="Create Order") as response: if response.status_code != 201: response.failure(f"Create order failed: {response.status_code}") @task(1) def get_user_profile(self): with self.client.get("/api/users/profile", headers=self.headers, name="Get User Profile") as response: if response.status_code != 200: response.failure(f"Get user profile failed: {response.status_code}")

Advanced Scenario Test

# src/test/locust/advanced_scenario_test.py from locust import HttpUser, task, between, TaskSet import random import json class BrowseProductsTaskSet(TaskSet): @task(3) def view_product_list(self): self.client.get("/api/products", name="View Product List") @task(2) def view_product_details(self): product_id = random.randint(1, 100) self.client.get(f"/api/products/{product_id}", name="View Product Details") @task(1) def search_products(self): search_terms = ["laptop", "phone", "tablet", "watch", "camera"] term = random.choice(search_terms) self.client.get(f"/api/products/search?q={term}", name="Search Products") @task(1) def stop(self): self.interrupt() class UserAccountTaskSet(TaskSet): def on_start(self): self.login() def login(self): credentials = { "username": f"testuser{random.randint(1, 1000)}@example.com", "password": "testpass123" } response = self.client.post("/api/auth/login", json=credentials) if response.status_code == 200: self.token = response.json().get('token') self.headers = {'Authorization': f'Bearer {self.token}'} else: self.token = None self.headers = {} @task(2) def view_profile(self): self.client.get("/api/users/profile", headers=self.headers, name="View Profile") @task(1) def update_profile(self): profile_data = { "name": f"Updated User {random.randint(1, 1000)}", "email": f"updated{random.randint(1, 1000)}@example.com" } self.client.put("/api/users/profile", json=profile_data, headers=self.headers, name="Update Profile") @task(1) def stop(self): self.interrupt() class OrderTaskSet(TaskSet): @task(2) def view_orders(self): self.client.get("/api/orders", headers=self.parent.headers, name="View Orders") @task(1) def create_order(self): order_data = { "items": [ { "productId": random.randint(1, 100), "quantity": random.randint(1, 3) } ], "shippingAddress": { "street": "123 Test St", "city": "Test City", "zipCode": "12345" } } self.client.post("/api/orders", json=order_data, headers=self.parent.headers, name="Create Order") @task(1) def stop(self): self.interrupt() class WebsiteUser(HttpUser): wait_time = between(2, 5) tasks = [BrowseProductsTaskSet, UserAccountTaskSet, OrderTaskSet] def on_start(self): self.headers = {}

6. Results Analysis and Reporting

// LocustResultsAnalyzer.java package com.example.locust.analysis; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @Component public class LocustResultsAnalyzer { private static final Logger logger = LoggerFactory.getLogger(LocustResultsAnalyzer.class); private final ObjectMapper objectMapper; public LocustResultsAnalyzer(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public LocustTestAnalysis analyzeCsvResults(String resultsDirectory) { try { Path statsPath = Paths.get(resultsDirectory + "_stats.csv"); Path failuresPath = Paths.get(resultsDirectory + "_failures.csv"); Path historyPath = Paths.get(resultsDirectory + "_stats_history.csv"); LocustTestAnalysis analysis = new LocustTestAnalysis(); analysis.setResultsDirectory(resultsDirectory); if (Files.exists(statsPath)) { analyzeStatsCsv(statsPath, analysis); } if (Files.exists(failuresPath)) { analyzeFailuresCsv(failuresPath, analysis); } if (Files.exists(historyPath)) { analyzeHistoryCsv(historyPath, analysis); } return analysis; } catch (IOException e) { logger.error("Failed to analyze Locust CSV results", e); throw new RuntimeException("Failed to analyze Locust results", e); } } public LocustTestAnalysis analyzeJsonResults(String jsonFile) { try { Path jsonPath = Paths.get(jsonFile); String content = Files.readString(jsonPath); JsonNode rootNode = objectMapper.readTree(content); LocustTestAnalysis analysis = new LocustTestAnalysis(); analysis.setResultsDirectory(jsonFile); extractStatsFromJson(rootNode, analysis); extractErrorsFromJson(rootNode, analysis); return analysis; } catch (IOException e) { logger.error("Failed to analyze Locust JSON results: {}", jsonFile, e); throw new RuntimeException("Failed to analyze Locust JSON results", e); } } private void analyzeStatsCsv(Path statsPath, LocustTestAnalysis analysis) throws IOException { List<String> lines = Files.readAllLines(statsPath); if (lines.size() < 2) return; // Skip header for (int i = 1; i < lines.size(); i++) { String[] columns = lines.get(i).split(","); if (columns.length >= 11) { RequestStats stats = parseRequestStats(columns); analysis.addRequestStats(stats); } } } private void analyzeFailuresCsv(Path failuresPath, LocustTestAnalysis analysis) throws IOException { if (!Files.exists(failuresPath)) return; List<String> lines = Files.readAllLines(failuresPath); if (lines.size() < 2) return; for (int i = 1; i < lines.size(); i++) { String[] columns = lines.get(i).split(","); if (columns.length >= 4) { ErrorStats error = parseErrorStats(columns); analysis.addErrorStats(error); } } } private void analyzeHistoryCsv(Path historyPath, LocustTestAnalysis analysis) throws IOException { if (!Files.exists(historyPath)) return; List<String> lines = Files.readAllLines(historyPath); if (lines.size() < 2) return; List<TimeSeriesPoint> timeSeries = new ArrayList<>(); for (int i = 1; i < lines.size(); i++) { String[] columns = lines.get(i).split(","); if (columns.length >= 8) { TimeSeriesPoint point = parseTimeSeriesPoint(columns); timeSeries.add(point); } } analysis.setTimeSeries(timeSeries); } private RequestStats parseRequestStats(String[] columns) { RequestStats stats = new RequestStats(); stats.setName(columns[0].replace("\"", "")); stats.setMethod(columns[1].replace("\"", "")); stats.setNumRequests(Long.parseLong(columns[2])); stats.setNumFailures(Long.parseLong(columns[3])); stats.setMedianResponseTime(Integer.parseInt(columns[4])); stats.setAverageResponseTime(Double.parseDouble(columns[5])); stats.setMinResponseTime(Integer.parseInt(columns[6])); stats.setMaxResponseTime(Integer.parseInt(columns[7])); stats.setRequestsPerSecond(Double.parseDouble(columns[8])); stats.setFailuresPerSecond(Double.parseDouble(columns[9])); if (columns.length > 10) { String percentileStr = columns[10].replace("\"", "").replace("%", ""); stats.setResponseTimePercentile(Double.parseDouble(percentileStr)); } return stats; } private ErrorStats parseErrorStats(String[] columns) { ErrorStats error = new ErrorStats(); error.setMethod(columns[0].replace("\"", "")); error.setName(columns[1].replace("\"", "")); error.setError(columns[2].replace("\"", "")); error.setOccurrences(Integer.parseInt(columns[3])); return error; } private TimeSeriesPoint parseTimeSeriesPoint(String[] columns) { TimeSeriesPoint point = new TimeSeriesPoint(); point.setTimestamp(columns[0]); point.setCurrentUsers(Integer.parseInt(columns[1])); point.setRequestsPerSecond(Double.parseDouble(columns[5])); point.setFailureRate(Double.parseDouble(columns[6])); point.setAverageResponseTime(Double.parseDouble(columns[7])); return point; } private void extractStatsFromJson(JsonNode rootNode, LocustTestAnalysis analysis) { JsonNode stats = rootNode.path("stats"); if (stats.isArray()) { for (JsonNode statNode : stats) { RequestStats statsObj = new RequestStats(); statsObj.setName(statNode.path("name").asText()); statsObj.setMethod(statNode.path("method").asText()); statsObj.setNumRequests(statNode.path("num_requests").asLong()); statsObj.setNumFailures(statNode.path("num_failures").asLong()); statsObj.setAverageResponseTime(statNode.path("avg_response_time").asDouble()); statsObj.setMinResponseTime(statNode.path("min_response_time").asInt()); statsObj.setMaxResponseTime(statNode.path("max_response_time").asInt()); statsObj.setRequestsPerSecond(statNode.path("current_rps").asDouble()); analysis.addRequestStats(statsObj); } } } private void extractErrorsFromJson(JsonNode rootNode, LocustTestAnalysis analysis) { JsonNode errors = rootNode.path("errors"); if (errors.isArray()) { for (JsonNode errorNode : errors) { ErrorStats error = new ErrorStats(); error.setName(errorNode.path("name").asText()); error.setMethod(errorNode.path("method").asText()); error.setError(errorNode.path("error").asText()); error.setOccurrences(errorNode.path("occurrences").asInt()); analysis.addErrorStats(error); } } } public PerformanceReport generateReport(LocustTestAnalysis analysis) { PerformanceReport report = new PerformanceReport(); report.setAnalysis(analysis); report.setGeneratedAt(new Date()); // Calculate overall metrics calculateOverallMetrics(analysis, report); // Generate recommendations generateRecommendations(analysis, report); // Determine status determineStatus(analysis, report); return report; } private void calculateOverallMetrics(LocustTestAnalysis analysis, PerformanceReport report) { long totalRequests = analysis.getRequestStats().stream() .mapToLong(RequestStats::getNumRequests) .sum(); long totalFailures = analysis.getRequestStats().stream() .mapToLong(RequestStats::getNumFailures) .sum(); double failureRate = totalRequests > 0 ? (double) totalFailures / totalRequests : 0.0; double successRate = 1.0 - failureRate; double avgResponseTime = analysis.getRequestStats().stream() .mapToDouble(RequestStats::getAverageResponseTime) .average() .orElse(0.0); report.setTotalRequests(totalRequests); report.setTotalFailures(totalFailures); report.setFailureRate(failureRate); report.setSuccessRate(successRate); report.setAverageResponseTime(avgResponseTime); } private void generateRecommendations(LocustTestAnalysis analysis, PerformanceReport report) { List<String> recommendations = new ArrayList<>(); // Check for high failure rates if (report.getFailureRate() > 0.05) { recommendations.add("Investigate and fix the root cause of high failure rates"); } // Check for slow endpoints analysis.getRequestStats().stream() .filter(stats -> stats.getAverageResponseTime() > 1000) .forEach(stats -> { recommendations.add(String.format( "Optimize endpoint %s %s (avg response time: %.2f ms)", stats.getMethod(), stats.getName(), stats.getAverageResponseTime())); }); // Check for high error rates on specific endpoints analysis.getRequestStats().stream() .filter(stats -> stats.getNumRequests() > 0) .filter(stats -> (double) stats.getNumFailures() / stats.getNumRequests() > 0.1) .forEach(stats -> { recommendations.add(String.format( "Fix errors in endpoint %s %s (failure rate: %.1f%%)", stats.getMethod(), stats.getName(), (double) stats.getNumFailures() / stats.getNumRequests() * 100)); }); report.setRecommendations(recommendations); } private void determineStatus(LocustTestAnalysis analysis, PerformanceReport report) { if (report.getFailureRate() > 0.1) { report.setStatus(PerformanceReport.ReportStatus.FAILED); } else if (report.getFailureRate() > 0.05 || report.getAverageResponseTime() > 2000) { report.setStatus(PerformanceReport.ReportStatus.WARNING); } else { report.setStatus(PerformanceReport.ReportStatus.PASSED); } } // Data classes public static class LocustTestAnalysis { private String resultsDirectory; private List<RequestStats> requestStats = new ArrayList<>(); private List<ErrorStats> errorStats = new ArrayList<>(); private List<TimeSeriesPoint> timeSeries = new ArrayList<>(); // Getters and setters public String getResultsDirectory() { return resultsDirectory; } public void setResultsDirectory(String resultsDirectory) { this.resultsDirectory = resultsDirectory; } public List<RequestStats> getRequestStats() { return requestStats; } public void setRequestStats(List<RequestStats> requestStats) { this.requestStats = requestStats; } public List<ErrorStats> getErrorStats() { return errorStats; } public void setErrorStats(List<ErrorStats> errorStats) { this.errorStats = errorStats; } public List<TimeSeriesPoint> getTimeSeries() { return timeSeries; } public void setTimeSeries(List<TimeSeriesPoint> timeSeries) { this.timeSeries = timeSeries; } public void addRequestStats(RequestStats stats) { this.requestStats.add(stats); } public void addErrorStats(ErrorStats error) { this.errorStats.add(error); } } public static class RequestStats { private String name; private String method; private long numRequests; private long numFailures; private int medianResponseTime; private double averageResponseTime; private int minResponseTime; private int maxResponseTime; private double requestsPerSecond; private double failuresPerSecond; private double responseTimePercentile; // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public long getNumRequests() { return numRequests; } public void setNumRequests(long numRequests) { this.numRequests = numRequests; } public long getNumFailures() { return numFailures; } public void setNumFailures(long numFailures) { this.numFailures = numFailures; } public int getMedianResponseTime() { return medianResponseTime; } public void setMedianResponseTime(int medianResponseTime) { this.medianResponseTime = medianResponseTime; } public double getAverageResponseTime() { return averageResponseTime; } public void setAverageResponseTime(double averageResponseTime) { this.averageResponseTime = averageResponseTime; } public int getMinResponseTime() { return minResponseTime; } public void setMinResponseTime(int minResponseTime) { this.minResponseTime = minResponseTime; } public int getMaxResponseTime() { return maxResponseTime; } public void setMaxResponseTime(int maxResponseTime) { this.maxResponseTime = maxResponseTime; } public double getRequestsPerSecond() { return requestsPerSecond; } public void setRequestsPerSecond(double requestsPerSecond) { this.requestsPerSecond = requestsPerSecond; } public double getFailuresPerSecond() { return failuresPerSecond; } public void setFailuresPerSecond(double failuresPerSecond) { this.failuresPerSecond = failuresPerSecond; } public double getResponseTimePercentile() { return responseTimePercentile; } public void setResponseTimePercentile(double responseTimePercentile) { this.responseTimePercentile = responseTimePercentile; } } public static class ErrorStats { private String method; private String name; private String error; private int occurrences; // Getters and setters public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getError() { return error; } public void setError(String error) { this.error = error; } public int getOccurrences() { return occurrences; } public void setOccurrences(int occurrences) { this.occurrences = occurrences; } } public static class TimeSeriesPoint { private String timestamp; private int currentUsers; private double requestsPerSecond; private double failureRate; private double averageResponseTime; // Getters and setters public String getTimestamp() { return timestamp; } public void setTimestamp(String timestamp) { this.timestamp = timestamp; } public int getCurrentUsers() { return currentUsers; } public void setCurrentUsers(int currentUsers) { this.currentUsers = currentUsers; } public double getRequestsPerSecond() { return requestsPerSecond; } public void setRequestsPerSecond(double requestsPerSecond) { this.requestsPerSecond = requestsPerSecond; } public double getFailureRate() { return failureRate; } public void setFailureRate(double failureRate) { this.failureRate = failureRate; } public double getAverageResponseTime() { return averageResponseTime; } public void setAverageResponseTime(double averageResponseTime) { this.averageResponseTime = averageResponseTime; } } public static class PerformanceReport { public enum ReportStatus { PASSED, WARNING, FAILED } private LocustTestAnalysis analysis; private Date generatedAt; private ReportStatus status; private long totalRequests; private long totalFailures; private double failureRate; private double successRate; private double averageResponseTime; private List<String> recommendations = new ArrayList<>(); // Getters and setters public LocustTestAnalysis getAnalysis() { return analysis; } public void setAnalysis(LocustTestAnalysis analysis) { this.analysis = analysis; } public Date getGeneratedAt() { return generatedAt; } public void setGeneratedAt(Date generatedAt) { this.generatedAt = generatedAt; } public ReportStatus getStatus() { return status; } public void setStatus(ReportStatus status) { this.status = status; } public long getTotalRequests() { return totalRequests; } public void setTotalRequests(long totalRequests) { this.totalRequests = totalRequests; } public long getTotalFailures() { return totalFailures; } public void setTotalFailures(long totalFailures) { this.totalFailures = totalFailures; } public double getFailureRate() { return failureRate; } public void setFailureRate(double failureRate) { this.failureRate = failureRate; } public double getSuccessRate() { return successRate; } public void setSuccessRate(double successRate) { this.successRate = successRate; } public double getAverageResponseTime() { return averageResponseTime; } public void setAverageResponseTime(double averageResponseTime) { this.averageResponseTime = averageResponseTime; } public List<String> getRecommendations() { return recommendations; } public void setRecommendations(List<String> recommendations) { this.recommendations = recommendations; } } }

7. JUnit 5 Test Integration

// LocustPerformanceTest.java package com.example.locust.test; import com.example.locust.generator.LocustTestGenerator; import com.example.locust.runner.LocustTestRunner; import com.example.locust.analysis.LocustResultsAnalyzer; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.GenericContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest @Testcontainers @TestInstance(TestInstance.Lifecycle.PER_CLASS) class LocustPerformanceTest { @Container static GenericContainer<?> locustContainer = new GenericContainer<>("locustio/locust") .withExposedPorts(8089) .withCommand("-f /locust-scripts/basic_api_test.py --host http://host.docker.internal:8080"); @Autowired private LocustTestGenerator testGenerator; @Autowired private LocustTestRunner testRunner; @Autowired private LocustResultsAnalyzer resultsAnalyzer; @BeforeAll void setup() { generateTestScripts(); } @Test @DisplayName("Basic API Load Test - 100 Users") void testBasicApiLoad() { LocustTestRunner.LocustOptions options = new LocustTestRunner.LocustOptions(); options.setNumUsers(100); options.setSpawnRate(10); options.setRunTime("2m"); options.setOnlySummary(true); LocustTestRunner.TestExecutionResult result = testRunner.runLocustTest( "basic_api_test.py", options); assertTrue(result.isSuccess(), "Locust test should complete successfully"); assertFalse(result.isTimeout(), "Locust test should not timeout"); // Analyze results LocustResultsAnalyzer.LocustTestAnalysis analysis = resultsAnalyzer.analyzeCsvResults(result.getResultsDirectory()); LocustResultsAnalyzer.PerformanceReport report = resultsAnalyzer.generateReport(analysis); assertEquals(LocustResultsAnalyzer.PerformanceReport.ReportStatus.PASSED, report.getStatus(), "Performance test should pass"); assertTrue(report.getSuccessRate() >= 0.95, "Success rate should be at least 95%"); } @Test @DisplayName("Advanced Scenario Test") void testAdvancedScenario() { LocustTestRunner.TestExecutionResult result = testRunner.runLocustTest( "advanced_scenario_test.py", new LocustTestRunner.LocustOptions()); assertTrue(result.isSuccess(), "Advanced scenario test should complete successfully"); LocustResultsAnalyzer.LocustTestAnalysis analysis = resultsAnalyzer.analyzeCsvResults(result.getResultsDirectory()); LocustResultsAnalyzer.PerformanceReport report = resultsAnalyzer.generateReport(analysis); System.out.println("Performance Report:"); System.out.println("Total Requests: " + report.getTotalRequests()); System.out.println("Success Rate: " + String.format("%.2f%%", report.getSuccessRate() * 100)); System.out.println("Average Response Time: " + report.getAverageResponseTime() + "ms"); System.out.println("Recommendations: " + report.getRecommendations()); assertTrue(report.getSuccessRate() >= 0.90, "Advanced scenario should meet minimum success rate"); } @Test @DisplayName("Docker-based Load Test") void testDockerLoadTest() { LocustTestRunner.LocustOptions options = new LocustTestRunner.LocustOptions(); options.setNumUsers(50); options.setSpawnRate(5); options.setRunTime("1m"); LocustTestRunner.TestExecutionResult result = testRunner.runLocustWithDocker( "basic_api_test.py", options); assertTrue(result.isSuccess(), "Docker-based test should complete successfully"); } private void generateTestScripts() { // Generate a programmatic test script LocustTestGenerator.LocustTestConfig config = new LocustTestGenerator.LocustTestConfig(); // Add user credentials for (int i = 1; i <= 10; i++) { Map<String, String> creds = new HashMap<>(); creds.put("username", "testuser" + i + "@example.com"); creds.put("password", "password123"); config.getUserCredentials().add(creds); } // Create API task set LocustTestGenerator.TaskSetConfig apiTasks = new LocustTestGenerator.TaskSetConfig("ApiTaskSet"); LocustTestGenerator.TaskConfig getProducts = new LocustTestGenerator.TaskConfig(); getProducts.setName("get_products"); getProducts.setWeight(3); getProducts.setMethod("GET"); getProducts.setUrl("/api/products"); getProducts.setExpectedStatus(200); apiTasks.getTasks().add(getProducts); LocustTestGenerator.TaskConfig getUserProfile = new LocustTestGenerator.TaskConfig(); getUserProfile.setName("get_user_profile"); getUserProfile.setWeight(2); getUserProfile.setMethod("GET"); getUserProfile.setUrl("/api/users/profile"); getUserProfile.setExpectedStatus(200); Map<String, String> authHeaders = new HashMap<>(); authHeaders.put("Authorization", "Bearer ${token}"); getUserProfile.setHeaders(authHeaders); apiTasks.getTasks().add(getUserProfile); config.getTaskSets().add(apiTasks); // Create user class LocustTestGenerator.UserClassConfig apiUser = new LocustTestGenerator.UserClassConfig("ApiUser"); apiUser.setTaskSet("ApiTaskSet"); apiUser.setSetup(true); config.getUserClasses().add(apiUser); testGenerator.generateAndSaveTestScript(config, "generated_api_test.py"); } @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("locust.scripts.directory", () -> "src/test/locust"); registry.add("locust.results.directory", () -> "target/locust-results"); } }

This comprehensive Locust integration provides:

  • Python script generation from Java configuration
  • Multiple execution modes (local, Docker, distributed)
  • Results analysis and performance reporting
  • Custom task definitions for complex scenarios
  • JUnit 5 integration for CI/CD pipelines
  • Real-time metrics collection and analysis
  • Distributed testing capabilities with Locust4J
  • Comprehensive reporting with recommendations

The setup enables robust performance testing of Java applications using Locust's powerful load testing capabilities with the convenience of Java configuration and analysis.

Leave a Reply

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


Macro Nepal Helper