Testy jednostkowe

Po zmianie lub dodaniu kodu uruchom istniejące testy jednostkowe i zastanów się nad napisaniem kolejnych. Wszystkie testy są wykonywane na nieskompresowanych wersjach kodu.

Dostępne są 2 zestawy testów jednostkowych: testy JS i testy z generatorem bloków.

Testy JS

Testy JS potwierdzają działanie wewnętrznych funkcji JavaScript w rdzeni Blockly. Używamy Mocha do testów jednostkowych, Sinon do skrócenia zależności oraz Chai do twierdzeń o kodzie.

Przeprowadzane testy

Zarówno w przypadku prób blokowych, jak i blokowanych, npm run test uruchomi testy jednostkowe. W bloku uruchomione zostaną też inne testy, takie jak lintowanie i kompilacja. Możesz też otworzyć tests/mocha/index.html w przeglądarce, aby interaktywnie przeprowadzać wszystkie testy mokki.

Pisanie testów

Do testów używamy interfejsu Mocha TDD. Testy są podzielone na pakiety, które mogą zawierać zarówno dodatkowe podpakiety, jak i testy. Zasadniczo każdy komponent Blockly (np. toolbox czy workspace) ma własny plik testowy, który zawiera co najmniej 1 pakiet. Każdy pakiet może zawierać metody setup i teardown, które będą wywoływane odpowiednio przed każdym testem w danym pakiecie i po nim.

Pomocnicy w testowaniu

Dla Blockly mamy wiele funkcji pomocniczych, które mogą być przydatne podczas pisania testów. Można je znaleźć w podstawowych i fragmentach blokowych.

Do funkcji pomocniczych należą sharedTestSetup i sharedTestTeardown, które wymagane, aby trzeba je było wywoływać przed testami i po nich (patrz sekcja Wymagania).

sharedTestSetup:
  • Konfiguruje fałszywe liczniki czasu (w niektórych testach trzeba użyć this.clock.runAll).
  • Zdarzenie Stubs Blockly.Events.fire będzie uruchamiać się natychmiast (można skonfigurować).
  • Konfiguruje automatyczne czyszczenie typów blockType zdefiniowanych przy użyciu defineBlocksWithJsonArray.
  • Deklaruje w kontekście this kilka właściwości, które mają być dostępne:
    • this.clock (ale nie należy go przywrócić, ponieważ spowoduje to problemy w zadaniu sharedTestTeardown)
    • this.eventsFireStub
    • this.sharedCleanup (do użytku z addMessageToCleanup i addBlockTypeToCleanup) (UWAGA: nie musisz używać addBlockTypeToCleanup, jeśli blok został zdefiniowany za pomocą defineBlocksWithJsonArray)

Funkcja ma 1 opcjonalny parametr options do skonfigurowania konfiguracji. Obecnie służy ona tylko do określenia, czy kod Blockly.Events.fire powinien zostać natychmiast uruchomiony (domyślnie jest to niegotowe).

sharedTestTeardown:
  • Usuwa obszar roboczy this.workspace (w zależności od tego, gdzie został zdefiniowany, więcej informacji znajdziesz w sekcji Wymagania dotyczące testów).
  • Przywróć wszystkie namiastki.
  • Usuwa wszystkie typy blokad dodane przez defineBlocksWithJsonArray i addBlockTypeToCleanup.
  • Czyści wszystkie wiadomości dodane przy użyciu filtra addMessageToCleanup.

Wymagania testowe

  • Każdy test musi wywołać sharedTestSetup.call(this); jako pierwszy wiersz konfiguracji pakietu najbardziej zewnętrznego i sharedTestTeardown.call(this); jako ostatni wiersz podczas dezaktywacji pakietu najbardziej zewnętrznego pliku.
  • Jeśli potrzebujesz obszaru roboczego ze standardowym zestawem narzędzi, możesz użyć jednego z gotowych zestawów narzędzi na potrzeby testów index.html. Przykład znajdziesz poniżej.
  • this.workspace należy pozbyć się poprawnie. W większości testów definiujesz this.workspace w pakiecie zewnętrznym i używasz go we wszystkich kolejnych testach, ale w niektórych przypadkach możesz zdefiniować ją lub zmienić na nowo w pakiecie wewnętrznym (na przykład jeden z testów wymaga obszaru roboczego z innymi opcjami niż pierwotnie skonfigurowane). Należy zutylizować się po zakończeniu testu.
    • Jeśli zdefiniujesz właściwość this.workspace w najbardziej zewnętrznym pakiecie i nigdy jej nie zmienisz, nie musisz nic robić. Zostanie automatycznie usunięta przez sharedTestTeardown.
    • Jeśli po raz pierwszy zdefiniujesz właściwość this.workspace w pakiecie wewnętrznym (czyli nie została zdefiniowana w najbardziej zewnętrznym), musisz ją pozbyć się ręcznie, wywołując workspaceTeardown.call(this, this.workspace) przy demontażu pakietu.
    • Jeśli zdefiniujesz obiekt this.workpace w najbardziej zewnętrznym pakiecie, a następnie zdefiniujesz go ponownie w wewnętrznym pakiecie testowym, musisz najpierw wywołać metodę workspaceTeardown.call(this, this.workspace) przed ponownym zdefiniowaniem jej w celu usunięcia oryginalnego obszaru roboczego zdefiniowanego w pakiecie najwyższego poziomu. Nową wartość musisz też usunąć ręcznie, ponownie wywołując metodę workspaceTeardown.call(this, this.workspace) podczas wyłączania tego pakietu wewnętrznego.

