Tests unitaires

Après avoir modifié ou ajouté du code, nous vous recommandons d'exécuter des tests unitaires existants et d'envisager d'en écrire d'autres. Tous les tests sont exécutés sur les versions non compressées du code.

Il existe deux ensembles de tests unitaires: les tests JavaScript et les tests du générateur de blocs.

Tests JS

Les tests JavaScript confirment le fonctionnement des fonctions JavaScript internes dans le noyau de Blockly. Nous utilisons Mocha pour exécuter des tests unitaires, Sinon pour les dépendances de bouchon et Chai pour effectuer des assertions sur le code.

Exécution des tests

Dans Blockly et blockly-samples, npm run test exécute les tests unitaires. Dans Blockly, d'autres tests tels que l'analyse lint et la compilation sont également exécutés. Vous pouvez également ouvrir tests/mocha/index.html dans un navigateur pour exécuter de manière interactive tous les tests mocha.

Écrire des tests

Nous utilisons l'interface Mocha TDD pour exécuter les tests. Les tests sont organisés en suites, qui peuvent contenir à la fois des sous-suites et/ou des tests supplémentaires. En général, chaque composant de Blockly (comme toolbox ou workspace) possède son propre fichier de test contenant une ou plusieurs suites. Chaque suite peut avoir des méthodes setup et teardown qui sont appelées avant et après, respectivement, chaque test de cette suite.

Assistants de test

Il existe un certain nombre de fonctions d'assistance spécifiques à Blockly qui peuvent être utiles lors de l'écriture de tests. Vous les trouverez dans core et dans blockly-samples.

Les fonctions d'assistance incluent sharedTestSetup et sharedTestTeardown, qui doivent être appelées avant et après vos tests (voir la section "Conditions requises").

sharedTestSetup :
  • Configure les faux minuteurs sans risque (dans certains tests, vous devrez utiliser this.clock.runAll).
  • Stubs Blockly.Events.fire se déclenche immédiatement (configurable).
  • Configure le nettoyage automatique des blockTypes définis dans defineBlocksWithJsonArray.
  • Déclaration de quelques propriétés sur le contexte this qui sont censées être accessibles :
    • this.clock (mais ne doit pas être restauré, sinon cela entraînera des problèmes dans sharedTestTeardown)
    • this.eventsFireStub
    • this.sharedCleanup (à utiliser avec addMessageToCleanup et addBlockTypeToCleanup) (REMARQUE: vous n'avez pas besoin d'utiliser addBlockTypeToCleanup si vous avez défini le bloc à l'aide de defineBlocksWithJsonArray)

La fonction comporte un paramètre options facultatif pour configurer la configuration. Actuellement, il n'est utilisé que pour déterminer si Blockly.Events.fire doit être instancié immédiatement (par défaut).

sharedTestTeardown :
  • Élimine l'espace de travail this.workspace (selon l'emplacement où il a été défini, consultez la section "Exigences de test" pour en savoir plus).
  • Restaure tous les bouchons.
  • Nettoie tous les types de blocs ajoutés via defineBlocksWithJsonArray et addBlockTypeToCleanup.
  • Nettoie tous les messages ajoutés via addMessageToCleanup.

Exigences concernant les tests

  • Chaque test doit appeler sharedTestSetup.call(this); comme première ligne de la configuration de la suite la plus externe et sharedTestTeardown.call(this); comme dernière ligne de la déconstruction de la suite la plus externe pour un fichier.
  • Si vous avez besoin d'un espace de travail avec une boîte à outils générique, vous pouvez utiliser l'une des boîtes à outils prédéfinies sur le index.html de test. Vous trouverez un exemple ci-dessous.
  • Vous devez supprimer this.workspace comme il se doit. Dans la plupart des tests, vous définirez this.workspace dans la suite externe et l'utiliserez pour tous les tests suivants, mais dans certains cas, vous pourrez le définir ou le redéfinir dans une suite interne (par exemple, l'un de vos tests nécessite un espace de travail avec des options différentes de celles que vous avez configurées initialement). Il doit être jeté à la fin du test.
    • Si vous définissez this.workspace dans la suite la plus externe et ne la redéfinissez jamais, aucune autre action n'est requise. Elle sera automatiquement supprimée par sharedTestTeardown.
    • Si vous définissez this.workspace pour la première fois dans une suite interne (c'est-à-dire que vous ne l'avez pas défini dans la suite la plus externe), vous devez vous en débarrasser manuellement en appelant workspaceTeardown.call(this, this.workspace) lors du démontage de cette suite.
    • Si vous définissez this.workpace dans la suite de plus haut niveau, puis que vous le redéfinissez dans une suite de tests interne, vous devez d'abord appeler workspaceTeardown.call(this, this.workspace) avant de le redéfinir afin de supprimer l'espace de travail d'origine défini dans la suite de premier niveau. Vous devez également supprimer manuellement la nouvelle valeur en appelant à nouveau workspaceTeardown.call(this, this.workspace) lors du démontage de cette suite interne.

Structure des tests

Les tests unitaires suivent généralement une structure définie, qui peut être résumée comme suit : arranger, agir, affirmer.

  1. Organiser: configurez l'état du monde et toutes les conditions nécessaires au comportement testé.
  2. Agir: appelez le code testé pour déclencher le comportement testé.
  3. Assert: effectuez des assertions sur la valeur renvoyée ou les interactions avec des objets simulés afin de vérifier leur exactitude.

