google-site-verification: google61fe8ba583a51912.html
Performance Testing with k6 in Java

Comprehensive k6 Integration Guide for Java Applications

1. k6 Test Configuration and Setup

Maven Dependencies and Plugin Configuration

<!-- pom.xml --> <properties> <k6.version>0.0.9</k6.version> <gatling.version>3.9.5</gatling.version> <jmeter.version>5.6.2</jmeter.version> </properties> <dependencies> <!-- k6 Java API --> <dependency> <groupId>io.github.k6</groupId> <artifactId>k6-java-api</artifactId> <version>${k6.version}</version> <scope>test</scope> </dependency> <!-- Test Containers for k6 --> <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.19.3</version> <scope>test</scope> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Metrics Collection --> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> <version>1.11.5</version> </dependency> </dependencies> <build> <plugins> <!-- k6 Maven Plugin --> <plugin> <groupId>io.github.k6</groupId> <artifactId>k6-maven-plugin</artifactId> <version>${k6.version}</version> <configuration> <testFile>src/test/k6/performance-test.js</testFile> <output>results/performance-test.json</output> <quiet>false</quiet> </configuration> <executions> <execution> <id>performance-test</id> <phase>verify</phase> <goals> <goal>run</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

2. k6 Test Script Generator

// K6TestGenerator.java package com.example.k6.generator; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; 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 K6TestGenerator { private static final Logger logger = LoggerFactory.getLogger(K6TestGenerator.class); private final ObjectMapper objectMapper; private final ResourceLoader resourceLoader; @Value("${k6.scripts.directory:src/test/k6}") private String scriptsDirectory; public K6TestGenerator(ObjectMapper objectMapper, ResourceLoader resourceLoader) { this.objectMapper = objectMapper; this.resourceLoader = resourceLoader; } public String generateLoadTestScript(LoadTestConfig config) { StringBuilder script = new StringBuilder(); // Import statements script.append("import http from 'k6/http';\n"); script.append("import { check, sleep } from 'k6';\n"); script.append("import { Trend, Counter, Rate } from 'k6/metrics';\n\n"); // Custom metrics script.append("// Custom Metrics\n"); script.append("const responseTimeTrend = new Trend('response_time');\n"); script.append("const successRate = new Rate('success_rate');\n"); script.append("const errorCounter = new Counter('errors');\n\n"); // Test configuration script.append("// Test Configuration\n"); script.append("export const options = {\n"); script.append(generateOptions(config)); script.append("};\n\n"); // Test scenarios script.append("// Test Scenarios\n"); script.append(generateScenarios(config)); // Setup function (if needed) if (config.hasSetup()) { script.append(generateSetupFunction(config)); } // Main test function script.append(generateMainTestFunction(config)); // Teardown function (if needed) if (config.hasTeardown()) { script.append(generateTeardownFunction(config)); } return script.toString(); } private String generateOptions(LoadTestConfig config) { StringBuilder options = new StringBuilder(); options.append(" stages: [\n"); for (LoadTestConfig.Stage stage : config.getStages()) { options.append(String.format(" { duration: '%s', target: %d },\n", stage.getDuration(), stage.getTarget())); } options.append(" ],\n"); options.append(String.format(" thresholds: {\n")); options.append(String.format(" 'http_req_duration': ['p(95)<%d'],\n", config.getThresholds().getResponseTime95p())); options.append(String.format(" 'http_req_failed': ['rate<%.2f'],\n", config.getThresholds().getErrorRate())); options.append(String.format(" 'success_rate': ['rate>%.2f'],\n", config.getThresholds().getSuccessRate())); options.append(" },\n"); return options.toString(); } private String generateScenarios(LoadTestConfig config) { StringBuilder scenarios = new StringBuilder(); for (LoadTestConfig.Scenario scenario : config.getScenarios()) { scenarios.append(String.format("// Scenario: %s\n", scenario.getName())); scenarios.append(String.format("function %s() {\n", scenario.getFunctionName())); scenarios.append(generateScenarioSteps(scenario)); scenarios.append("}\n\n"); } return scenarios.toString(); } private String generateScenarioSteps(LoadTestConfig.Scenario scenario) { StringBuilder steps = new StringBuilder(); for (LoadTestConfig.Step step : scenario.getSteps()) { switch (step.getType()) { case "http": steps.append(generateHttpStep(step)); break; case "sleep": steps.append(String.format(" sleep(%d);\n", step.getDuration())); break; case "check": steps.append(generateCheckStep(step)); break; } } return steps.toString(); } private String generateHttpStep(LoadTestConfig.Step step) { StringBuilder httpStep = new StringBuilder(); httpStep.append(String.format(" const response = http.%s('%s', ", step.getMethod().toLowerCase(), step.getUrl())); if (step.getBody() != null) { httpStep.append(step.getBody()); } if (step.getParams() != null && !step.getParams().isEmpty()) { httpStep.append(", { headers: { "); step.getParams().forEach((key, value) -> { httpStep.append(String.format("'%s': '%s', ", key, value)); }); httpStep.append("} }"); } httpStep.append(");\n\n"); // Add checks httpStep.append(" check(response, {\n"); httpStep.append(String.format(" 'status is %d': (r) => r.status === %d,\n", step.getExpectedStatus(), step.getExpectedStatus())); if (step.getExpectedBody() != null) { httpStep.append(String.format(" 'response body contains expected data': (r) => r.body.includes('%s'),\n", step.getExpectedBody())); } httpStep.append(" });\n\n"); // Record metrics httpStep.append(" responseTimeTrend.add(response.timings.duration);\n"); httpStep.append(String.format(" successRate.add(response.status === %d);\n", step.getExpectedStatus())); httpStep.append(" if (response.status !== 200) {\n"); httpStep.append(" errorCounter.add(1);\n"); httpStep.append(" }\n\n"); return httpStep.toString(); } private String generateCheckStep(LoadTestConfig.Step step) { return String.format(" check(null, { '%s': () => true });\n", step.getName()); } private String generateMainTestFunction(LoadTestConfig config) { StringBuilder mainFunction = new StringBuilder(); mainFunction.append("// Main Test Function\n"); mainFunction.append("export default function() {\n"); for (LoadTestConfig.Scenario scenario : config.getScenarios()) { if (scenario.getWeight() > 0) { mainFunction.append(String.format(" %s();\n", scenario.getFunctionName())); } } mainFunction.append("}\n"); return mainFunction.toString(); } private String generateSetupFunction(LoadTestConfig config) { return String.format(""" // Setup Function export function setup() { // Setup code here return { token: 'test-token' }; } """); } private String generateTeardownFunction(LoadTestConfig config) { return """ // Teardown Function export function teardown(data) { // Cleanup code here } """; } public void generateAndSaveTestScript(LoadTestConfig config, String filename) { try { String script = generateLoadTestScript(config); Path scriptPath = Paths.get(scriptsDirectory, filename); Files.createDirectories(scriptPath.getParent()); Files.writeString(scriptPath, script); logger.info("Generated k6 test script: {}", scriptPath); } catch (IOException e) { logger.error("Failed to generate k6 test script", e); throw new RuntimeException("Failed to generate k6 test script", e); } } public static class LoadTestConfig { private List<Stage> stages = new ArrayList<>(); private List<Scenario> scenarios = new ArrayList<>(); private Thresholds thresholds = new Thresholds(); private boolean setup = false; private boolean teardown = false; // Getters and setters public List<Stage> getStages() { return stages; } public List<Scenario> getScenarios() { return scenarios; } public Thresholds getThresholds() { return thresholds; } public boolean hasSetup() { return setup; } public boolean hasTeardown() { return teardown; } public static class Stage { private String duration; private int target; public Stage(String duration, int target) { this.duration = duration; this.target = target; } // Getters public String getDuration() { return duration; } public int getTarget() { return target; } } public static class Scenario { private String name; private String functionName; private int weight = 1; private List<Step> steps = new ArrayList<>(); public Scenario(String name) { this.name = name; this.functionName = name.toLowerCase().replace(" ", "_"); } // Getters and setters public String getName() { return name; } public String getFunctionName() { return functionName; } public int getWeight() { return weight; } public List<Step> getSteps() { return steps; } } public static class Step { private String type; // http, sleep, check private String name; private String method; private String url; private String body; private Map<String, String> params; private int expectedStatus = 200; private String expectedBody; private int duration; // for sleep steps // Getters and setters public String getType() { return type; } public String getName() { return name; } public String getMethod() { return method; } public String getUrl() { return url; } public String getBody() { return body; } public Map<String, String> getParams() { return params; } public int getExpectedStatus() { return expectedStatus; } public String getExpectedBody() { return expectedBody; } public int getDuration() { return duration; } } public static class Thresholds { private int responseTime95p = 500; private double errorRate = 0.01; private double successRate = 0.95; // Getters public int getResponseTime95p() { return responseTime95p; } public double getErrorRate() { return errorRate; } public double getSuccessRate() { return successRate; } } } }

