Test delle unità

Dopo aver modificato o aggiunto codice, devi eseguire i test delle unità esistenti e valutare la possibilità di scriverne altri. Tutti i test vengono eseguiti sulle versioni non compresse del codice.

Esistono due insiemi di test delle unità: test JS e test del generatore di blocchi.

Test JS

I test JS confermano il funzionamento delle funzioni JavaScript interne nel nucleo di Blockly. Utilizziamo Mocha per eseguire i test delle unità, Sinon per stubre le dipendenze e Chai per creare asserzioni relative al codice.

Test in esecuzione

Sia negli esempi a blocchi che in quelli a blocchi, npm run test eseguirà i test delle unità. In blockly, verranno eseguiti anche altri test come linting e compilazione. Puoi anche aprire tests/mocha/index.html in un browser per eseguire in modo interattivo tutti i test mocha.

Test di scrittura

Utilizziamo l'interfaccia TDD di Mocha per eseguire i test. I test sono organizzati in suite, che possono contenere sia sottosuite aggiuntive e/o test. In genere, ogni componente di Blockly (ad esempio toolbox o workspace) ha il proprio file di test che contiene una o più suite. Ogni suite può avere un metodo setup e teardown che verrà chiamato rispettivamente prima e dopo ogni test della suite.

Componenti di supporto per i test

Abbiamo una serie di funzioni di supporto specifiche per Blockly che possono essere utili per scrivere i test. Puoi trovarli in core e in blockly-samples.

Le funzioni helper includono sharedTestSetup e sharedTestTeardown, che devono essere chiamate prima e dopo i test (consulta la sezione Requisiti).

sharedTestSetup:
  • Configura timer falsi di sinon (in alcuni test dovrai utilizzare this.clock.runAll).
  • Blocca Blockly.Events.fire in modo che si attivi immediatamente (configurabile).
  • Configura la pulizia automatica dei tipi di blocco definiti tramite defineBlocksWithJsonArray.
  • Dichiara alcune proprietà del contesto this che dovrebbero essere accessibili:
    • this.clock (ma non deve essere ripristinato, altrimenti causerà problemi in sharedTestTeardown)
    • this.eventsFireStub
    • this.sharedCleanup (da utilizzare con addMessageToCleanup e addBlockTypeToCleanup) (NOTA: non è necessario utilizzare addBlockTypeToCleanup se hai definito il blocco utilizzando defineBlocksWithJsonArray)

La funzione ha un parametro facoltativo options per configurare la configurazione. Al momento, viene utilizzato solo per determinare se attivare o meno lo stub Blockly.Events.fire immediatamente (verrà attivato per impostazione predefinita).

sharedTestTeardown:
  • Gestisce l'area di lavoro this.workspace (a seconda di dove è stata definita, consulta la sezione Requisiti di test per ulteriori informazioni).
  • Ripristina tutti gli stub.
  • Ripulisce tutti i tipi di blocchi aggiunti tramite defineBlocksWithJsonArray e addBlockTypeToCleanup.
  • Consente di eliminare tutti i messaggi aggiunti tramite addMessageToCleanup.

Requisiti per i test

  • Ogni test deve chiamare sharedTestSetup.call(this); come prima riga nel setup della suite più esterna e sharedTestTeardown.call(this); come ultima riga nel teardown della suite più esterna per un file.
  • Se hai bisogno di uno spazio di lavoro con una cassetta degli attrezzi generica, puoi utilizzare una delle cassettine degli attrezzi predefinite nel test index.html. Di seguito è riportato un esempio.
  • Devi smaltire correttamente this.workspace. Nella maggior parte dei test, definirai this.workspace nella suite più esterna e la utilizzerai per tutti i test successivi, ma in alcuni casi potresti definirla o ridefinirla in una suite interna (ad esempio, uno dei tuoi test richiede uno spazio di lavoro con opzioni diverse da quelle che hai configurato inizialmente). Deve essere smaltito al termine del test.
    • Se definisci this.workspace nella suite più esterna e non la ridefinisci mai, non sono necessarie ulteriori azioni. Verrà smaltito automaticamente da sharedTestTeardown.
    • Se definisci this.workspace per la prima volta in una suite interna (ovvero non lo hai definito nella suite più esterna), devi eliminarlo manualmente chiamando workspaceTeardown.call(this, this.workspace) durante lo smantellamento della suite.
    • Se definisci this.workpace nella suite più esterna, ma poi la ridefinisci in una suite di test interna, devi innanzitutto chiamare workspaceTeardown.call(this, this.workspace) prima di ridefinire l'ambiente per eliminare l'area di lavoro originale definita nella suite di primo livello. Devi anche eliminare manualmente il nuovo valore chiamando di nuovo workspaceTeardown.call(this, this.workspace) nel teardown di questa suite interna.

