單元測試

變更或新增程式碼後,您應執行現有的單元測試,並考慮編寫更多項目。所有測試都是在程式碼的未壓縮版本上執行。

單元測試分為兩組:JS 測試和區塊產生器測試。

JS 測試

JS 測試可確認 Blockly 核心中的內部 JavaScript 函式運作。我們會使用 Mocha 執行單元測試、使用 Sinon 執行虛設常式測試,以及使用 Chai 對程式碼做出斷言。

執行測試

在區塊範例和區塊範例中,npm run test 都會執行單元測試。在區塊中,這麼做也會執行其他測試,例如 Lint 和編譯。你也可以在瀏覽器中開啟 tests/mocha/index.html,以互動方式執行所有 Mocha 測試。

撰寫測試

我們使用 Mocha TDD 介面執行測試。測試會整理成套件,其中可包含額外的子套件和/或測試。一般來說,Blockly 的每個元件 (例如 toolboxworkspace) 都有自己的測試檔案,其中包含一或多個套件。每個套件都可以有 setupteardown 方法,系統會在該套件中的每個測試前後分別呼叫。

測試輔助程式

我們提供許多 Blockly 專屬的輔助函式,這些函式在編寫測試時可能很實用。您可以在核心區塊範例中找到這些程式庫。

輔助函式包含 sharedTestSetupsharedTestTeardown,此為測試前後需要呼叫 (請參閱「需求條件」一節)。

sharedTestSetup:
  • 設定 Sinon 假計時器 (在某些測試中,您需要使用 this.clock.runAll)。
  • 使用 Stubs Blockly.Events.fire 立即觸發 (可設定)。
  • 針對透過 defineBlocksWithJsonArray 定義的 blockType 設定自動清理功能。
  • this 結構定義上宣告幾個可供存取的屬性:
    • this.clock (但不應還原,會導致 sharedTestTeardown 發生問題)
    • this.eventsFireStub
    • this.sharedCleanup (與 addMessageToCleanupaddBlockTypeToCleanup 搭配使用) (注意:如果您使用 defineBlocksWithJsonArray 定義區塊,則不必使用 addBlockTypeToCleanup)

這個函式提供一個可選用的 options 參數來設定設定。目前,這只是用來判斷是否要立即啟動 Blockly.Events.fire 以立即觸發 (預設為虛設常式)。

sharedTestTeardown:
  • 處理工作區 this.workspace (視定義位置而定,詳情請參閱「測試規定」一節)。
  • 還原所有虛設常式。
  • 清理透過 defineBlocksWithJsonArrayaddBlockTypeToCleanup 新增的所有區塊類型。
  • 清理透過 addMessageToCleanup 新增的所有訊息。

測試規定

  • 每次測試都必須呼叫 sharedTestSetup.call(this); 做為最外套件設定程序的第一行,並將 sharedTestTeardown.call(this); 做為檔案最外包套件的最後一行。
  • 如果您需要含一般工具箱的工作區,可以在測試 index.html 上使用其中一個預設工具箱。請參考以下範例。
  • 您必須正確處理 this.workspace。在大部分的測試中,您會在最外層套件中定義 this.workspace,並用於所有後續測試,但在某些情況下,您可以在內部套件中定義或重新定義該套件 (例如其中一個測試需要有選項與原先設定的工作區)。請務必在測試結束時丟棄。
    • 如果在最外層套件中定義了 this.workspace,且不再重新定義,則無須採取進一步行動。這會由 sharedTestTeardown 自動處置。
    • 如果您在內部套件中首次定義 this.workspace (也就是未在最外套件中定義),必須在該套件的拆解中呼叫 workspaceTeardown.call(this, this.workspace),以手動方式捨棄此套件。
    • 如果您在最外層套件中定義了 this.workpace,然後在內部測試套件中重新定義,則必須先在重新定義程式庫前呼叫 workspaceTeardown.call(this, this.workspace),才能拆除頂層套件中定義的原始工作區。您也必須在內部套件的撕頁中再次呼叫 workspaceTeardown.call(this, this.workspace),以手動方式處理新值。