Struktura testów

Testy jednostkowe mają zwykle strukturę opartą na zbiorze danych, który można podsumować w postaci rozmieszczania, wykonywania i wykonywania.

  1. Rozmieść: określ stan świata i wszelkie warunki wymagane do przeprowadzenia testu.
  2. Działanie: wywołaj testowany kod, aby aktywować testowane zachowanie.
  3. Zgłoś: zgłoś wartość zwracaną lub interakcje z imitowanymi obiektami, aby zweryfikować ich poprawność.

W prostym teście może nie być żadnych działań do ustawienia, a etapy działania i zgłaszania można połączyć, umieszczając wywołanie do testowanego kodu w twierdzeniu. W bardziej złożonych przypadkach testy będą bardziej czytelne, jeśli będziesz przestrzegać tych 3 etapów.

Oto przykładowy plik testowy (uproszczony w stosunku do rzeczywistego).

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

Rzeczy, o których warto pamiętać w tym przykładzie:

  • Pakiet może zawierać inne pakiety zawierające dodatkowe metody setup i teardown.
  • Każdy pakiet i test ma opisową nazwę.
  • Asercje czai służą do wyrażania potwierdzeń dotyczących kodu.
    • Możesz podać opcjonalny argument tekstowy, który będzie wyświetlany w razie niepowodzenia testu. Ułatwia to debugowanie nieudanych testów.
    • Kolejność parametrów: chai.assert.equal(actualValue, expectedValue, optionalMessage). Jeśli zamienisz actual i expected, komunikaty o błędach nie będą miały sensu.
  • Sinon służy do pomijania metod, gdy nie chcesz wywoływać prawdziwego kodu. W tym przykładzie nie chcemy wywoływać funkcji danych rzeczywistych, ponieważ nie ma ona związku z testem. Interesuje nas tylko, jak testowana metoda wykorzystuje wyniki. Sinon przerywa działanie funkcji getMetrics, aby zwrócić szablon odpowiedzi, którą możemy łatwo sprawdzić w potwierdzeniach testowych.
  • Metody setup poszczególnych pakietów powinny zawierać tylko konfigurację ogólną obowiązującą w przypadku wszystkich testów. Jeśli test na konkretne zachowanie zależy od określonego warunku, należy go wyraźnie wskazać w odpowiednim teście.

Testy debugowania

  • Możesz otworzyć testy w przeglądarce i użyć narzędzi dla programistów, aby ustawić punkty przerwania i sprawdzić, czy testy kończą się nieoczekiwaniem (lub kończą się przypadkowo).
  • Ustaw .only() lub .skip() w teście lub pakiecie, aby uruchomić tylko ten zestaw testów albo pomiń jeden z nich. Na przykład:

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

    Pamiętaj, aby usunąć je przed zatwierdzeniem kodu.

Blokuj testy generatorów

Każdy blok ma własne testy jednostkowe. Te testy sprawdzają, czy bloki generują kod niż funkcje.

  1. Wczytaj tests/generators/index.html w Firefoksie lub Safari. W Chrome i Operze obowiązują ograniczenia zabezpieczeń, które uniemożliwiają ładowanie testów z lokalnego systemu „file://” (problemy 41024 i 47416).
  2. Wybierz w menu odpowiednią część systemu do przetestowania i kliknij „Wczytaj”. W obszarze roboczym powinny pojawić się blokady.
  3. Kliknij „JavaScript”.
    Skopiuj i uruchom wygenerowany kod w konsoli JavaScriptu. Jeśli wynik kończy się na „OK”, test się zakończył.
  4. Kliknij „Python”.
    Skopiuj i uruchom wygenerowany kod w interpreterze Pythona. Jeśli dane wyjściowe zakończą się na „OK”, test się nie powiódł.
  5. Kliknij „PHP”.
    Skopiuj i uruchom wygenerowany kod w interpreterze języka PHP. Jeśli dane wyjściowe zakończą się na „OK”, test się nie powiódł.
  6. Kliknij „Lua”.
    Skopiuj i uruchom wygenerowany kod w tłumaczeniu Lua. Jeśli dane wyjściowe zakończą się na „OK”, test się nie powiódł.
  7. Kliknij „Dart”.
    Skopiuj i uruchom wygenerowany kod w interpreterze Dart. Jeśli dane wyjściowe zakończą się na „OK”, test się nie powiódł.

Edytowanie testów generatorów bloków

  1. Wczytaj tests/generators/index.html w przeglądarce.
  2. Z menu wybierz odpowiednią część systemu i kliknij „Wczytaj”. W obszarze roboczym powinny pojawić się blokady.
  3. Wprowadź w blokach dowolne zmiany lub dodatki.
  4. Kliknij „XML”.
  5. Skopiuj wygenerowany kod XML do odpowiedniego pliku w usłudze tests/generators/.