En quoi consistent les tests ?

Lorsque vous écrivez un logiciel, vous pouvez vérifier qu'il fonctionne correctement en effectuant des tests. Les tests peuvent être définis de manière générale comme le processus qui consiste à exécuter un logiciel de manière spécifique afin de s'assurer qu'il se comporte comme prévu.

Des tests réussis vous permettent de vous assurer que lorsque vous ajoutez du code ou des fonctionnalités, ou que vous mettez à niveau vos dépendances, le logiciel que vous avez déjà écrit continuera de fonctionner comme prévu. Les tests peuvent également aider à protéger votre logiciel contre des scénarios improbables ou des entrées inattendues.

Voici quelques exemples de comportements que vous pouvez tester sur le Web:

  • S’assurer que la fonctionnalité d’un site Web fonctionne correctement lorsqu’un utilisateur clique sur un bouton.
  • Vérifier qu'une fonction complexe produit les résultats corrects
  • effectuer une action nécessitant la connexion de l'utilisateur ;
  • Vérifier qu'un formulaire signale correctement une erreur lorsque des données mal formulées sont saisies.
  • S'assurer qu'une application Web complexe continue de fonctionner lorsqu'un utilisateur dispose d'une bande passante extrêmement faible ou se déconnecte.

Tests automatisés et manuels

Vous pouvez tester votre logiciel de deux manières générales: les tests automatisés et les tests manuels.

Les tests manuels impliquent des humains exécutant directement un logiciel, par exemple en chargeant un site Web dans leur navigateur, et en confirmant qu'il se comporte comme prévu. Les tests manuels sont simples à créer ou à définir. Par exemple, votre site peut-il se charger ? Pouvez-vous effectuer ces actions ? Mais chaque exécution coûte énormément de temps à un humain. Bien que les humains soient très créatifs, ce qui peut permettre un type de test appelé test exploratoire, nous pouvons toujours avoir du mal à détecter les échecs ou les incohérences, en particulier lorsque vous effectuez plusieurs fois la même tâche.

Les tests automatisés sont des processus qui permettent de codifier des tests et de les exécuter de manière répétée par un ordinateur pour confirmer le comportement souhaité de votre logiciel, sans qu'un humain effectue des étapes répétées, telles que la configuration ou la vérification des résultats. Une fois que les tests automatisés sont configurés, ils peuvent être exécutés fréquemment. Cette définition reste très large, et il convient de noter que les tests automatisés prennent toutes sortes de formes. La majeure partie de ce cours s'intéresse aux tests automatisés.

Les tests manuels ont leur place, souvent en tant que précurseur de l'écriture des tests automatisés, mais également lorsque ces tests deviennent trop peu fiables, dont la portée est trop large ou trop difficile à écrire.

Les principes de base à travers un exemple

Pour nous, en tant que développeurs Web qui écrivent du code JavaScript ou dans des langages associés, un test automatisé concis peut être un script semblable à celui-ci que vous exécutez tous les jours, peut-être via Node ou en le chargeant dans un navigateur:

import { fibonacci } from "../src/math.js";

if (fibonacci(0) !== 0) {
  throw new Error("Invalid 0th fibonacci result");
}
const fib13 = fibonacci(13);
if (fib13 !== 233) {
  throw new Error("Invalid 13th fibonacci result, was=${fib13} wanted=233");
}

Cet exemple simplifié fournit les insights suivants:

  • Il s'agit d'un test, car il exécute un logiciel (la fonction Fibonacci) et garantit que son comportement fonctionne comme prévu en vérifiant ses résultats par rapport aux valeurs attendues. Si le comportement n'est pas correct, une erreur est renvoyée, que JavaScript exprime en générant une Error.

  • Même si vous exécutez ce script manuellement dans votre terminal ou votre navigateur, il s'agit toujours d'un test automatisé, car il peut être exécuté plusieurs fois sans que vous ayez à effectuer des étapes individuelles. La page suivante, où s'exécutent les tests, fournit des explications plus détaillées.

  • Bien que ce test n'utilise aucune bibliothèque (il s'agit d'un script JavaScript pouvant s'exécuter n'importe où), il reste un test. De nombreux outils peuvent vous aider à écrire des tests, y compris ceux qui seront abordés plus loin dans ce cours, mais ils continuent tous de suivre le principe fondamental consistant à générer une erreur en cas de problème.

Tester les bibliothèques en pratique

La plupart des bibliothèques ou des frameworks de test intégrés fournissent deux primitives principales qui facilitent l'écriture des tests: les assertions et un moyen de définir des tests indépendants. Celles-ci seront traitées en détail dans la section suivante, Assertions et autres primitives. Toutefois, de manière générale, il est important de se rappeler que presque tous les tests que vous voyez ou écrivez utiliseront ce type de primitives.

Les assertions sont un moyen de combiner la vérification d'un résultat et la création d'une erreur en cas de problème. Par exemple, vous pouvez rendre le test précédent plus concis en introduisant assert:

import { fibonacci } from "../src/math.js";
import { assert } from "a-made-up-testing-library";

assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");

Vous pouvez améliorer encore ce test en définissant des tests indépendants, éventuellement regroupés dans des suites. La suite suivante teste indépendamment les fonctions Fibonacci et Catalan:

import { fibonacci, catalan } from "../src/math.js";
import { assert, test, suite } from "a-made-up-testing-library";

suite("math tests", () => {
  test("fibonacci function", () => {
    assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
    assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");
  });
  test("relationship between sequences", () => {
    const numberToCheck = 4;
    const fib = fibonacci(numberToCheck);
    const cat = catalan(numberToCheck);
    assert.isAbove(fib, cat);
  });
});

Dans ce contexte des tests logiciels, le terme test fait référence à un scénario de test: un scénario unique, indépendant et adressable, tel que le scénario de test "relation entre les séquences" dans l'exemple précédent.

Les tests nommés individuellement sont utiles, entre autres, pour les tâches suivantes:

  • Déterminer comment un test réussit ou échoue au fil du temps.
  • Mettez en évidence un bug ou un scénario par leur nom afin de pouvoir vérifier plus facilement que le scénario est résolu.
  • Exécuter certains tests indépendamment des autres, par exemple à l'aide d'un filtre global

Une façon d'envisager les scénarios de test consiste à utiliser les "trois A" des tests unitaires : organiser, agir et affirmer. À la base de chaque scénario de test:

  • Organisez certaines valeurs ou certains états (il peut simplement s'agir de données d'entrée codées en dur).
  • Effectuer une action, telle que l'appel d'une méthode.
  • Effectuez l'assertion des valeurs de sortie ou de l'état mis à jour (à l'aide de assert).

L'ampleur des tests

Les exemples de code de la section précédente décrivent un test unitaire, car ils testent des parties mineures de votre logiciel, en se concentrant souvent sur un seul fichier, et dans ce cas, uniquement la sortie d'une seule fonction. La complexité des tests augmente lorsque vous considérez le code de plusieurs fichiers, composants ou même de différents systèmes interconnectés (parfois hors de votre contrôle, comme un service réseau ou le comportement d'une dépendance externe). Pour cette raison, les types de test sont souvent nommés en fonction de leur champ d'application ou de leur échelle.

Outre les tests unitaires, les tests de composants, les tests visuels et les tests d'intégration sont des exemples d'autres types de tests. Aucun de ces noms n'a de définitions rigoureuses et ils peuvent avoir des significations différentes selon votre codebase. N'oubliez donc pas de les utiliser comme guide et de trouver des définitions qui vous conviennent. Par exemple, qu'est-ce qu'un composant en cours de test dans votre système ? Pour les développeurs React, cela peut littéralement correspondre à un "composant React", mais cela peut avoir une signification différente pour les développeurs dans d'autres contextes.

L'échelle d'un test individuel peut le placer dans un concept souvent appelé "pyramide de test", ce qui peut être une bonne règle de base pour ce qu'un test vérifie et comment il s'exécute.

Pyramide de test, avec les tests de bout en bout (E2E) en haut, les tests d'intégration au milieu et les tests unitaires en bas
Pyramide de tests

Cette idée a été itérée et diverses autres formes sont désormais popularisées, telles que le losange de test ou le cornet de glace de test. Vos priorités en termes d'écriture des tests seront probablement propres à votre codebase. Cependant, une fonctionnalité courante est que les tests simples, tels que les tests unitaires, ont tendance à être plus rapides à exécuter, plus faciles à écrire (vous en aurez donc plus) et à tester un champ d'application limité, tandis que les tests complexes tels que les tests de bout en bout sont difficiles à écrire, mais peuvent tester un champ d'application plus large. En fait, la couche supérieure de nombreuses "formes" de test est généralement un test manuel, car certaines interactions utilisateur sont trop complexes pour être codifiées dans un test automatisé.

Ces types seront développés dans les types de tests automatisés.

Testez vos connaissances

Quelles primitives la plupart des bibliothèques et frameworks de test fournissent-ils ?

Un service d'exécution qui utilise un fournisseur de services cloud.
Certains exécuteurs basés sur un navigateur permettent d'externaliser vos tests, mais ce n'est pas une fonctionnalité normale de tester les bibliothèques.
Assertions qui provoquent des exceptions si elles ne sont pas satisfaites
Bien que vous puissiez générer une erreur pour faire échouer un test, assert() et ses variantes ont tendance à être inclus, car ils facilitent l'écriture des vérifications.
Un moyen de classer les tests dans la pyramide de test.
Il n'y a pas vraiment de façon standard de le faire. Vous pouvez ajouter un préfixe aux noms de vos tests ou les placer dans des fichiers différents, mais la catégorisation n'est pas vraiment intégrée à la plupart des frameworks de test.
Possibilité de définir des tests indépendants par fonction
La méthode test() est incluse dans presque tous les exécuteurs de test. C'est important, car le code de test ne s'exécute pas au niveau supérieur d'un fichier, ce qui permet au lanceur de test de traiter chaque scénario de test comme une unité indépendante.