Outils du métier

Les tests automatisés sont essentiellement du code qui génère ou provoque une erreur si quelque chose ne va pas. La plupart des bibliothèques ou des frameworks de test fournissent une variété qui facilitent l'écriture des tests.

Comme indiqué dans la section précédente, ces primitives incluent presque toujours un élément de définir des tests indépendants (appelés scénarios de test) et de fournir assertions. Les assertions sont un moyen de combiner la vérification d'un résultat et l'envoi en cas de problème, et peut être considéré comme la primitive de base de toutes les primitives de test.

Cette page décrit une approche générale de ces primitives. Framework que vous avez choisi a probablement quelque chose comme ça, mais ce n'est pas une référence exacte.

Exemple :

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

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

Dans cet exemple, un groupe de tests (parfois appelé suite) est créé pour les tests tests", et définit trois scénarios de test indépendants qui exécutent chacun des assertions. Ces scénarios de test peuvent généralement être traités individuellement ou exécutés, par exemple, l'indicateur de filtre dans votre lanceur de test.

Des assistants d'assertion en tant que primitives

La plupart des frameworks de test, y compris Vitest, incluent une collection d'assertions des assistants sur un objet assert qui vous permettent de vérifier rapidement les valeurs renvoyées ou d'autres États, par opposition à une attente. Il s'agit souvent d'un "bien connu". valeurs. Dans l'exemple précédent, nous savons que le 13e nombre de Fibonacci doit être 233. Nous pouvons donc le confirmer directement à l'aide de assert.equal.

Vous pouvez également avoir des attentes qu'une valeur prend une certaine forme, ou qu'elle supérieure à une autre valeur, ou avoir une autre propriété. Ce cours ne couvrent l'ensemble des assistants d'assertion possibles, mais les frameworks de test effectuez toujours au moins les vérifications de base suivantes:

  • Une valeur 'truthy' (souvent décrit comme une réponse positive) vérifie qu'une condition est vraie, ce qui correspond à la façon dont vous pourriez écrire une if qui vérifie si quelque chose a réussi ou Correct. Il est généralement fourni sous la forme assert(...) ou assert.ok(...). prend une valeur plus un commentaire facultatif.

  • Un contrôle d'égalité, comme dans l'exemple du test mathématique, dans lequel la valeur ou l'état d'un objet pour qu'il corresponde à une valeur correcte connue. Elles sont destinées égalité primitive (pour les nombres et les chaînes, par exemple) ou égalité référentielle (il s'agit du même objet). En coulisse, ce ne sont qu'une « vérité » cocher avec une comparaison == ou ===.

    • JavaScript fait la distinction entre l'égalité lâche (==) et stricte (===). La plupart des bibliothèques de test fournissent les méthodes assert.equal et assert.strictEqual, respectivement.
  • Les contrôles d'égalité approfondis, qui étendent les contrôles d'égalité pour inclure la vérification le contenu d'objets, de tableaux et d'autres types de données plus complexes, ainsi que une logique interne pour balayer les objets afin de les comparer. C'est important car JavaScript ne dispose d'aucun moyen intégré pour comparer le contenu deux objets ou tableaux. Par exemple, [1,2,3] == [1,2,3] est toujours "false". Tests les frameworks incluent souvent des assistants deepEqual ou deepStrictEqual.

Des assistants d'assertion qui comparent deux valeurs (au lieu d'une simple vérification de "vérité") utilisent généralement deux ou trois arguments:

  • La valeur réelle, telle qu'elle est générée à partir du code testé ou décrivant le à valider.
  • La valeur attendue, généralement codée en dur (par exemple, un nombre littéral ou ).
  • Un commentaire facultatif décrivant ce qui est attendu ou ce qui a pu échouer qui sera inclus si cette ligne échoue.

Une pratique assez courante consiste à combiner des assertions pour construire car il est rare que l'on puisse confirmer correctement l'état par lui-même. Exemple :

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest utilise la bibliothèque d'assertions Chai. en interne pour fournir ses assistants d'assertion, et il peut être utile d'examiner sa référence pour voir quelles assertions et assistants pourraient convenir à votre code.

Assertions Fluent et BDD

Certains développeurs préfèrent un style d'assertion qui peut être appelé "basé sur le comportement" (BDD), ou Style Fluent assertions. On parle également de "s'attendre" car le point d'entrée la vérification des attentes est une méthode nommée expect().

Les assistants attendus se comportent de la même manière que les assertions écrites en tant que méthode simple comme assert.ok ou assert.strictDeepEquals, mais certains développeurs les trouvent plus faciles à lire. Une assertion BDD peut se présenter comme suit:

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

Ce style d'assertions fonctionne grâce à une technique appelée chaînage de méthodes, où l'objet renvoyé par expect peut être continuellement enchaîné avec d'autres appels de méthode. Certaines parties de l'appel, y compris to.be et that.does dans l'exemple précédent, n'ont pas de fonction et ne sont inclus que pour effectuer l'appel plus facile à lire et, potentiellement, de générer un commentaire automatisé si le test a échoué. (En particulier, expect n'accepte généralement pas les commentaires facultatifs, car le chaînage doit décrire clairement l'échec.)

De nombreux frameworks de test sont compatibles avec Fluent/BDD et les assertions standards. Vivre, par exemple, exporte les données Chai adopte une approche légèrement plus concise du BDD. Jest, d'autre part, n'inclut que la valeur attendu par défaut.

Regrouper des tests sur plusieurs fichiers

