Découvrez comment intégrer facilement l'analyse statique, les tests unitaires et d'autres méthodes de test de logiciels C et C++ dans votre pipeline CI/CD. Inscrivez-vous pour la démo >>

Construire les performances de l'API à partir de zéro: utiliser des tests unitaires pour évaluer les performances des composants de l'API

Par Sergueï Baranov

18 août 2016

7  min lire

Les tests de performances au niveau des unités sont une pratique puissante que vous pouvez facilement réaliser avec une intégration étroite entre vos outils de test unitaire et de test de performance, afin que vous puissiez comprendre les performances des composants que vous intégrez dans votre application cible.

Les API ont été conçues à l'origine comme des outils d'intégration de base qui permettaient à des applications disparates d'échanger des données, mais elles sont devenues un ciment critique qui lie plusieurs processus en une seule application. Les applications modernes agrègent et consomment des API à un rythme effréné afin d'atteindre les objectifs commerciaux. En fait, la logique métier de la plupart des applications modernes repose désormais sur une combinaison d'API et de bibliothèques tierces, ce qui signifie que les performances des transactions de bout en bout dépendent fortement des performances des API et des composants que l'application exploite. .

Compte tenu de leur capacité à atteindre ou à défaire des objectifs de performance, les composants clés de l'application doivent faire l'objet d'une évaluation rigoureuse des performances dans le cadre du processus d'acceptation. Plus tôt vous comprenez les capacités de performance des composants clés d'une application, plus il est facile de résoudre les problèmes et plus vous pouvez garantir que l'application intégrée répondra à ses exigences de performances.

Tests unitaires

L'utilisation de tests unitaires pour évaluer les performances des composants offre un certain nombre d'avantages, notamment les suivants: 

  • Les tests unitaires offrent un moyen flexible mais standardisé de tester au niveau des composants.
  • Les tests unitaires sont bien compris et largement utilisés dans les environnements de développement.
  • En règle générale, les tests unitaires ne nécessitent qu'une fraction des ressources matérielles nécessaires pour tester l'ensemble de l'application. Cela signifie que vous pouvez tester les composants à un niveau de «stress» maximum projeté (voir l'image ci-dessous) tôt et plus souvent avec les ressources matérielles disponibles dans les environnements de développement.

Fig.1: Niveau de charge approximatif et proportions de durée de test des scénarios de test de performance typiques

Malgré ces avantages, les tests de performance au niveau de l'unité sont souvent négligés car la plupart des outils de test unitaire ne disposent pas des capacités généralement trouvées dans les outils de test de performance dédiés (par exemple, la capacité de configurer et d'exécuter diverses configurations de test de performance, la surveillance des ressources système et applicatives pendant le test, collecte et analyse des résultats des tests de performance, etc.).

Mais vous pouvez tirer le meilleur parti des deux mondes en exécutant des tests au niveau de l'unité avec des outils de test de performances traditionnels. Ci-dessous, je vais également vous donner une stratégie pour mesurer et comparer les performances des composants que votre équipe pourrait intégrer dans votre application cible.

Établissement d'un flux de travail d'analyse comparative des composants

La nécessité d'une analyse comparative au niveau des composants dans les environnements de développement peut survenir à différentes étapes du cycle de vie du logiciel et est motivée par les questions suivantes:

  • Lequel des composants tiers disponibles satisfait non seulement les exigences fonctionnelles, mais a également les meilleures performances? Dois-je utiliser le composant A, B, C, etc. ou en implémenter un nouveau? Ces questions se posent généralement aux étapes de conception et de prototypage.
  • Laquelle des implémentations de code alternatives est la plus optimale du point de vue des performances? Ces questions surviennent généralement pendant la phase de développement et sont liées au code développé en interne.

Un benchmark de composant correctement configuré et exécuté peut aider à répondre à ces questions. Un flux de travail de référence de composant typique (illustré à la figure 2 ci-dessous) se compose de:

  1. Création de tests unitaires pour les composants testés.
  2. Sélection des paramètres de performance de référence (ce sont les mêmes pour tous les composants).
  3. Exécution des tests de performance.
  4. Comparaison des profils de performance de différents composants.

Ceci est illustré dans la figure 2 ci-dessous:

Fig.2: Un workflow de référence de composant typique