3. k6 Test Runner Service

// K6TestRunner.java package com.example.k6.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.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; @Service public class K6TestRunner { private static final Logger logger = Logger.getLogger(K6TestRunner.class); @Value("${k6.binary.path:k6}") private String k6BinaryPath; @Value("${k6.scripts.directory:src/test/k6}") private String scriptsDirectory; @Value("${k6.results.directory:target/k6-results}") private String resultsDirectory; private final ExecutorService executorService = Executors.newCachedThreadPool(); public TestExecutionResult runTest(String testScript, TestOptions options) { try { Path scriptPath = Paths.get(scriptsDirectory, testScript); Path outputPath = Paths.get(resultsDirectory, testScript.replace(".js", "-" + System.currentTimeMillis() + ".json")); Files.createDirectories(outputPath.getParent()); List<String> command = buildK6Command(scriptPath, outputPath, options); logger.info("Executing k6 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()); int exitCode = process.waitFor(); TestExecutionResult result = new TestExecutionResult(); result.setExitCode(exitCode); result.setOutput(output); result.setOutputFile(outputPath.toString()); result.setSuccess(exitCode == 0); if (exitCode == 0) { logger.info("k6 test completed successfully: {}", testScript); } else { logger.error("k6 test failed with exit code {}: {}", exitCode, testScript); } return result; } catch (IOException | InterruptedException e) { logger.error("Failed to execute k6 test: {}", testScript, e); throw new RuntimeException("k6 test execution failed", e); } } public CompletableFuture<TestExecutionResult> runTestAsync(String testScript, TestOptions options) { return CompletableFuture.supplyAsync(() -> runTest(testScript, options), executorService); } public TestExecutionResult runTestWithDocker(String testScript, TestOptions options) { try { List<String> command = new ArrayList<>(); command.add("docker"); command.add("run"); command.add("--rm"); command.add("-i"); command.add("-v"); command.add(Paths.get(scriptsDirectory).toAbsolutePath() + ":/scripts"); command.add("-v"); command.add(Paths.get(resultsDirectory).toAbsolutePath() + ":/results"); command.add("grafana/k6:latest"); command.add("run"); command.add("--out"); command.add("json=/results/" + testScript.replace(".js", "-" + System.currentTimeMillis() + ".json")); command.add("/scripts/" + testScript); // Add k6 options if (options.getVus() > 0) { command.add("--vus"); command.add(String.valueOf(options.getVus())); } if (options.getDuration() != null) { command.add("--duration"); command.add(options.getDuration()); } logger.info("Executing k6 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()); int exitCode = process.waitFor(); TestExecutionResult result = new TestExecutionResult(); result.setExitCode(exitCode); result.setOutput(output); result.setSuccess(exitCode == 0); return result; } catch (IOException | InterruptedException e) { logger.error("Failed to execute k6 test with Docker: {}", testScript, e); throw new RuntimeException("k6 Docker test execution failed", e); } } private List<String> buildK6Command(Path scriptPath, Path outputPath, TestOptions options) { List<String> command = new ArrayList<>(); command.add(k6BinaryPath); command.add("run"); command.add("--out"); command.add("json=" + outputPath.toString()); // Add k6 options if (options.getVus() > 0) { command.add("--vus"); command.add(String.valueOf(options.getVus())); } if (options.getIterations() > 0) { command.add("--iterations"); command.add(String.valueOf(options.getIterations())); } if (options.getDuration() != null) { command.add("--duration"); command.add(options.getDuration()); } if (options.getStages() != null && !options.getStages().isEmpty()) { command.add("--stage"); command.add(String.join(",", options.getStages())); } command.add(scriptPath.toString()); 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"); } } return output.toString(); } public static class TestOptions { private int vus; private int iterations; private String duration; private List<String> stages = new ArrayList<>(); // Getters and setters public int getVus() { return vus; } public void setVus(int vus) { this.vus = vus; } public int getIterations() { return iterations; } public void setIterations(int iterations) { this.iterations = iterations; } public String getDuration() { return duration; } public void setDuration(String duration) { this.duration = duration; } public List<String> getStages() { return stages; } public void setStages(List<String> stages) { this.stages = stages; } } public static class TestExecutionResult { private boolean success; private int exitCode; private String output; private String outputFile; private long executionTime; // Getters and setters 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 getOutputFile() { return outputFile; } public void setOutputFile(String outputFile) { this.outputFile = outputFile; } public long getExecutionTime() { return executionTime; } public void setExecutionTime(long executionTime) { this.executionTime = executionTime; } } }

