Testy jednostkowe

Po zmianie lub dodaniu kodu należy uruchomić istniejące testy jednostkowe oraz rozważyć napisanie kolejnych. Wszystkie testy są wykonywane na nieskompresowanych wersjach kodu.

Istnieją 2 zbiory testów jednostkowych: testy JS i testy generatora bloków.

Testy JS

Testy JS sprawdzają działanie wewnętrznych funkcji JavaScriptu w rdzeniu Blockly. Korzystamy z mechanizmu Mocha do testów jednostkowych, Sinon do ograniczania zależności i Chai do tworzenia asercji kodu.

Uruchamianie testów

Zarówno w blockly, jak i w blockly-samples polecenie npm run test uruchamia testy jednostkowe. W blokowanych momentach zostaną też uruchomione inne testy, takie jak lintowanie i kompilacja. Możesz też otworzyć tests/mocha/index.html w przeglądarce, aby interaktywnie uruchomić wszystkie testy Mocha.

Testy pisania

Do testowania używamy interfejsu TDD Mocha. Testy są grupowane w pakiety, które mogą zawierać dodatkowe podpakiety lub testy. Zazwyczaj każdy komponent Blockly (np. toolbox lub workspace) ma własny plik testowy, który zawiera co najmniej 1 test. Każdy pakiet może mieć metodę setupteardown, która będzie wywoływana odpowiednio przed każdym testem w danym pakiecie i po nim.

Pomocnicze narzędzia testowe

Mamy kilka funkcji pomocniczych dostępnych w Blockly, które mogą być przydatne podczas pisania testów. Można je znaleźć w rdzeniu i w blockly-samples.

Funkcje pomocnicze to sharedTestSetupsharedTestTeardown, które należy wywołać przed i po testach (patrz sekcja Wymagania).

sharedTestSetup:
  • Konfiguruje fałszywe minutniki Sinon (w niektórych testach musisz użyć this.clock.runAll).
  • Stubs Blockly.Events.fire uruchamia się natychmiast (można skonfigurować).
  • Konfiguruje automatyczne czyszczenie typów blokowania zdefiniowanych w elementach defineBlocksWithJsonArray.
  • Deklaruje kilka właściwości w kontekście this, które mają być dostępne:
    • this.clock (nie należy go jednak przywracać, ponieważ może to spowodować problemy w sharedTestTeardown)
    • this.eventsFireStub
    • this.sharedCleanup (do użycia z addMessageToCleanupaddBlockTypeToCleanup) (UWAGA: jeśli blok został zdefiniowany za pomocą elementu defineBlocksWithJsonArray, nie musisz używać elementu addBlockTypeToCleanup).

Funkcja ma 1 opcjonalny parametr options, który służy do konfigurowania. Obecnie służy tylko do określenia, czy stub Blockly.Events.fire ma zostać wywołany natychmiast (domyślnie tak się stanie).

sharedTestTeardown:
  • Usuwa obszar roboczy this.workspace (w zależności od miejsca zdefiniowania, więcej informacji znajdziesz w sekcji Wymagania dotyczące testów).
  • przywraca wszystkie puste miejsca;
  • Usuwa wszystkie typy bloków dodane za pomocą defineBlocksWithJsonArray i addBlockTypeToCleanup.
  • Czyści wszystkie wiadomości dodane przez addMessageToCleanup.

Wymagania dotyczące testów

  • Każdy test musi wywoływać sharedTestSetup.call(this); jako pierwszy wiersz konfiguracji pakietu zewnętrznego i sharedTestTeardown.call(this); jako ostatni wiersz dezaktywacji najbardziej zewnętrznego pakietu pliku.
  • Jeśli potrzebujesz obszaru roboczego z ogólnym zestawem narzędzi, możesz użyć jednego z gotowych zestawów narzędzi do testu index.html. Przykład znajdziesz poniżej.
  • Urządzenie this.workspace należy wyrzucić w odpowiedni sposób. W większości testów definiujesz zmienną this.workspace w zewnętrznym pakiecie i używasz jej we wszystkich kolejnych testach, ale w niektórych przypadkach możesz ją zdefiniować lub zdefiniować ponownie w wewnętrznym pakiecie (np. gdy jeden z testów wymaga workspace z innymi opcjami niż te, które zostały pierwotnie skonfigurowane). Po zakończeniu testu należy go wyrzucić.
    • Jeśli zdefiniujesz element this.workspace w zewnętrznym zestawie i nigdy go nie zmodyfikujesz, nie musisz podejmować żadnych dodatkowych działań. Zostanie on automatycznie usunięty przez firmę sharedTestTeardown.
    • Jeśli this.workspace zostanie zdefiniowany po raz pierwszy w podzestawie wewnętrznym (czyli nie został zdefiniowany w zewnętrznym podzestawie), musisz go usunąć ręcznie, wywołując funkcję workspaceTeardown.call(this, this.workspace) w ramach rozkładania tego podzestawu.
    • Jeśli zdefiniujesz usługę this.workpace w zewnętrznym pakiecie, a potem zdefiniujesz ją ponownie w wewnętrznym pakiecie testowym, musisz najpierw wywołać metodę workspaceTeardown.call(this, this.workspace) przed ponownym zdefiniowaniem jej w celu dezaktywacji oryginalnego obszaru roboczego zdefiniowanego w pakiecie najwyższego poziomu. Musisz też ręcznie usunąć nową wartość, ponownie wywołując funkcję workspaceTeardown.call(this, this.workspace) podczas rozkładania tego pakietu wewnętrznego.