Pour un exemple concret, voyons comment comparer les performances de quatre composants d'analyseur JSON: Jackson (streaming), JsonSmart, Gson et FlexJson. Ici, nous utiliserons JUnit comme cadre de test unitaire et Parasoft Load Test comme application de test de charge, qui fait partie de Parasoft SOAtest. La même stratégie peut être appliquée avec d'autres frameworks de test unitaire et applications de test de charge.

Création de tests unitaires pour les composants testés

Le test JUnit doit appeler le composant comme le ferait l'application cible. Cela s'applique à la configuration du composant, au choix des méthodes d'API du composant et aux valeurs et plages d'arguments de méthode.

Le niveau souhaité de vérification des résultats dépend de la nature des tests. En règle générale, vous feriez une vérification plus approfondie des résultats des tests unitaires pour les tests de fiabilité, mais effectuez un niveau de vérification plus basique pour les tests d'efficacité (car la logique de vérification des résultats «lourds» peut fausser l'image des performances). D'autre part, il est important d'effectuer au moins une vérification pour s'assurer que le composant est correctement configuré et appelé.

Dans notre benchmark, le contenu JSON est chargé à partir d'un fichier au début du test de performances et mis en cache en mémoire. Le fichier JSON a une taille de 225 Ko et contient 215 objets de description de compte de niveau supérieur. Chaque objet de compte a une paire nom / valeur «solde»:

{

    "id": "54b98c2b7b3bd64aae699040",

    "index": 214,

    "guid": "565c44b0-9e6d-4b8e-819c-48aa4dd9d7c2",

    "balance": "3,809.46 XNUMX $",

plus

}

Le niveau de base de vérification de la fonctionnalité des composants est implémenté de la manière suivante: le test JUnit appelle l'API parser pour trouver tous les éléments «équilibre» à l'intérieur du contenu JSON et calculer le solde total de tous les objets de compte dans le document JSON. Le solde calculé est ensuite comparé à la valeur attendue:

public classe JacksonStreamParserTest S'étend Cas de test {

    @Tester

    public annuler testParser () jette IOException {       

        flotter solde = 0;

        JsonFactory jsonFactory = Neuf (ve) JsonFactory();

        Chaîne json = JsonContentProvider.getJsonContenu();

        JsonParser jp = jsonFactory.createJsonParser(json);           

        tout en (jp.nextToken ()! = nul) {               

            Chaîne fieldname = jp.getCurrentName ();

            if (nom de champ! = nul && nom de domaine.équivaut à("équilibre")) {

                jp.nextToken ();                   

                balance + = TestUtil.parseCurrencyStr(jp.getText ());  

            }

        }

        TestUtil.affirmerSolde(équilibre);       

    }

}

Parce que nous comparons des analyseurs pour l'efficacité, nous utilisons une vérification des ressources légère pour éviter de déformer l'image des performances. Si vous choisissez de faire une vérification des résultats lourds, vous pouvez la compenser de la même manière que nous compensons la consommation de ressources du cadre de test des performances, décrit ci-dessous. Cependant, cela exigera une préparation plus élaborée du scénario de référence.

Compenser le cadre de test

Étant donné que nous comparons les performances des composants qui s'exécutent dans le même processus que l'application de test des performances du conteneur, nous devons séparer le partage des ressources au niveau du système et au niveau de l'application consommées par cette application et le framework JUnit du partage consommé par le composant lui-même. Pour estimer cette part, nous pouvons exécuter un scénario de test de charge de référence avec un test JUnit vide:

public classe Test de base S'étend Cas de test {

    @Tester

    public annuler testBaseline () {               

    }

}

Si les niveaux d'utilisation des ressources du scénario de référence ne sont pas négligeables, nous devons soustraire ces niveaux des niveaux des exécutions de référence des composants pour compenser la part des ressources consommées par le cadre de test.

Sélection et configuration des paramètres de performance de référence

La configuration de l'environnement de test doit émuler les principaux paramètres de l'environnement cible, tels que le système d'exploitation, la version JVM, les options JVM telles que les options GC, le mode serveur, etc. Il n'est pas toujours possible ou pratique de reproduire tous les paramètres de déploiement dans l'environnement de test; cependant, plus il est proche, moins il y a de chances que les performances des composants pendant le test diffèrent de celles de l'environnement cible.