4. Performance Test Results Analyzer

// K6ResultsAnalyzer.java package com.example.k6.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 K6ResultsAnalyzer { private static final Logger logger = LoggerFactory.getLogger(K6ResultsAnalyzer.class); private final ObjectMapper objectMapper; public K6ResultsAnalyzer(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public TestAnalysis analyzeResults(String resultsFile) { try { Path filePath = Paths.get(resultsFile); String content = Files.readString(filePath); JsonNode rootNode = objectMapper.readTree(content); TestAnalysis analysis = new TestAnalysis(); analysis.setTestName(filePath.getFileName().toString()); analysis.setTimestamp(System.currentTimeMillis()); extractMetrics(rootNode, analysis); extractThresholds(rootNode, analysis); extractCustomMetrics(rootNode, analysis); return analysis; } catch (IOException e) { logger.error("Failed to analyze k6 results: {}", resultsFile, e); throw new RuntimeException("Failed to analyze k6 results", e); } } private void extractMetrics(JsonNode rootNode, TestAnalysis analysis) { JsonNode metrics = rootNode.path("metrics"); // HTTP metrics analysis.setHttpRequests(extractMetricValue(metrics, "http_reqs", "count")); analysis.setFailedRequests(extractMetricValue(metrics, "http_req_failed", "rate")); analysis.setTotalDuration(extractMetricValue(metrics, "iteration_duration", "avg")); // Response times analysis.setResponseTimeAvg(extractMetricValue(metrics, "http_req_duration", "avg")); analysis.setResponseTimeP95(extractMetricValue(metrics, "http_req_duration", "p(95)")); analysis.setResponseTimeP99(extractMetricValue(metrics, "http_req_duration", "p(99)")); // Throughput analysis.setRequestsPerSecond(extractMetricValue(metrics, "http_reqs", "rate")); // Data transferred analysis.setDataSent(extractMetricValue(metrics, "data_sent", "count")); analysis.setDataReceived(extractMetricValue(metrics, "data_received", "count")); } private void extractThresholds(JsonNode rootNode, TestAnalysis analysis) { JsonNode thresholds = rootNode.path("thresholds"); List<ThresholdResult> thresholdResults = new ArrayList<>(); if (thresholds.isObject()) { thresholds.fields().forEachRemaining(entry -> { String thresholdName = entry.getKey(); JsonNode thresholdData = entry.getValue(); ThresholdResult result = new ThresholdResult(); result.setName(thresholdName); result.setOk(thresholdData.path("ok").asBoolean()); result.setActualValue(thresholdData.path("values").path("rate").asDouble()); thresholdResults.add(result); }); } analysis.setThresholdResults(thresholdResults); } private void extractCustomMetrics(JsonNode rootNode, TestAnalysis analysis) { JsonNode metrics = rootNode.path("metrics"); Map<String, Object> customMetrics = new HashMap<>(); // Extract custom metrics if (metrics.has("response_time")) { customMetrics.put("response_time_avg", extractMetricValue(metrics, "response_time", "avg")); customMetrics.put("response_time_max", extractMetricValue(metrics, "response_time", "max")); } if (metrics.has("success_rate")) { customMetrics.put("success_rate", extractMetricValue(metrics, "success_rate", "rate")); } if (metrics.has("errors")) { customMetrics.put("total_errors", extractMetricValue(metrics, "errors", "count")); } analysis.setCustomMetrics(customMetrics); } private double extractMetricValue(JsonNode metrics, String metricName, String valueType) { JsonNode metric = metrics.path(metricName); if (metric.isMissingNode()) { return 0.0; } JsonNode values = metric.path("values"); if (values.has(valueType)) { return values.path(valueType).asDouble(); } return 0.0; } public PerformanceReport generateReport(TestAnalysis analysis) { PerformanceReport report = new PerformanceReport(); report.setAnalysis(analysis); report.setGeneratedAt(new Date()); // Calculate performance score double performanceScore = calculatePerformanceScore(analysis); report.setPerformanceScore(performanceScore); // Generate recommendations List<String> recommendations = generateRecommendations(analysis); report.setRecommendations(recommendations); // Determine overall status report.setStatus(determineOverallStatus(analysis)); return report; } private double calculatePerformanceScore(TestAnalysis analysis) { double score = 100.0; // Deduct points for high response times if (analysis.getResponseTimeP95() > 1000) { score -= 20; } else if (analysis.getResponseTimeP95() > 500) { score -= 10; } // Deduct points for high error rate double errorRate = analysis.getFailedRequests(); if (errorRate > 0.05) { score -= 30; } else if (errorRate > 0.01) { score -= 15; } // Deduct points for failed thresholds long failedThresholds = analysis.getThresholdResults().stream() .filter(result -> !result.isOk()) .count(); score -= failedThresholds * 10; return Math.max(0, score); } private List<String> generateRecommendations(TestAnalysis analysis) { List<String> recommendations = new ArrayList<>(); if (analysis.getResponseTimeP95() > 1000) { recommendations.add("Optimize backend services to reduce response times above 1 second"); } if (analysis.getFailedRequests() > 0.05) { recommendations.add("Investigate and fix the root cause of high error rates"); } if (analysis.getRequestsPerSecond() < 10) { recommendations.add("Consider performance optimizations to increase throughput"); } // Check threshold failures analysis.getThresholdResults().stream() .filter(result -> !result.isOk()) .forEach(result -> { recommendations.add(String.format("Threshold failed: %s (actual: %.2f)", result.getName(), result.getActualValue())); }); return recommendations; } private PerformanceReport.ReportStatus determineOverallStatus(TestAnalysis analysis) { long failedThresholds = analysis.getThresholdResults().stream() .filter(result -> !result.isOk()) .count(); if (failedThresholds > 0) { return PerformanceReport.ReportStatus.FAILED; } else if (analysis.getFailedRequests() > 0.01) { return PerformanceReport.ReportStatus.WARNING; } else { return PerformanceReport.ReportStatus.PASSED; } } public static class TestAnalysis { private String testName; private long timestamp; private long httpRequests; private double failedRequests; private double totalDuration; private double responseTimeAvg; private double responseTimeP95; private double responseTimeP99; private double requestsPerSecond; private long dataSent; private long dataReceived; private List<ThresholdResult> thresholdResults = new ArrayList<>(); private Map<String, Object> customMetrics = new HashMap<>(); // Getters and setters public String getTestName() { return testName; } public void setTestName(String testName) { this.testName = testName; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public long getHttpRequests() { return httpRequests; } public void setHttpRequests(long httpRequests) { this.httpRequests = httpRequests; } public double getFailedRequests() { return failedRequests; } public void setFailedRequests(double failedRequests) { this.failedRequests = failedRequests; } public double getTotalDuration() { return totalDuration; } public void setTotalDuration(double totalDuration) { this.totalDuration = totalDuration; } public double getResponseTimeAvg() { return responseTimeAvg; } public void setResponseTimeAvg(double responseTimeAvg) { this.responseTimeAvg = responseTimeAvg; } public double getResponseTimeP95() { return responseTimeP95; } public void setResponseTimeP95(double responseTimeP95) { this.responseTimeP95 = responseTimeP95; } public double getResponseTimeP99() { return responseTimeP99; } public void setResponseTimeP99(double responseTimeP99) { this.responseTimeP99 = responseTimeP99; } public double getRequestsPerSecond() { return requestsPerSecond; } public void setRequestsPerSecond(double requestsPerSecond) { this.requestsPerSecond = requestsPerSecond; } public long getDataSent() { return dataSent; } public void setDataSent(long dataSent) { this.dataSent = dataSent; } public long getDataReceived() { return dataReceived; } public void setDataReceived(long dataReceived) { this.dataReceived = dataReceived; } public List<ThresholdResult> getThresholdResults() { return thresholdResults; } public void setThresholdResults(List<ThresholdResult> thresholdResults) { this.thresholdResults = thresholdResults; } public Map<String, Object> getCustomMetrics() { return customMetrics; } public void setCustomMetrics(Map<String, Object> customMetrics) { this.customMetrics = customMetrics; } } public static class ThresholdResult { private String name; private boolean ok; private double actualValue; // Getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isOk() { return ok; } public void setOk(boolean ok) { this.ok = ok; } public double getActualValue() { return actualValue; } public void setActualValue(double actualValue) { this.actualValue = actualValue; } } public static class PerformanceReport { public enum ReportStatus { PASSED, WARNING, FAILED } private TestAnalysis analysis; private Date generatedAt; private double performanceScore; private ReportStatus status; private List<String> recommendations = new ArrayList<>(); // Getters and setters public TestAnalysis getAnalysis() { return analysis; } public void setAnalysis(TestAnalysis analysis) { this.analysis = analysis; } public Date getGeneratedAt() { return generatedAt; } public void setGeneratedAt(Date generatedAt) { this.generatedAt = generatedAt; } public double getPerformanceScore() { return performanceScore; } public void setPerformanceScore(double performanceScore) { this.performanceScore = performanceScore; } public ReportStatus getStatus() { return status; } public void setStatus(ReportStatus status) { this.status = status; } public List<String> getRecommendations() { return recommendations; } public void setRecommendations(List<String> recommendations) { this.recommendations = recommendations; } } }

