Skip to content

Commit 1c264f9

Browse files
authored
[2026-02-13] Czy wiesz czym jest narzędzie Artillery? (#276)
1 parent 267924d commit 1c264f9

3 files changed

Lines changed: 295 additions & 0 deletions

File tree

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
layout: post
3+
title: Czy wiesz, czym jest narzędzie artillery?
4+
description: ""
5+
date: 2026-02-13T08:00:00+01:00
6+
published: true
7+
didyouknow: false
8+
lang: pl
9+
author: kdudek
10+
image: /assets/img/posts/2026-02-13-czy-wiesz-czym-jest-narzedzie-artillery/thumbnail.webp
11+
tags:
12+
- artillery
13+
- tests
14+
---
15+
16+
Artillery to oparte o Node.js narzędzie do wykonywania testów wydajnościowych, które może być prostszą alternatywą dla np. Gatlinga.
17+
Charakteryzuje się ono prostotą użycia, wspiera różne technologie (np. HTTP API, WebSockety), daje możliwość testowania rozproszonego oraz
18+
może być rozszerzane przez pluginy.
19+
20+
## Instalacja
21+
Artillery można zainstalować poprzez:
22+
23+
```shell
24+
npm install -g artillery@latest
25+
```
26+
27+
lub używając odpowiedniego obrazu Dockerowego. Same testy pisane są w YAMLu lub w JavaScripcie.
28+
29+
## Przykładowy test
30+
31+
Załóżmy, że nasza aplikacja wystawia trzy usługi:
32+
33+
- `GET /get-projects` – zwracająca listę projektów
34+
- `GET /get-sprints?project={{nazwa_projektu}}` – zwracająca listę trwających sprintów dla danego projektu
35+
- `POST /add-task` – pozwalająca na dodanie zadania do sprintu
36+
37+
Chcemy przetestować dwa scenariusze:
38+
39+
1. Użytkownik wchodzi na listę projektów, wyświetla listę sprintów dla projektu TEST-PROJECT, a następnie po dwóch sekundach dodaje zadanie do pierwszego sprintu z listy.
40+
2. Użytkownik dodaje po kolei 10 zadań do losowych dostępnych sprintów.
41+
42+
Dodatkowo zakładamy trzy fazy:
43+
- pierwsza, rozgrzewkowa - 5 wirtualnych użytkowników na sekundę, zwiększających się do 10 na koniec fazy,
44+
```yaml
45+
duration: 60
46+
arrivalRate: 5
47+
rampTo: 10
48+
name: Warm up phase
49+
```
50+
- druga, stopniowo zwiększająca obciążenie do 50 użytkowników,
51+
```yaml
52+
duration: 60
53+
arrivalRate: 10
54+
rampTo: 50
55+
name: Ramp up load
56+
```
57+
58+
- trzecia testująca duży przypływ użytkowników - 50 użytkowników co sekundę.
59+
```yaml
60+
duration: 30
61+
arrivalRate: 50
62+
name: Spike phase
63+
```
64+
65+
## Konfiguracja testu
66+
67+
W sekcji `config` tworzymy podstawową konfigurację testu – definiujemy, na jaki adres będą kierowane żądania, fazy testu oraz zmienne,
68+
które możemy wykorzystać w ramach scenariuszy.
69+
Dodatkowo zdefiniowany jest plugin rozszerzający wyniki oraz procesor – czyli plik zawierający funkcje JavaScript, które mogą być wykorzystane w teście.
70+
71+
```yaml
72+
config:
73+
target: http://localhost:8080
74+
phases: [...]
75+
plugins:
76+
metrics-by-endpoint: {}
77+
processor: "./functions.js"
78+
variables:
79+
project: "TEST-PROJECT"
80+
```
81+
82+
## Scenariusze i flow
83+
84+
Sekcja `scenarios` zawiera definicje scenariuszy testowych. Każdy scenariusz ma określony `weight`, który decyduje o tym, jak często będzie wybierany przez wirtualnych użytkowników. W ramach `flow` określone są kroki scenariusza, w których możemy wykorzystywać zmienne zdefiniowane w konfiguracji lub tworzyć je na bieżąco, np. na podstawie odpowiedzi usług.
85+
86+
```yaml
87+
scenarios:
88+
# 9 na 10 użytkowników wybierze ten scenariusz
89+
- name: "Standard scenario - add single task"
90+
weight: 9
91+
flow:
92+
- get:
93+
url: "/get-projects"
94+
- get:
95+
url: "/get-sprints"
96+
qs:
97+
project: "{{project}}"
98+
capture:
99+
json: "$.sprints[0].sprintId"
100+
as: "sprintId"
101+
- think: 2
102+
- post:
103+
url: '/add-task'
104+
json:
105+
name: "Example task"
106+
type: "TECHNICAL"
107+
sprintId: "{{sprintId}}"
108+
# 1 na 10 użytkowników wybierze ten scenariusz
109+
- name: "Rare scenario - add multiple tasks"
110+
weight: 1
111+
flow:
112+
- get:
113+
url: "/get-projects"
114+
- get:
115+
url: "/get-sprints"
116+
qs:
117+
project: "{{project}}"
118+
capture:
119+
json: "$.sprints"
120+
as: "sprints"
121+
- think: 2
122+
- loop:
123+
- post:
124+
beforeRequest: "setAddTaskBody"
125+
url: '/add-task'
126+
count: 10
127+
```
128+
129+
## Funkcje pomocnicze (processor)
130+
131+
Plik `functions.js` z funkcjami pomocniczymi:
132+
133+
```javascript
134+
module.exports = {
135+
setAddTaskBody
136+
}
137+
138+
function setAddTaskBody(requestParams, context, ee, next) {
139+
const type = Math.random() < 0.75 ? "BUG" : "TECHNICAL"
140+
const name = randomString();
141+
const sprints = context.vars["sprints"];
142+
const randomSprint = sprints[Math.floor(Math.random() * sprints.length)];
143+
144+
const task = {
145+
name,
146+
type,
147+
sprintId: randomSprint.sprintId
148+
}
149+
requestParams.json = task;
150+
return next();
151+
}
152+
153+
function randomString() {
154+
return (Math.random() + 1).toString(36).substring(7);
155+
}
156+
```
157+
158+
## Hooki w Artillery
159+
160+
Artillery pozwala na wykorzystywanie hooków, które są funkcjami JavaScriptowymi zawartymi w pliku będącym procesorem. Wyróżniamy następujące hooki:
161+
162+
- `beforeScenario` i `afterScenario` – przed/po wykonaniu scenariusza przez wirtualnego użytkownika
163+
- `beforeRequest` – przed wysłaniem requestu (można ustawić parametry takie jak URL, ciasteczka, nagłówki czy ciało żądania)
164+
- `afterResponse` – po otrzymaniu odpowiedzi (np. zdefiniować zmienne na dalsze potrzeby testu)
165+
- `function` – funkcja wywoływana w dowolnym miejscu scenariusza
166+
167+
W powyższej implementacji scenariusza wykorzystany został `beforeRequest`, będący funkcją w której ustawiamy body żądania na podstawie wcześniej zapisanej zmiennej.
168+
Funkcja ta przyjmuje cztery parametry:
169+
170+
- `requestParams` – obiekt żądania
171+
- `context` – kontekst wirtualnego użytkownika; za pośrednictwem `context.vars` mamy dostęp do wszystkich zdefiniowanych zmiennych
172+
- `ee` – event emitter do bezpośredniej komunikacji z Artillery
173+
- `next` – obowiązkowy callback, dzięki któremu test jest kontynuowany
174+
175+
## Uruchamianie testów
176+
177+
Aby uruchomić taki scenariusz wystarczy użyć komendy:
178+
179+
```shell
180+
artillery run scenario.yml
181+
```
182+
183+
Można zapisać wyniki testu do formatu JSON dodając flagę `--output`:
184+
185+
```shell
186+
artillery run --output results.json scenario.yml
187+
```
188+
189+
## Raportowanie wyników
190+
191+
Plik ten można wykorzystać do wygenerowania raportu HTML za pośrednictwem komendy:
192+
193+
```shell
194+
artillery report results.json
195+
```
196+
197+
Fragment wygenerowanego raportu, przedstawiający czasy odpowiedzi (ich minimalną i maksymalną wartość, a także medianę oraz percentyle 95 i 99) dla wszystkich żądań HTTP wysłanych w ramach scenariuszy:
198+
199+
<img src="/assets/img/posts/2026-02-13-czy-wiesz-czym-jest-narzedzie-artillery/http_response_time.png" alt="Fragment raportu wygenerowanego przez Artillery" />
200+
201+
## Podsumowanie
202+
203+
Poniżej pełny przykład pliku testowego YAML oraz procesora JS, zbierający wszystkie elementy opisane powyżej:
204+
205+
```yaml
206+
config:
207+
target: http://localhost:8080
208+
phases:
209+
- duration: 60
210+
arrivalRate: 5
211+
rampTo: 10
212+
name: Warm up phase
213+
- duration: 60
214+
arrivalRate: 10
215+
rampTo: 50
216+
name: Ramp up load
217+
- duration: 30
218+
arrivalRate: 50
219+
name: Spike phase
220+
plugins:
221+
metrics-by-endpoint: {}
222+
processor: "./functions.js"
223+
variables:
224+
project: "TEST-PROJECT"
225+
scenarios:
226+
- name: "Standard scenario - add single task"
227+
weight: 9
228+
flow:
229+
- get:
230+
url: "/get-projects"
231+
- get:
232+
url: "/get-sprints"
233+
qs:
234+
project: "{{project}}"
235+
capture:
236+
json: "$.sprints[0].sprintId"
237+
as: "sprintId"
238+
- think: 2
239+
- post:
240+
url: '/add-task'
241+
json:
242+
name: "Example task"
243+
type: "TECHNICAL"
244+
sprintId: "{{sprintId}}"
245+
- name: "Rare scenario - add multiple tasks"
246+
weight: 1
247+
flow:
248+
- get:
249+
url: "/get-projects"
250+
- get:
251+
url: "/get-sprints"
252+
qs:
253+
project: "{{project}}"
254+
capture:
255+
json: "$.sprints"
256+
as: "sprints"
257+
- think: 2
258+
- loop:
259+
- post:
260+
beforeRequest: "setAddTaskBody"
261+
url: '/add-task'
262+
count: 10
263+
```
264+
265+
```javascript
266+
module.exports = {
267+
setAddTaskBody
268+
}
269+
270+
function setAddTaskBody(requestParams, context, ee, next) {
271+
const type = Math.random() < 0.75 ? "BUG" : "TECHNICAL"
272+
const name = randomString();
273+
const sprints = context.vars["sprints"];
274+
const randomSprint = sprints[Math.floor(Math.random() * sprints.length)];
275+
276+
const task = {
277+
name,
278+
type,
279+
sprintId: randomSprint.sprintId
280+
}
281+
requestParams.json = task;
282+
return next();
283+
}
284+
285+
function randomString() {
286+
return (Math.random() + 1).toString(36).substring(7);
287+
}
288+
```
289+
290+
Artillery to proste i przyjemne narzędzie, które posiada także wiele innych funkcjonalności niewykorzystanych w powyższym przykładzie – można się z nimi zapoznać w dokumentacji.
291+
Potencjalnym problemem może być jednak brak wsparcia dla równoległych requestów wywoływanych przez jednego wirtualnego użytkownika – chociaż teoretycznie w kodzie Artillery znaleźć można opcję `parallel`,
292+
to w praktyce nie do końca ona działa. W takiej sytuacji obejściem może być np. odpowiednie zdefiniowanie dodatkowych scenariuszy, które zasymulują takie współbieżne żądania.
293+
294+
## Dokumentacja
295+
- [Artillery – oficjalna dokumentacja](https://www.artillery.io/docs)
24.1 KB
Loading
12.9 KB
Loading

0 commit comments

Comments
 (0)