单元测试

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

单元测试分为两组:JS 测试和块生成器测试。

JS 测试

JS 测试可确认 Blockly 核心中的内部 JavaScript 函数的运行情况。我们使用 Mocha 运行单元测试,使用 Sinon 对依赖项进行存根,使用 Chai 对代码进行断言。

运行测试

在 blockly 和 blockly-samples 中,npm run test 都会运行单元测试。在代码块中,此操作还会运行其他测试,例如执行 lint 请求和编译。您还可以在浏览器中打开 tests/mocha/index.html,以交互方式运行所有 Mocha 测试。

编写测试

我们使用 Mocha TDD 接口运行测试。测试会整理到套件中,套件可以包含其他子套件和/或测试。通常,Blockly 的每个组件(例如 toolboxworkspace)都有自己的测试文件,其中包含一个或多个套件。每个套件都可以有一个 setupteardown 方法,分别在该套件中的每个测试之前和之后调用。

测试帮助程序

我们提供了一些专门针对 Blockly 的辅助函数,在编写测试时可能会派上用场。这些文件可在 coreblockly-samples 中找到。

辅助函数包括 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),以手动处理新值。

测试结构

单元测试通常遵循一套结构,可总结为准备、操作、断言

  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 来桩方法。在此示例中,我们不想调用真实的 metrics 函数,因为它与此测试无关。我们只关心被测方法如何使用这些结果。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”。
    Dart 解释器中复制并运行生成的代码。如果输出以“OK”结尾,则表示测试已通过。

修改分块生成器测试

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