5. Sample k6 Test Scripts

Basic Load Test Script

// src/test/k6/basic-load-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; import { Trend, Rate, Counter } from 'k6/metrics'; // Custom metrics const responseTimeTrend = new Trend('response_time'); const successRate = new Rate('success_rate'); const errorCounter = new Counter('errors'); // Test configuration export const options = { stages: [ { duration: '2m', target: 100 }, // Ramp up to 100 users { duration: '5m', target: 100 }, // Stay at 100 users { duration: '2m', target: 200 }, // Ramp up to 200 users { duration: '5m', target: 200 }, // Stay at 200 users { duration: '2m', target: 0 }, // Ramp down to 0 users ], thresholds: { 'http_req_duration': ['p(95)<500', 'p(99)<1000'], 'http_req_failed': ['rate<0.01'], 'success_rate': ['rate>0.95'], 'response_time': ['p(95)<500'] } }; // Setup function export function setup() { const loginResponse = http.post('http://localhost:8080/api/auth/login', JSON.stringify({ username: 'testuser', password: 'testpass' }), { headers: { 'Content-Type': 'application/json' } }); const authToken = loginResponse.json('token'); return { authToken }; } // Main test function export default function(data) { const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${data.authToken}` }; // Test user profile endpoint const profileResponse = http.get('http://localhost:8080/api/users/profile', { headers }); check(profileResponse, { 'profile status is 200': (r) => r.status === 200, 'profile response has user data': (r) => r.json('id') !== undefined, }); responseTimeTrend.add(profileResponse.timings.duration); successRate.add(profileResponse.status === 200); if (profileResponse.status !== 200) { errorCounter.add(1); } // Test products endpoint const productsResponse = http.get('http://localhost:8080/api/products', { headers }); check(productsResponse, { 'products status is 200': (r) => r.status === 200, 'products response has data': (r) => r.json().length > 0, }); responseTimeTrend.add(productsResponse.timings.duration); successRate.add(productsResponse.status === 200); if (productsResponse.status !== 200) { errorCounter.add(1); } sleep(1); }

