Les tests de charge ne sont pas tant demandĂ©s et rĂ©pandus que les autres types de tests - il n'y a pas tellement d'outils qui vous permettent d'effectuer de tels tests, mais gĂ©nĂ©ralement simples et pratiques peuvent ĂȘtre comptĂ©s sur les doigts d'une main.
En ce qui concerne les tests de performances - tout d'abord, tout le monde pense à JMeter - il reste sans aucun doute l'outil le plus célÚbre avec le plus grand nombre de plugins. Je n'ai jamais aimé JMeter en raison de son interface non évidente et de son seuil d'entrée élevé, dÚs qu'il devient nécessaire de tester une application non Hello World.
Et maintenant, inspiré par le succÚs des tests dans deux projets différents, j'ai décidé de partager des informations sur un logiciel relativement simple et pratique -
LocustPour ceux qui sont trop paresseux pour passer sous la coupe, j'ai enregistré une vidéo:
Qu'est ce que c'est
Outil open source qui vous permet de spécifier des scénarios de charge avec du code Python qui prend en charge la charge distribuée et, comme le prétendent les auteurs, a été utilisé pour tester la charge de Battlelog pour une série de jeux Battlefild (immédiatement captivant)
Des pros:
- documentation simple avec exemple copier-coller. Vous pouvez commencer Ă tester, mĂȘme sans presque aucune compĂ©tence en programmation.
- «Under the hood» utilise la bibliothĂšque de requĂȘtes (HTTP pour les personnes). Sa documentation peut ĂȘtre utilisĂ©e comme un cheat sheet Ă©tendu et des tests de dĂ©bits
- Prise en charge de Python - j'aime juste le langage
- Le paragraphe précédent donne plusieurs plates-formes pour exécuter des tests
- Propre serveur Web Flask pour afficher les résultats des tests
Des inconvénients:
- Pas de capture et de relecture - toutes les mains
- Le résultat du paragraphe précédent - vous avez besoin d'un cerveau. Comme pour Postman, vous devez comprendre le fonctionnement de HTTP.
- Compétences minimales en programmation nécessaires
- ModÚle de charge linéaire - qui bouleverse immédiatement les fans pour générer des utilisateurs "selon Gauss"
Processus de test
Tout test est une tĂąche complexe qui nĂ©cessite une planification, une prĂ©paration, un suivi de la mise en Ćuvre et une analyse des rĂ©sultats. Pendant les tests de rĂ©sistance, si possible, il est possible et nĂ©cessaire de collecter toutes les donnĂ©es possibles susceptibles d'affecter le rĂ©sultat:
- Matériel serveur (CPU, RAM, ROM)
- Logiciel serveur (OS, version serveur, JAVA, .NET, etc., la base de donnĂ©es et la quantitĂ© de donnĂ©es elle-mĂȘme, serveur et journaux des applications de test)
- Bande passante réseau
- La présence de serveurs proxy, d'équilibreurs de charge et de protection DDOS
- Charger les donnĂ©es de test (nombre d'utilisateurs, temps de rĂ©ponse moyen, nombre de requĂȘtes par seconde)
Les exemples dĂ©crits ci-dessous peuvent ĂȘtre classĂ©s comme des tests de charge fonctionnelle en boĂźte noire. MĂȘme sans rien savoir de l'application testĂ©e et sans accĂšs aux journaux, nous pouvons mesurer ses performances.
Avant de commencer
Afin de tester les tests de charge dans la pratique, j'ai déployé un serveur Web localement simple
https://github.com/typicode/json-server . Je vais lui donner presque tous les exemples suivants. J'ai pris les données du serveur à partir d'un exemple en ligne déployé -
https://jsonplaceholder.typicode.com/Pour l'exécuter, nodeJS est requis.
Spoiler Ă©vident : comme avec les tests de sĂ©curitĂ© - il est prĂ©fĂ©rable de mener des expĂ©riences avec des tests de stress sur les chats localement, sans charger les services en ligne pour ne pas ĂȘtre banni
Pour commencer, Python est Ă©galement requis - dans tous les exemples, j'utiliserai la version 3.6, ainsi que le criquet lui-mĂȘme (au moment de la rĂ©daction, la version 0.9.0). Il peut ĂȘtre installĂ© avec la commande
python -m pip install locustio
Les dĂ©tails d'installation peuvent ĂȘtre trouvĂ©s dans la documentation officielle.
Analyser un exemple
Ensuite, nous avons besoin d'un fichier de test. J'ai pris un exemple de la documentation, car il est trĂšs simple et direct:
from locust import HttpLocust, TaskSet def login(l): l.client.post("/login", {"username":"ellen_key", "password":"education"}) def logout(l): l.client.post("/logout", {"username":"ellen_key", "password":"education"}) def index(l): l.client.get("/") def profile(l): l.client.get("/profile") class UserBehavior(TaskSet): tasks = {index: 2, profile: 1} def on_start(self): login(self) def on_stop(self): logout(self) class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 5000 max_wait = 9000
Câest tout! C'est vraiment suffisant pour commencer le test! Regardons un exemple avant de commencer.
En sautant les importations, au tout début, nous voyons 2 fonctions de connexion et de déconnexion presque identiques, constituées d'une seule ligne.
l.client - un objet de session HTTP avec lequel nous allons crĂ©er une charge. Nous utilisons la mĂ©thode POST, qui est presque identique Ă celle de la bibliothĂšque de requĂȘtes. Presque - parce que dans cet exemple, nous passons comme premier argument non pas une URL complĂšte, mais seulement une partie de celle-ci - un service spĂ©cifique.
Le deuxiĂšme argument transmet les donnĂ©es - et je ne peux m'empĂȘcher de remarquer qu'il est trĂšs pratique d'utiliser des dictionnaires Python, qui sont automatiquement convertis en json
Vous pouvez également noter que nous ne traitons en aucun cas le résultat de la demande - si elle réussit, les résultats (par exemple, les cookies) seront enregistrés dans cette session. Si une erreur se produit, elle sera enregistrée et ajoutée aux statistiques sur la charge.
Si nous voulons savoir si nous avons correctement écrit la demande, nous pouvons toujours la vérifier comme suit:
import requests as r response=r.post(base_url+"/login",{"username":"ellen_key","password":"education"}) print(response.status_code)
J'ai ajouté uniquement la variable
base_url , qui devrait contenir l'adresse complÚte de la ressource testée.
Les fonctions suivantes sont des requĂȘtes, qui crĂ©eront une charge. Encore une fois, nous n'avons pas besoin de traiter la rĂ©ponse du serveur - les rĂ©sultats iront immĂ©diatement aux statistiques.
Vient ensuite la classe
UserBehavior (le nom de la classe peut ĂȘtre n'importe lequel). Comme son nom l'indique, il dĂ©crira le comportement d'un utilisateur sphĂ©rique dans le vide de l'application testĂ©e. Nous passons Ă la propriĂ©tĂ© des
tùches un dictionnaire de méthodes que l'utilisateur appellera et leur fréquence d'appels. Maintenant, malgré le fait que nous ne savons pas quelle fonction et dans quel ordre chaque utilisateur appellera - ils sont sélectionnés au hasard, nous garantissons que la fonction d'
index sera appelée en moyenne 2 fois plus souvent que la fonction de
profil .
En plus du comportement, la classe parent TaskSet vous permet de spĂ©cifier 4 fonctions qui peuvent ĂȘtre effectuĂ©es avant et aprĂšs les tests. L'ordre des appels sera le suivant:
- setup - est appelé 1 fois au début de UserBehavior (TaskSet) - ce n'est pas dans l'exemple
- on_start - appelé 1 fois par chaque nouvel utilisateur de la charge au démarrage
- tĂąches - exĂ©cution des tĂąches elles-mĂȘmes
- on_stop - appelé une fois par chaque utilisateur une fois le test terminé
- démontage - est appelé 1 fois à la sortie du TaskSet - il n'est pas non plus dans l'exemple
Il convient de mentionner ici qu'il existe 2 façons de déclarer le comportement de l'utilisateur: la premiÚre est déjà indiquée dans l'exemple ci-dessus - les fonctions sont annoncées à l'avance. La deuxiÚme façon consiste à déclarer des méthodes directement dans la classe
UserBehavior :
from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): def on_start(self): self.client.post("/login", {"username":"ellen_key", "password":"education"}) def on_stop(self): self.client.post("/logout", {"username":"ellen_key", "password":"education"}) @task(2) def index(self): self.client.get("/") @task(1) def profile(self): self.client.get("/profile") class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 5000 max_wait = 9000
Dans cet exemple, les fonctions utilisateur et la fréquence de leur appel sont définies à l'aide de l'annotation de
tùche . Fonctionnellement, rien n'a changé.
La derniĂšre classe de l'exemple est
WebsiteUser (le nom de la classe peut ĂȘtre quelconque). Dans cette classe, nous dĂ©finissons le modĂšle de comportement utilisateur
UserBehavior *** +, ainsi que les temps d'attente minimum et maximum entre les appels Ă des tĂąches individuelles par chaque utilisateur. Pour le rendre plus clair, voici comment le visualiser:

Pour commencer
Exécutez le serveur dont nous testerons les performances:
json-server --watch sample_server/db.json
Nous modifions également le fichier d'exemple pour qu'il puisse tester le service, supprimer la connexion et la déconnexion, définir le comportement de l'utilisateur:
- Ouvrez la page principale 1 fois au début du travail
- Obtenez une liste de tous les messages x2
- Rédiger un commentaire sur le premier message x1
from locust import HttpLocust, TaskSet, task class UserBehavior(TaskSet): def on_start(self): self.client.get("/") @task(2) def posts(self): self.client.get("/posts") @task(1) def comment(self): data = { "postId": 1, "name": "my comment", "email": "test@user.habr", "body": "Author is cool. Some text. Hello world!" } self.client.post("/comments", data) class WebsiteUser(HttpLocust): task_set = UserBehavior min_wait = 1000 max_wait = 2000
Pour démarrer à l'invite de commandes, exécutez la commande
locust -f my_locust_file.py --host=http://localhost:3000
oĂč hĂŽte est l'adresse de la ressource testĂ©e. C'est Ă lui que seront ajoutĂ©es les adresses de services indiquĂ©es dans le test.
S'il n'y a aucune erreur dans le test, le serveur de chargement démarrera et sera disponible sur
http: // localhost: 8089 /
Comme vous pouvez le voir, le serveur que nous allons tester est indiqué ici - c'est à cette URL que seront ajoutées les adresses des services du fichier de test.
Ici aussi, nous pouvons indiquer le nombre d'utilisateurs pour la charge et leur croissance par seconde.
Sur le bouton, on démarre la charge!

Résultats
AprĂšs un certain temps, nous arrĂȘtons le test et examinons les premiers rĂ©sultats:
- Comme prévu, chacun des 10 utilisateurs créés au début est allé à la page principale
- La liste des articles a été ouverte en moyenne 2 fois plus souvent qu'un commentaire n'a été écrit
- Il y a un temps de rĂ©ponse moyen et mĂ©dian pour chaque opĂ©ration, le nombre d'opĂ©rations par seconde est dĂ©jĂ des donnĂ©es utiles, mĂȘme si maintenant les prendre et les comparer avec le rĂ©sultat attendu des exigences
Sur le deuxiÚme onglet, vous pouvez voir les graphiques de charge en temps réel. Si le serveur plante à une certaine charge ou si son comportement change, cela sera immédiatement visible sur le graphique.

Sur le troisiÚme onglet, vous pouvez voir les erreurs - dans mon cas, il s'agit d'une erreur client. Mais si le serveur renvoie une erreur 4XX ou 5XX, son texte sera écrit ici
Si une erreur se produit dans le code de votre texte, elle tombera dans l'onglet Exceptions. Jusqu'à présent, j'ai l'erreur la plus courante liée à l'utilisation de la commande print () dans le code - ce n'est pas la meilleure façon de se connecter :)
Sur le dernier onglet, vous pouvez télécharger tous les résultats des tests au format csv
Ces rĂ©sultats sont-ils pertinents? Voyons cela. Le plus souvent, les exigences de performances (le cas Ă©chĂ©ant) sont similaires Ă ceci: le temps de chargement moyen des pages (rĂ©ponse du serveur) doit ĂȘtre infĂ©rieur Ă N secondes avec une charge de M utilisateurs. Ne spĂ©cifiant pas vraiment ce que les utilisateurs doivent faire. Et j'aime le criquet pour cela - cela crĂ©e l'activitĂ© d'un nombre spĂ©cifique d'utilisateurs qui effectuent au hasard les actions attendues qu'ils attendent des utilisateurs.
Si nous devons effectuer un benchmark - pour mesurer le comportement du systÚme à différentes charges, nous pouvons créer plusieurs classes de comportement et effectuer plusieurs tests à différentes charges.
C'est suffisant pour commencer. Si vous avez aimé l'article, j'ai l'intention d'écrire sur:
- scénarios de test complexes dans lesquels les résultats d'une étape sont utilisés dans les éléments suivants
- traitement des rĂ©ponses du serveur, comme cela peut ĂȘtre faux mĂȘme si HTTP 200 OK est arrivĂ©
- difficultĂ©s Ă©videntes qui peuvent ĂȘtre rencontrĂ©es et comment les contourner
- tester sans utiliser l'interface utilisateur
- test de charge distribuée