Test de charge avec le criquet

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 - Locust

Pour 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:

  1. setup - est appelé 1 fois au début de UserBehavior (TaskSet) - ce n'est pas dans l'exemple
  2. on_start - appelé 1 fois par chaque nouvel utilisateur de la charge au démarrage
  3. tĂąches - exĂ©cution des tĂąches elles-mĂȘmes
  4. on_stop - appelé une fois par chaque utilisateur une fois le test terminé
  5. 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:

  1. Ouvrez la page principale 1 fois au début du travail
  2. Obtenez une liste de tous les messages x2
  3. 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:

  1. Comme prévu, chacun des 10 utilisateurs créés au début est allé à la page principale
  2. La liste des articles a été ouverte en moyenne 2 fois plus souvent qu'un commentaire n'a été écrit
  3. 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

Source: https://habr.com/ru/post/fr430502/


All Articles