API Endpoint Test Script

// src/test/k6/api-endpoints-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; import { Trend, Counter, Rate } from 'k6/metrics'; // Custom metrics const authResponseTime = new Trend('auth_response_time'); const userResponseTime = new Trend('user_response_time'); const productResponseTime = new Trend('product_response_time'); const orderResponseTime = new Trend('order_response_time'); const errorRate = new Rate('error_rate'); export const options = { scenarios: { auth_flow: { executor: 'constant-vus', exec: 'authScenario', vus: 10, duration: '5m', }, user_flow: { executor: 'ramping-vus', exec: 'userScenario', stages: [ { duration: '2m', target: 20 }, { duration: '3m', target: 20 }, { duration: '2m', target: 0 }, ], }, browsing_flow: { executor: 'constant-arrival-rate', exec: 'browsingScenario', rate: 10, timeUnit: '1s', duration: '5m', preAllocatedVUs: 10, }, }, thresholds: { 'auth_response_time': ['p(95)<300'], 'user_response_time': ['p(95)<400'], 'product_response_time': ['p(95)<500'], 'order_response_time': ['p(95)<800'], 'error_rate': ['rate<0.02'] } }; export function authScenario() { const loginData = JSON.stringify({ username: `user_${__VU}@test.com`, password: 'password123' }); const params = { headers: { 'Content-Type': 'application/json' }, }; const loginResponse = http.post('http://localhost:8080/api/auth/login', loginData, params); check(loginResponse, { 'auth login status is 200': (r) => r.status === 200, 'auth login has token': (r) => r.json('token') !== undefined, }); authResponseTime.add(loginResponse.timings.duration); errorRate.add(loginResponse.status !== 200); sleep(1); } export function userScenario() { // First login const loginResponse = http.post('http://localhost:8080/api/auth/login', JSON.stringify({ username: `user_${__VU}@test.com`, password: 'password123' }), { headers: { 'Content-Type': 'application/json' } }); const token = loginResponse.json('token'); const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }; // Get user profile const profileResponse = http.get('http://localhost:8080/api/users/profile', { headers }); check(profileResponse, { 'user profile status is 200': (r) => r.status === 200, }); userResponseTime.add(profileResponse.timings.duration); // Update user profile const updateData = JSON.stringify({ name: `Updated User ${__VU}`, email: `updated_${__VU}@test.com` }); const updateResponse = http.put('http://localhost:8080/api/users/profile', updateData, { headers }); check(updateResponse, { 'user update status is 200': (r) => r.status === 200, }); userResponseTime.add(updateResponse.timings.duration); errorRate.add(profileResponse.status !== 200 || updateResponse.status !== 200); sleep(2); } export function browsingScenario() { const headers = { 'Content-Type': 'application/json' }; // Browse products const productsResponse = http.get('http://localhost:8080/api/products', { headers }); check(productsResponse, { 'products list status is 200': (r) => r.status === 200, 'products list has items': (r) => r.json().length > 0, }); productResponseTime.add(productsResponse.timings.duration); // Get product details const productId = Math.floor(Math.random() * 100) + 1; const productResponse = http.get(`http://localhost:8080/api/products/${productId}`, { headers }); check(productResponse, { 'product details status is 200': (r) => r.status === 200, }); productResponseTime.add(productResponse.timings.duration); errorRate.add(productsResponse.status !== 200 || productResponse.status !== 200); sleep(1); }