Struktura testu

Testy jednostkowe zwykle mają ustaloną strukturę, którą można podsumować jako uporządkuj, wykonaj, sprawdź.

  1. Uporządkuj: ustaw stan świata i wszystkie warunki wymagane do testowanego zachowania.
  2. Działanie: wywołaj testowany kod, aby aktywować testowane działanie.
  3. Assert twierdzenie dotyczące wartości zwracanej lub interakcji z udawanymi obiektami w celu sprawdzenia poprawności.

W przypadku prostego testu zachowanie może nie wymagać aranżacji, a etapy działania i sprawdzenia można połączyć, umieszczając w zasadzie wywołanie kodu poddanego testom. W przypadku bardziej skomplikowanych przypadków testy będą łatwiejsze do odczytania, jeśli będziesz przestrzegać tych 3 etapów.

Oto przykładowy plik testowy (uproszczony w rzeczywistości).

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

Uwagi dotyczące tego przykładu:

  • W ramach pakietu mogą się znajdować inne pakiety, które mają dodatkowe metody setupteardown.
  • Każdy zestaw i każdy test ma nazwę opisową.
  • Założenia Chai służą do tworzenia założeń dotyczących kodu.
    • Możesz podać opcjonalny argument w postaci ciągu, który będzie wyświetlany w przypadku niepowodzenia testu. Ułatwia to debugowanie niedziałających testów.
    • Kolejność parametrów to chai.assert.equal(actualValue, expectedValue, optionalMessage). Jeśli zamienisz actual na expected, komunikaty o błędach nie będą miały sensu.
  • Metoda Sinon jest stosowana, gdy nie chcesz wywoływać prawdziwego kodu. W tym przykładzie nie chcemy wywoływać funkcji realMetrics, ponieważ nie jest ona istotna dla tego testu. Interesuje nas tylko sposób wykorzystania wyników przez testowaną metodę. Sinon zastępuje funkcję getMetrics, aby zwracała gotową odpowiedź, którą możemy łatwo sprawdzić w naszych stwierdzeniach testowych.
  • Metody setup dla każdego pakietu powinny zawierać tylko ogólną konfigurację, która ma zastosowanie do wszystkich testów. Jeśli testowanie określonego zachowania zależy od określonego warunku, należy wyraźnie podać ten warunek w odpowiednim teście.

Testy debugowania

  • Testy możesz otworzyć w przeglądarce i użyć narzędzi dla programistów, aby ustawić punkty przerwania i sprawdzić, czy testy nie wypadły nieoczekiwanie (lub okazały się nieoczekiwane).
  • Ustaw .only() lub .skip() dla testu lub zestawu testów, aby uruchomić tylko ten zestaw testów lub pominąć test. 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.

Testy generatora bloków

Każdy blok ma własne testy jednostkowe. Te testy sprawdzają, czy bloki generują kod, który działa zgodnie z oczekiwaniami.

  1. Otwórz tests/generators/index.html w Firefoksie lub Safari. Pamiętaj, że Chrome i Opera mają ograniczenia bezpieczeństwa, które uniemożliwiają wczytywanie testów z lokalnego systemu „file://” (problemy 4102447416).
  2. W menu wybierz odpowiednią część systemu, którą chcesz przetestować, a następnie kliknij „Wczytaj”. W obszarze roboczym powinny pojawić się bloki.
  3. Kliknij „JavaScript”.
    Skopiuj wygenerowany kod i uruchom go w konsoli JavaScriptu. Jeśli dane wyjściowe kończą się komunikatem „OK”, test został zaliczony.
  4. Kliknij „Python”.
    Skopiuj i uruchom wygenerowany kod w interpreterze Pythona. Jeśli dane wyjściowe kończą się na „OK”, test został zaliczony.
  5. Kliknij „PHP”.
    Skopiuj wygenerowany kod i uruchom go w interpreterze PHP. Jeśli dane wyjściowe kończą się na „OK”, test został zaliczony.
  6. Kliknij „Lua”.
    Skopiuj i uruchom wygenerowany kod w interpreterze Lua. Jeśli w wyniku pojawi się „OK”, test zakończył się powodzeniem.
  7. Kliknij „Dart”.
    Skopiuj wygenerowany kod i uruchom go w tłumaczu Dart. Jeśli dane wyjściowe kończą się na „OK”, test został zaliczony.

Edytowanie testów generatora bloków

  1. Wczytaj tests/generators/index.html w przeglądarce.
  2. W menu wybierz odpowiednią część systemu, a potem kliknij „Wczytaj”. W obszarze roboczym powinny pojawić się bloki.
  3. wprowadzać żadnych zmian ani dodatków w blokach.
  4. Kliknij „XML”.
  5. Skopiuj wygenerowany plik XML do odpowiedniego pliku w folderze tests/generators/.