測試結構

單元測試通常會遵循一組結構,這些結構可以匯總為排列、行為、斷言

  1. 排列:為測試中的行為設定世界狀態以及任何必要條件。
  2. Act:呼叫受測試的程式碼,觸發接受測試的行為。
  3. 「Assert」:針對傳回值或與模擬物件的互動做出斷言,以便驗證正確性。

在簡單的測試中,可能沒有任何行為可以安排,而且行為和宣告階段可以透過斷言中測試中的程式碼來組合。如果是較複雜的情況,只要持續完成這 3 個階段,測試就能更容易理解。

以下是測試檔案範例 (由實際內容簡化)。

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

本範例的注意事項:

  • 一個套件可以包含其他具有額外 setupteardown 方法的套件。
  • 每個套件和測試都有描述性的名稱。
  • 使用 Chai 斷言用於對程式碼的斷言。
    • 您可以提供選用的字串引數,以便在測試失敗時顯示。這可以幫助您更輕鬆地對故障的測試進行偵錯。
    • 參數的順序為 chai.assert.equal(actualValue, expectedValue, optionalMessage)。如果替換 actualexpected,錯誤訊息就不會合理。
  • 如果您不想呼叫實際程式碼,可以使用 Sinon 來虛設常式方法。在此範例中,我們不想呼叫實際的指標函式,因為它與此測試無關。我們只需要注意測試方法如何使用結果。Sinon 建立虛設常式 getMetrics 函式可傳回罐頭回應,方便我們在測試宣告中進行檢查。
  • 每個套件的 setup 方法只能包含適用於所有測試的一般設定。如果特定行為的測試仰賴特定條件,則應在相關測試中清楚載明。

偵錯測試

  • 您可以在瀏覽器中開啟測試,並使用開發人員工具設定中斷點,並調查測試是否意外失敗 (或意外通過!)。
  • 在測試或套件上設定 .only().skip(),以便僅執行該一組測試,或是略過測試。例如:

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

    請記得在提交程式碼前移除這些項目。

區塊產生器測試

每個區塊都有自己的單元測試。這些測試會驗證是否封鎖產生的程式碼,使其不如預期。

  1. 在 Firefox 或 Safari 中載入 tests/generators/index.html。請注意,Chrome 和 Opera 設有安全性限制,導致無法從本機「file://」系統載入測試 (問題 4102447416)。
  2. 從下拉式選單中選擇要測試的系統相關部分,然後按一下「載入」。區塊應該顯示在工作區中。
  3. 按一下「JavaScript」。
    在 JavaScript 控制台中複製並執行產生的程式碼。如果輸出內容的結尾是「OK」,表示測試已通過。
  4. 按一下「Python」。
    Python 解譯器中複製並執行產生的程式碼。如果輸出內容的結尾為「OK」,表示測試已通過。
  5. 按一下 [PHP]。
    PHP 解譯器中複製並執行產生的程式碼。如果輸出內容的結尾為「OK」,表示測試已通過。
  6. 按一下「Lua」。
    Lua 解譯器中複製並執行產生的程式碼。 如果輸出內容的結尾為「OK」,表示測試已通過。
  7. 按一下「飛鏢」。
    Dart 解譯器中複製並執行產生的程式碼。 如果輸出內容的結尾為「OK」,表示測試已通過。

編輯區塊產生器測試

  1. 在瀏覽器中載入 tests/generators/index.html
  2. 從下拉式選單中選取系統的相關部分,然後按一下「載入」。區塊應該顯示在工作區中。
  3. 在區塊中進行變更或新增。
  4. 按一下「XML」。
  5. 將產生的 XML 複製到 tests/generators/ 中的適當檔案。