Skip to content

Commit 6f2de7a

Browse files
committed
feat: JMeter performance test suite with parameterized plan and CI dashboard
0 parents  commit 6f2de7a

4 files changed

Lines changed: 250 additions & 0 deletions

File tree

.github/workflows/performance.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Performance Test
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
workflow_dispatch:
9+
inputs:
10+
users:
11+
description: 'Virtual users (threads)'
12+
default: '20'
13+
rampup:
14+
description: 'Ramp-up (seconds)'
15+
default: '10'
16+
loops:
17+
description: 'Loops per user'
18+
default: '5'
19+
20+
jobs:
21+
load-test:
22+
name: JMeter load test
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- name: Set up JDK 17
28+
uses: actions/setup-java@v4
29+
with:
30+
distribution: temurin
31+
java-version: '17'
32+
33+
- name: Install Apache JMeter
34+
run: |
35+
JMETER_VERSION=5.6.3
36+
curl -L -o jmeter.tgz "https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz"
37+
tar -xzf jmeter.tgz
38+
echo "$PWD/apache-jmeter-${JMETER_VERSION}/bin" >> "$GITHUB_PATH"
39+
40+
- name: Run test plan (non-GUI)
41+
run: |
42+
mkdir -p results
43+
jmeter -n -t test-plan/api-load-test.jmx \
44+
-Jusers=${{ github.event.inputs.users || '20' }} \
45+
-Jrampup=${{ github.event.inputs.rampup || '10' }} \
46+
-Jloops=${{ github.event.inputs.loops || '5' }} \
47+
-l results/result.jtl \
48+
-e -o results/dashboard
49+
50+
- name: Upload HTML dashboard
51+
if: always()
52+
uses: actions/upload-artifact@v4
53+
with:
54+
name: jmeter-dashboard
55+
path: results/

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# JMeter results / reports
2+
results/
3+
*.jtl
4+
*.log
5+
jmeter.log
6+
7+
# JMeter binaries (downloaded in CI)
8+
apache-jmeter-*/
9+
*.tgz
10+
11+
# OS / IDE
12+
.DS_Store
13+
Thumbs.db
14+
.idea/
15+
.vscode/

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# ⚡ Performance Testing — Apache JMeter
2+
3+
[![JMeter](https://img.shields.io/badge/Apache%20JMeter-5.6-D22128?logo=apachejmeter&logoColor=white)](https://jmeter.apache.org/)
4+
[![CI](https://github.com/Joselv1990/performance-jmeter/actions/workflows/performance.yml/badge.svg)](../../actions)
5+
6+
Testes de **performance e carga** de APIs REST com **Apache JMeter**: plano de teste parametrizado,
7+
assertions de SLA, execução headless (não-GUI) e **dashboard HTML** publicado no CI.
8+
9+
> 🇬🇧 *API performance & load testing with Apache JMeter — parameterized test plan, SLA assertions, headless execution and an HTML dashboard published in CI.*
10+
11+
App alvo: [JSONPlaceholder](https://jsonplaceholder.typicode.com) (API REST pública e estável).
12+
13+
---
14+
15+
## ✨ Destaques técnicos
16+
17+
- **Plano parametrizado** — usuários, ramp-up e loops definidos por propriedades (`-Jusers`, `-Jrampup`, `-Jloops`); host/protocolo configuráveis.
18+
- **Response Assertions** — valida status `200` e conteúdo do corpo.
19+
- **Duration Assertion** — SLA de tempo de resposta (< 2s).
20+
- **Execução não-GUI** — modo recomendado para carga real, gera `.jtl`.
21+
- **Dashboard HTML** — relatório visual gerado automaticamente (latência, throughput, percentis).
22+
- **CI** — GitHub Actions roda o plano e publica o relatório como artifact.
23+
24+
## 🧱 Estrutura
25+
26+
```
27+
test-plan/
28+
└── api-load-test.jmx → plano de teste (Thread Group, samplers, assertions)
29+
.github/workflows/
30+
└── performance.yml → execução headless + dashboard no CI
31+
```
32+
33+
## ✅ Pré-requisitos
34+
35+
- **JDK 8+**
36+
- **Apache JMeter 5.6+** ([download](https://jmeter.apache.org/download_jmeter.cgi))
37+
38+
## 🚀 Como executar
39+
40+
### Modo GUI (desenvolvimento do plano)
41+
42+
```bash
43+
jmeter -t test-plan/api-load-test.jmx
44+
```
45+
46+
### Modo não-GUI (carga real + relatório)
47+
48+
```bash
49+
jmeter -n -t test-plan/api-load-test.jmx \
50+
-Jusers=50 -Jrampup=10 -Jloops=10 \
51+
-l results/result.jtl \
52+
-e -o results/dashboard
53+
```
54+
55+
Abra `results/dashboard/index.html` para ver o relatório.
56+
57+
### Parâmetros
58+
59+
| Propriedade | Padrão | Descrição |
60+
|-------------|--------|------------------------------|
61+
| `users` | 10 | usuários virtuais (threads) |
62+
| `rampup` | 5 | tempo de ramp-up (s) |
63+
| `loops` | 5 | iterações por usuário |
64+
| `host` | jsonplaceholder.typicode.com | host alvo |
65+
| `protocol` | https | http/https |
66+
67+
## 📊 Métricas avaliadas
68+
69+
- Throughput (req/s) · Tempo de resposta médio e percentis (p90/p95/p99) · Taxa de erro · APDEX
70+
71+
---
72+
73+
## 👤 Autor
74+
75+
**Jose Luis Vieira** — QA Engineer | Automation (Web, Mobile & API)
76+
[GitHub](https://github.com/Joselv1990) · [LinkedIn](https://www.linkedin.com/in/jose-luis-vieira)

test-plan/api-load-test.jmx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
3+
<hashTree>
4+
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="API Load Test - JSONPlaceholder" enabled="true">
5+
<stringProp name="TestPlan.comments">Parameterized load test. Override at runtime, e.g. -Jusers=50 -Jrampup=10 -Jloops=10</stringProp>
6+
<boolProp name="TestPlan.functional_mode">false</boolProp>
7+
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
8+
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
9+
<collectionProp name="Arguments.arguments">
10+
<elementProp name="HOST" elementType="Argument">
11+
<stringProp name="Argument.name">HOST</stringProp>
12+
<stringProp name="Argument.value">${__P(host,jsonplaceholder.typicode.com)}</stringProp>
13+
<stringProp name="Argument.metadata">=</stringProp>
14+
</elementProp>
15+
<elementProp name="PROTOCOL" elementType="Argument">
16+
<stringProp name="Argument.name">PROTOCOL</stringProp>
17+
<stringProp name="Argument.value">${__P(protocol,https)}</stringProp>
18+
<stringProp name="Argument.metadata">=</stringProp>
19+
</elementProp>
20+
</collectionProp>
21+
</elementProp>
22+
</TestPlan>
23+
<hashTree>
24+
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
25+
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
26+
<collectionProp name="Arguments.arguments"/>
27+
</elementProp>
28+
<stringProp name="HTTPSampler.domain">${HOST}</stringProp>
29+
<stringProp name="HTTPSampler.protocol">${PROTOCOL}</stringProp>
30+
<stringProp name="HTTPSampler.connect_timeout">5000</stringProp>
31+
<stringProp name="HTTPSampler.response_timeout">10000</stringProp>
32+
</ConfigTestElement>
33+
<hashTree/>
34+
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header Manager" enabled="true">
35+
<collectionProp name="HeaderManager.headers">
36+
<elementProp name="" elementType="Header">
37+
<stringProp name="Header.name">Accept</stringProp>
38+
<stringProp name="Header.value">application/json</stringProp>
39+
</elementProp>
40+
</collectionProp>
41+
</HeaderManager>
42+
<hashTree/>
43+
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="API Users" enabled="true">
44+
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
45+
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
46+
<boolProp name="LoopController.continue_forever">false</boolProp>
47+
<stringProp name="LoopController.loops">${__P(loops,5)}</stringProp>
48+
</elementProp>
49+
<stringProp name="ThreadGroup.num_threads">${__P(users,10)}</stringProp>
50+
<stringProp name="ThreadGroup.ramp_time">${__P(rampup,5)}</stringProp>
51+
<boolProp name="ThreadGroup.scheduler">false</boolProp>
52+
<stringProp name="ThreadGroup.duration"></stringProp>
53+
<stringProp name="ThreadGroup.delay"></stringProp>
54+
</ThreadGroup>
55+
<hashTree>
56+
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /posts" enabled="true">
57+
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
58+
<collectionProp name="Arguments.arguments"/>
59+
</elementProp>
60+
<stringProp name="HTTPSampler.path">/posts</stringProp>
61+
<stringProp name="HTTPSampler.method">GET</stringProp>
62+
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
63+
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
64+
</HTTPSamplerProxy>
65+
<hashTree>
66+
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Status 200" enabled="true">
67+
<collectionProp name="Asserion.test_strings">
68+
<stringProp name="49586">200</stringProp>
69+
</collectionProp>
70+
<stringProp name="Assertion.custom_message">Expected HTTP 200 on /posts</stringProp>
71+
<stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
72+
<boolProp name="Assertion.assume_success">false</boolProp>
73+
<intProp name="Assertion.test_type">8</intProp>
74+
</ResponseAssertion>
75+
<hashTree/>
76+
<DurationAssertion guiclass="DurationAssertionGui" testclass="DurationAssertion" testname="Under 2s" enabled="true">
77+
<stringProp name="DurationAssertion.duration">2000</stringProp>
78+
</DurationAssertion>
79+
<hashTree/>
80+
</hashTree>
81+
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GET /posts/1" enabled="true">
82+
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
83+
<collectionProp name="Arguments.arguments"/>
84+
</elementProp>
85+
<stringProp name="HTTPSampler.path">/posts/1</stringProp>
86+
<stringProp name="HTTPSampler.method">GET</stringProp>
87+
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
88+
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
89+
</HTTPSamplerProxy>
90+
<hashTree>
91+
<ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Body contains userId" enabled="true">
92+
<collectionProp name="Asserion.test_strings">
93+
<stringProp name="-836313440">userId</stringProp>
94+
</collectionProp>
95+
<stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
96+
<boolProp name="Assertion.assume_success">false</boolProp>
97+
<intProp name="Assertion.test_type">16</intProp>
98+
</ResponseAssertion>
99+
<hashTree/>
100+
</hashTree>
101+
</hashTree>
102+
</hashTree>
103+
</hashTree>
104+
</jmeterTestPlan>

0 commit comments

Comments
 (0)