单元测试

更改或添加代码后,您应运行现有的单元测试并考虑编写更多单元测试。所有测试都在未压缩的代码版本上执行。

单元测试分为两组: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)。
  • 将 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),以手动处置新值。

测试结构

单元测试通常遵循集合结构,可以概括为“arrange, act, assert”。

  1. Arrange:设置世界状态以及被测行为的所有必要条件。
  2. 操作:调用被测代码以触发被测试的行为。
  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. 从下拉菜单中选择要测试的系统相关部分,然后点击“Load”。方块应该会显示在工作区中。
  3. 点击“JavaScript”。
    复制生成的代码并在 JavaScript 控制台中运行。如果输出以“OK”结尾,则表示测试已通过。
  4. 点击“Python”。
    复制生成的代码并在 Python 解释器中运行。如果输出以“OK”结尾,则表示测试已通过。
  5. 点击“PHP”。
    复制生成的代码并在 PHP 解释器中运行。 如果输出以“OK”结尾,则表示测试已通过。
  6. 点击“Lua”。
    复制生成的代码并在 Lua 解释器中运行。如果输出以“OK”结尾,则表示测试已通过。
  7. 点击“Dart”。
    复制生成的代码并在 Dart 解释器中运行。如果输出以“OK”结尾,则表示测试已通过。

修改块生成器测试

  1. 在浏览器中加载 tests/generators/index.html
  2. 从下拉菜单中选择系统的相关部分,然后点击“Load”。方块应该会显示在工作区中。
  3. 对组成要素进行更改或添加。
  4. 点击“XML”。
  5. 将生成的 XML 复制到 tests/generators/ 中的相应文件。