Struttura del test

I test di unità in genere seguono una struttura fissa, che può essere riassunta come organizza, esegui, verifica.

  1. Organizza: configura lo stato del mondo e le eventuali condizioni necessarie per il comportamento in fase di test.
  2. Azione: chiama il codice in test per attivare il comportamento in esame.
  3. Assert: effettua affermazioni sul valore restituito o sulle interazioni con oggetti simulati al fine di verificarne la correttezza.

In un test semplice, potrebbe non essere necessario organizzare alcun comportamento e le fasi di azione e verifica possono essere combinate inserendo in linea la chiamata al codice in test nella verifica. Per i casi più complessi, i test saranno più leggibili se attieniti a queste tre fasi.

Ecco un esempio di file di test (semplificato rispetto al file reale).

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');
    });
  });
});

Aspetti importanti di questo esempio:

  • Una suite può contenere altre suite con metodi setup e teardown aggiuntivi.
  • Ogni suite e ogni test ha un nome descrittivo.
  • Le asserzioni Chai vengono utilizzate per fare affermazioni sul codice.
    • Puoi fornire un argomento stringa facoltativo che verrà visualizzato se il test non va a buon fine. In questo modo è più facile eseguire il debug dei test non funzionanti.
    • L'ordine dei parametri è chai.assert.equal(actualValue, expectedValue, optionalMessage). Se scambi actual e expected, i messaggi di errore non avranno senso.
  • Sinon viene usato per eseguire lo stubing dei metodi quando non si vuole chiamare il codice reale. In questo esempio, non vogliamo chiamare la funzione delle metriche reali perché non è pertinente per questo test. Ci interessa solo come vengono utilizzati i risultati dal metodo in prova. Sinon sostituisce la funzione getMetrics in modo da restituire una risposta predefinita che possiamo verificare facilmente nelle nostre asserzioni di test.
  • I metodi setup per ogni suite devono contenere solo la configurazione generica che si applica a tutti i test. Se un test per un determinato comportamento si basa su una determinata condizione, questa deve essere chiaramente indicata nel test pertinente.

Test di debug

  • Puoi aprire i test in un browser e utilizzare gli strumenti per sviluppatori per impostare punti di interruzione e verificare se i test non superano inaspettatamente (o se vengono superati inaspettatamente).
  • Imposta .only() o .skip() su un test o una suite per eseguire solo quel determinato insieme di test o salta un test. Ad esempio:

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

    Ricordati di rimuoverli prima di eseguire il commit del codice.

Test del generatore di blocchi

Ogni blocco ha i propri test di unità. Questi test verificano che i blocchi generino codice che funzioni come previsto.

  1. Carica tests/generators/index.html in Firefox o Safari. Tieni presente che Chrome e Opera hanno limitazioni di sicurezza che impediscono il caricamento dei test dal sistema locale "file://" (problemi 41024 e 47416).
  2. Scegli la parte pertinente del sistema da testare dal menu a discesa e fai clic su "Carica". Nell'area di lavoro dovrebbero essere visualizzati i blocchi.
  3. Fai clic su "JavaScript".
    Copia ed esegui il codice generato in una console JavaScript. Se l'output termina con "OK", il test è stato superato.
  4. Fai clic su "Python".
    Copia ed esegui il codice generato in un interprete Python. Se l'output termina con "OK", il test è stato superato.
  5. Fai clic su "PHP".
    Copia ed esegui il codice generato in un interprete PHP. Se l'output termina con "OK", il test è stato superato.
  6. Fai clic su "Lua".
    Copia ed esegui il codice generato in un interprete Lua. Se l'output termina con "OK", il test è stato superato.
  7. Fai clic su "Dart".
    Copia ed esegui il codice generato in un interprete Dart. Se l'output termina con "OK", il test è stato superato.

Modificare i test del generatore di blocchi

  1. Carica tests/generators/index.html in un browser.
  2. Scegli la parte pertinente del sistema dal menu a discesa e fai clic su "Carica". I blocchi dovrebbero apparire nell'area di lavoro.
  3. Apporta eventuali modifiche o aggiunte ai blocchi.
  4. Fai clic su "XML".
  5. Copia il codice XML generato nel file appropriato in tests/generators/.