Lorsque nous écrivons des tests, nous avons déjà tendance à fournir des regroupements implicites, que tous les tests se trouvent dans un seul fichier, il est courant d'écrire des tests pour plusieurs . En fait, les exécuteurs de test ne savent généralement qu'un fichier est destiné à un test, car d'un filtre prédéfini ou d'une expression régulière (par exemple, vitest inclut tous les attributs les fichiers de votre projet qui se terminent par une extension telle que ".test.jsx". ou ".spec.ts". (.test" et ".spec", plus plusieurs extensions valides).

Les tests des composants sont généralement situés dans un fichier d'appairage. comme dans la structure de répertoires suivante:

<ph type="x-smartling-placeholder">
</ph> Une liste de fichiers dans un
  , y compris UserList.tsx et UserList.test.tsx.
Un fichier de composant et le fichier de test associé.

De même, les tests unitaires ont tendance à être placés à côté du code testé. Les tests de bout en bout peuvent se trouver dans leur propre fichier, et les tests d'intégration peuvent même être placés dans des dossiers uniques. Ces structures peuvent être utiles lorsque les scénarios de test complexes nécessitent davantage de fichiers de soutien, tels que prendre en charge les bibliothèques nécessaires uniquement pour un test.

Regrouper les tests dans des fichiers

Comme utilisé dans les exemples précédents, il est courant de placer des tests dans un appel pour suite() qui regroupe les tests que vous avez configurés avec test(). Les suites ne sont généralement pas eux-mêmes, mais ils aident à structurer les tests en regroupant les tests associés ou des objectifs en appelant la méthode transmise. Pour test(), la méthode transmise décrit les actions du test lui-même.

Comme pour les assertions, il existe une équivalence assez standard dans Fluent/BDD pour regrouper des tests. Quelques exemples types sont comparés dans le code suivant:

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

Dans la plupart des frameworks, suite et describe se comportent de la même manière, tout comme test et it, par opposition aux plus grandes différences entre l'utilisation de expect et assert pour écrire des assertions.

D'autres outils adoptent des approches légèrement différentes pour organiser les suites et les tests. Pour exemple, le lanceur de test intégré de Node.js prend en charge les appels d'imbrication de test() pour créer implicitement une hiérarchie de test. Toutefois, Vitest n'autorise que ce type l'imbrication à l'aide de suite() et n'exécutera pas un test() défini dans un autre test().

Comme pour les assertions, rappelez-vous que la combinaison exacte méthodes fournies par votre pile technologique n'est pas si important. Ce cours aborde de manière abstraite, mais vous devez déterminer comment elles s'appliquent à votre le choix des outils.

Méthodes du cycle de vie

L'une des raisons de regrouper vos tests, même implicitement au premier niveau dans une est de fournir des méthodes de configuration et de suppression qui s'exécutent pour chaque test, ou une fois pour un groupe de tests. La plupart des frameworks proposent quatre méthodes:

Pour chaque "test()" ou "it()" Une fois pour la suite
Avant l'exécution du test "avantChaque()" `beforeAll()`
Après l'exécution du test "afterChaque()" "afterAll()"

Par exemple, vous pouvez préremplir une base de données d'utilisateurs virtuels avant chaque avant de l'effacer:

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => {  });
  test('alice can message bob', async () => {  });
});

Cela peut être utile pour simplifier vos tests. Vous pouvez partager des configurations et supprimer le code, au lieu de le dupliquer à chaque test. De plus, si le le code de configuration et de suppression lui-même génère une erreur, ce qui peut indiquer les problèmes qui n'impliquent pas que les tests eux-mêmes échouent.

Conseils d'ordre général

Voici quelques conseils à retenir lorsque vous pensez à ces primitives.

Les primitives sont un guide

N'oubliez pas que les outils et les primitives ici et dans les pages suivantes correspondent exactement à Vitest, Jest, Mocha, Web Test Runner ou autre un framework spécifique. Vitest étant un guide général, assurez-vous de cartographier à votre choix de framework.

Combiner des assertions selon les besoins

Fondamentalement, les tests sont du code capable de générer des erreurs. Chaque coureur fournira un primitive, probablement test(), pour décrire des scénarios de test distincts.

Mais si cet exécuteur fournit également assert(), expect() et des assistants d'assertion, n'oubliez pas que cette partie est davantage axée sur la commodité et que vous pouvez l'ignorer si vous nécessaire. Vous pouvez exécuter tout code susceptible de générer une erreur, y compris other des bibliothèques d'assertion ou une instruction if à l'ancienne.

La configuration de l'IDE peut vous sauver la vie

Assurez-vous que votre IDE, comme VSCode, a accès à la saisie semi-automatique et la documentation sur les outils de test de votre choix peut vous rendre plus productif. Pour Par exemple, l'assertion Chai contient plus de 100 méthodes sur assert. bibliothèque, ainsi que la documentation de sorte qu'il apparaisse en ligne peut être pratique.

Cela peut être particulièrement important pour certains frameworks de test qui renseignent le d'un espace de noms global par ses méthodes de test. Il s'agit d'une différence subtile, mais il est souvent possible d'utiliser des bibliothèques de test sans les importer si elles sont automatiquement ajouté à l'espace de noms global:

// some.test.js
test('using test as a global', () => {  });

Nous vous recommandons d'importer les assistants même s'ils sont compatibles automatiquement. car cela donne à votre IDE un moyen clair de rechercher ces méthodes. (Il se peut que vous ayez rencontré ce problème lors de la compilation de React, car certains codebases possèdent une couche React global, mais d'autres non, et exigent qu'il soit importé dans tous les fichiers à l'aide de React.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => {  });