Dans un test simple, il peut n'y avoir aucun comportement à organiser, et les étapes d'action et d'assertion peuvent être combinées en intégrant l'appel au code testé dans l'assertion. Pour les cas plus complexes, vos tests seront plus lisibles si vous vous en tenez à ces trois étapes.

Voici un exemple de fichier de test (simplifié par rapport à la réalité).

suite('Flyout', function() {
  setup(function() {
    sharedTestSetup.call(this);
    this.toolboxXml = document.getElementById('toolbox-simple');
    this.workspace = Blockly.inject('blocklyDiv',
        {
          toolbox: this.toolboxXml
        });
  });

  teardown(function() {
    sharedTestTeardown.call(this);
  });

  suite('simple flyout', function() {
    setup(function() {
      this.flyout = this.workspace.getFlyout();
    });
    test('y is always 0', function() {
      // Act and assert stages combined for simple test case
      chai.assert.equal(this.flyout.getY(), 0, 'y coordinate in vertical flyout is 0');
    });
    test('x is right of workspace if flyout at right', function() {
      // Arrange
      sinon.stub(this.flyout.targetWorkspace, 'getMetrics').returns({
        viewWidth: 100,
      });
      this.flyout.targetWorkspace.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT;
      this.flyout.toolboxPosition_ = Blockly.TOOLBOX_AT_RIGHT;

      // Act
      var x = this.flyout.getX();

      // Assert
      chai.assert.equal(x, 100, 'x is right of workspace');
    });
  });
});

Points à noter concernant cet exemple:

  • Une suite peut contenir d'autres suites qui disposent de méthodes setup et teardown supplémentaires.
  • Chaque suite et chaque test ont un nom descriptif.
  • Les assertions Chai permettent de faire des assertions sur le code.
    • Vous pouvez fournir un argument de chaîne facultatif qui s'affichera en cas d'échec du test. Cela facilite le débogage des tests défectueux.
    • L'ordre des paramètres est chai.assert.equal(actualValue, expectedValue, optionalMessage). Si vous échangez actual et expected, les messages d'erreur n'auront aucun sens.
  • Sinon permet de créer des bouchons de méthode lorsque vous ne souhaitez pas appeler le code réel. Dans cet exemple, nous ne voulons pas appeler la fonction de métriques réelles, car elle n'est pas pertinente pour ce test. Nous ne nous soucions que de la manière dont les résultats sont utilisés par la méthode testée. Sinon, il remplace la fonction getMetrics pour renvoyer une réponse standard que nous pouvons facilement vérifier dans nos assertions de test.
  • Les méthodes setup de chaque suite ne doivent contenir que la configuration générique qui s'applique à tous les tests. Si un test d'un comportement particulier repose sur une certaine condition, cette condition doit être clairement indiquée dans le test concerné.

Tests de débogage

  • Vous pouvez ouvrir les tests dans un navigateur et utiliser les outils de développement pour définir des points d'arrêt et déterminer si vos tests échouent de manière inattendue (ou réussissent de manière inattendue).
  • Définissez .only() ou .skip() sur un test ou une suite pour n'exécuter que cet ensemble de tests, ou ignorer un test. Exemple :

    suite.only('Workspace', function () {
      suite('updateToolbox', function () {
        test('test name', function () {
          // ...
        });
        test.skip('test I don’t care about', function () {
          // ...
        });
      });
    });
    

    N'oubliez pas de les supprimer avant de valider votre code.

Tests du générateur de blocs

Chaque bloc dispose de ses propres tests unitaires. Ces tests vérifient que les blocs génèrent du code plutôt que des fonctions comme prévu.

  1. Chargez tests/generators/index.html dans Firefox ou Safari. Notez que Chrome et Opera comportent des restrictions de sécurité qui empêchent le chargement des tests à partir du système local "file://" (problèmes 41024 et 47416).
  2. Dans le menu déroulant, choisissez la partie pertinente du système à tester, puis cliquez sur "Charger". Des blocs doivent apparaître dans l'espace de travail.
  3. Cliquez sur "JavaScript".
    Copiez et exécutez le code généré dans une console JavaScript. Si la sortie se termine par "OK", le test a réussi.
  4. Cliquez sur "Python".
    Copiez et exécutez le code généré dans un interpréteur Python. Si la sortie se termine par "OK", le test a réussi.
  5. Cliquez sur "PHP".
    Copiez et exécutez le code généré dans un interpréteur PHP. Si la sortie se termine par "OK", le test a réussi.
  6. Cliquez sur "Lua".
    Copiez et exécutez le code généré dans un interpréteur Lua. Si la sortie se termine par "OK", le test a réussi.
  7. Cliquez sur "Dart".
    Copiez et exécutez le code généré dans un interpréteur Dart. Si la sortie se termine par "OK", le test a réussi.

Modifier les tests du générateur de blocs

  1. Chargez tests/generators/index.html dans un navigateur.
  2. Sélectionnez la partie du système concernée dans le menu déroulant, puis cliquez sur "Charger". Des blocs doivent apparaître dans l'espace de travail.
  3. Apportez les modifications ou ajouts souhaités aux blocs.
  4. Cliquez sur "XML".
  5. Copiez le fichier XML généré dans le fichier approprié de tests/generators/.