6. JUnit 5 Test Integration

// K6PerformanceTest.java package com.example.k6.test; import com.example.k6.generator.K6TestGenerator; import com.example.k6.runner.K6TestRunner; import com.example.k6.analysis.K6ResultsAnalyzer; 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 static org.junit.jupiter.api.Assertions.*; @SpringBootTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) class K6PerformanceTest { @Autowired private K6TestGenerator testGenerator; @Autowired private K6TestRunner testRunner; @Autowired private K6ResultsAnalyzer resultsAnalyzer; @BeforeAll void setup() { // Generate test scripts before all tests generateTestScripts(); } @Test @DisplayName("Basic Load Test - 100 Users") void testBasicLoad() { K6TestRunner.TestOptions options = new K6TestRunner.TestOptions(); options.setVus(100); options.setDuration("5m"); K6TestRunner.TestExecutionResult result = testRunner.runTest("basic-load-test.js", options); assertTrue(result.isSuccess(), "k6 test should complete successfully"); // Analyze results K6ResultsAnalyzer.TestAnalysis analysis = resultsAnalyzer.analyzeResults(result.getOutputFile()); K6ResultsAnalyzer.PerformanceReport report = resultsAnalyzer.generateReport(analysis); assertEquals(K6ResultsAnalyzer.PerformanceReport.ReportStatus.PASSED, report.getStatus(), "Performance test should pass all thresholds"); assertTrue(report.getPerformanceScore() >= 80, "Performance score should be at least 80"); } @Test @DisplayName("API Endpoints Performance Test") void testApiEndpoints() { K6TestRunner.TestExecutionResult result = testRunner.runTest("api-endpoints-test.js", new K6TestRunner.TestOptions()); assertTrue(result.isSuccess(), "API endpoints test should complete successfully"); K6ResultsAnalyzer.TestAnalysis analysis = resultsAnalyzer.analyzeResults(result.getOutputFile()); K6ResultsAnalyzer.PerformanceReport report = resultsAnalyzer.generateReport(analysis); System.out.println("Performance Score: " + report.getPerformanceScore()); System.out.println("Recommendations: " + report.getRecommendations()); assertTrue(report.getPerformanceScore() >= 70, "API endpoints should meet minimum performance standards"); } @Test @DisplayName("Stress Test - 500 Concurrent Users") void testStressScenario() { K6TestGenerator.LoadTestConfig config = new K6TestGenerator.LoadTestConfig(); // Define stress test stages config.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("2m", 100)); config.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("3m", 300)); config.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("2m", 500)); config.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("1m", 500)); config.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("2m", 0)); // Generate stress test script testGenerator.generateAndSaveTestScript(config, "stress-test.js"); // Run the test K6TestRunner.TestExecutionResult result = testRunner.runTest("stress-test.js", new K6TestRunner.TestOptions()); assertTrue(result.isSuccess(), "Stress test should complete successfully"); } private void generateTestScripts() { // Generate basic load test K6TestGenerator.LoadTestConfig basicConfig = new K6TestGenerator.LoadTestConfig(); basicConfig.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("1m", 50)); basicConfig.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("3m", 50)); basicConfig.getStages().add(new K6TestGenerator.LoadTestConfig.Stage("1m", 0)); K6TestGenerator.LoadTestConfig.Scenario basicScenario = new K6TestGenerator.LoadTestConfig.Scenario("Basic API Calls"); K6TestGenerator.LoadTestConfig.Step getUsers = new K6TestGenerator.LoadTestConfig.Step(); getUsers.setType("http"); getUsers.setMethod("GET"); getUsers.setUrl("http://localhost:8080/api/users"); getUsers.setExpectedStatus(200); basicScenario.getSteps().add(getUsers); basicConfig.getScenarios().add(basicScenario); testGenerator.generateAndSaveTestScript(basicConfig, "generated-basic-test.js"); } @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("k6.scripts.directory", () -> "src/test/k6"); registry.add("k6.results.directory", () -> "target/k6-results"); } }

This comprehensive k6 integration provides:

  • Test script generation from Java configuration
  • Programmatic test execution with various options
  • Results analysis and performance reporting
  • Custom metrics collection
  • Threshold validation and performance scoring
  • JUnit 5 integration for CI/CD pipelines
  • Docker support for k6 execution
  • Multiple test scenarios (load, stress, spike)

The setup enables comprehensive performance testing of Java applications with detailed analysis and reporting capabilities.

Leave a Reply

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


Macro Nepal Helper