Comprendre la concurrence, l'intensité et la durée du test

Les principaux paramètres de test de performance qui déterminent les conditions dans lesquelles les composants seront testés sont niveau de charge (défini comme intensité de charge et un  chargement simultané) et durée de charge (voir la figure 3 ci-dessous).

Pour mettre en forme ces paramètres de test de performances génériques en formes concrètes, commencez par examiner les exigences de performances de l'application cible dans laquelle vous prévoyez d'utiliser ce composant. Les exigences de performance au niveau de l'application peuvent fournir des caractéristiques concrètes ou approximatives du niveau de charge auquel le composant doit être soumis. La traduction des exigences de performance des applications en exigences de performance des composants présente cependant de nombreux défis. Par exemple, si l'exigence de performances de l'application indique qu'elle doit répondre dans les M millisecondes à un niveau de charge de N utilisateurs ou demandes par seconde, comment cela se traduit-il en exigences de performances pour un composant qui fait partie de cette application? Combien de demandes à un composant spécifique une demande à l'application générera-t-elle?

S'il existe une version plus ancienne de l'application, vous pouvez faire une estimation éclairée en traçant quelques appels au niveau de l'application ou en examinant les statistiques de suivi des appels collectées par un outil APM (Application Performance Management). Si aucune de ces options n'est disponible, la réponse peut venir de l'examen de la conception de l'application.

Si les paramètres de charge des composants ne peuvent pas être déduits des spécifications de performances de l'application cible ou si une telle spécification n'est pas disponible, un autre choix pour un benchmark consiste à exécuter au niveau de charge maximum qui peut être atteint sur le matériel disponible pour le test. Cependant, le risque impliqué dans cette approche, lorsqu'elle est prise sans tenir compte des niveaux de charge attendus, est que les résultats de référence peuvent ne pas être pertinents dans le contexte de l'application cible.

Dans tous les cas - mais en particulier pour les benchmarks où les niveaux de charge sont déterminés par les limites de performances du matériel disponible pour le test - soyez conscient de l'impact de la surutilisation des ressources sur les résultats du test. Par exemple, lors de la comparaison des temps d'exécution des composants, il est généralement conseillé de rester en dessous du niveau d'utilisation du processeur de 75 à 80%. L'augmentation de l'utilisation au-dessus de ce niveau peut nuire à la précision de la mesure du temps d'exécution des tests et peut fausser les résultats de référence.

Souvent, le paramètre de test de performance agrégé «niveau de charge» n'est pas explicitement séparé en ses parties principales: intensité et concurrence (voir la figure 3). Cela peut conduire à une configuration de test de performance inadéquate.

Fig.3: Les principaux paramètres de test de performance: intensité, concurrence et durée

Intensité de charge est le taux de requêtes ou d'appels de test auquel le composant sera testé. Dans une application de test de performances, l'intensité de la charge peut être configurée directement en définissant les niveaux de succès / test par seconde / minute / heure d'un scénario de test de performances. Il peut également être configuré indirectement en tant que combinaison du nombre d'utilisateurs virtuels et du temps de réflexion de l'utilisateur virtuel.

Charger la concurrence (dans notre contexte) peut être décrit comme le degré de parallélisme avec lequel une charge d'une intensité donnée est appliquée. Le niveau de concurrence peut être configuré par le nombre d'utilisateurs virtuels ou de threads dans un scénario de test de charge. La définition d'un niveau d'accès concurrentiel approprié est essentielle lors du test des conditions de concurrence potentielles et des problèmes de performances dus à des conflits de threads et à l'accès aux ressources partagées, ainsi que pour mesurer l'empreinte mémoire de plusieurs instances du composant.

Durée du test pour un test de référence de composant dépend des objectifs du test. Lorsqu'il est abordé du point de vue mathématique, l'un des principaux facteurs dans la détermination de la durée du test est la signification statistique de l'ensemble de données de test de charge. Cependant, ce type d'approche peut être trop compliqué à des fins pratiques quotidiennes. 

Pour lire le reste du blog, consultez le livre blanc ici.

Par Sergueï Baranov

Sergei est un ingénieur logiciel principal chez Parasoft, se concentrant sur les tests de charge et de performance au sein de Parasoft SOAtest.

Recevez les dernières nouvelles et ressources sur les tests de logiciels dans votre boîte de réception.