Модульные тесты, модульные тесты, модульные тесты, модульные тесты

После изменения или добавления кода вам следует запустить существующие модульные тесты и подумать о написании большего. Все тесты выполняются на несжатых версиях кода.

Существует два набора модульных тестов: тесты JS и тесты генератора блоков.

JS-тесты

JS-тесты подтверждают работу внутренних функций JavaScript в ядре Blockly. Мы используем Mocha для запуска модульных тестов, Sinon для заглушки зависимостей и Chai для утверждения кода.

Запуск тестов

Как в блочных, так и в блочных образцах npm run test запустит модульные тесты. В блочном режиме также будут выполняться другие тесты, такие как анализ и компиляция. Вы также можете открытьtests tests/mocha/index.html в браузере, чтобы в интерактивном режиме запустить все тесты Mocha.

Написание тестов

Для запуска тестов мы используем интерфейс Mocha TDD. Тесты организованы в наборы, которые могут содержать как дополнительные поднаборы, так и/или тесты. Как правило, каждый компонент Blockly (например, toolbox или workspace ) имеет собственный тестовый файл, содержащий один или несколько наборов. Каждый пакет может иметь методы setup и teardown , которые будут вызываться до и после каждого теста в этом наборе соответственно.

Помощники по тестированию

У нас есть ряд вспомогательных функций, специфичных для Blockly, которые могут быть полезны при написании тестов. Их можно найти в ядре и в блочно-сэмплах .

Вспомогательные функции включают в себя sharedTestSetup и sharedTestTeardown , которые необходимо вызывать до и после тестов (см. раздел «Требования»).

sharedTestSetup :
  • Устанавливает поддельные таймеры sinon (в некоторых тестах вам понадобится использовать this.clock.runAll ).
  • Заглушка Blockly.Events.fire для немедленного запуска (настраивается).
  • Настраивает автоматическую очистку типов блоков, определенных с помощью defineBlocksWithJsonArray .
  • Объявляет несколько свойств в контексте this , которые должны быть доступны:
    • this.clock (но его не следует восстанавливать, иначе это вызовет проблемы в sharedTestTeardown )
    • this.eventsFireStub
    • this.sharedCleanup (для использования с addMessageToCleanup и addBlockTypeToCleanup ) (ПРИМЕЧАНИЕ: вам не нужно использовать addBlockTypeToCleanup если вы определили блок с помощью defineBlocksWithJsonArray )

Функция имеет один дополнительный options для настройки. В настоящее время он используется только для определения того, следует ли заглушить Blockly.Events.fire для немедленного запуска (по умолчанию заглушка).

sharedTestTeardown :
  • Удаляет рабочую область this.workspace (в зависимости от того, где она была определена, дополнительную информацию см. в разделе «Требования к тестированию»).
  • Восстанавливает все заглушки.
  • Очищает все типы блоков, добавленные с помощью defineBlocksWithJsonArray и addBlockTypeToCleanup .
  • Очищает все сообщения, добавленные с помощью 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. Действие : вызов тестируемого кода, чтобы активировать тестируемое поведение.
  3. Assert : делайте утверждения о возвращаемом значении или взаимодействии с имитируемыми объектами, чтобы проверить правильность.

В простом тесте может не быть никакого поведения, которое нужно было бы организовать, и этапы действия и утверждения можно объединить, встроив в утверждение вызов тестируемого кода. В более сложных случаях ваши тесты будут более читабельными, если вы будете придерживаться этих трех этапов.

Вот пример тестового файла (упрощенный по сравнению с реальным).

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

Что следует отметить из этого примера:

  • Пакет может содержать другие пакеты, которые имеют дополнительные методы setup и teardown .
  • Каждый набор и тест имеют описательное имя.
  • Утверждения Chai используются для создания утверждений о коде.
    • Вы можете указать необязательный строковый аргумент, который будет отображаться в случае неудачного завершения теста. Это упрощает отладку неработающих тестов.
    • Порядок параметров: chai.assert.equal(actualValue, expectedValue, optionalMessage) . Если вы поменяете местами actual и expected , сообщения об ошибках не будут иметь смысла.
  • 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. Загрузите tests/generators/index.html в Firefox или Safari. Обратите внимание, что Chrome и Opera имеют ограничения безопасности, которые не позволяют загружать тесты из локальной системы «file://» (ошибки 41024 и 47416 ).
  2. Выберите соответствующую часть системы для тестирования из раскрывающегося меню и нажмите «Загрузить». Блоки должны появиться в рабочей области.
  3. Нажмите «JavaScript».
    Скопируйте и запустите сгенерированный код в консоли JavaScript. Если вывод заканчивается «ОК», тест пройден.
  4. Нажмите «Питон».
    Скопируйте и запустите сгенерированный код в интерпретаторе Python . Если вывод заканчивается «ОК», тест пройден.
  5. Нажмите «PHP».
    Скопируйте и запустите сгенерированный код в интерпретаторе PHP . Если вывод заканчивается «ОК», тест пройден.
  6. Нажмите «Луа».
    Скопируйте и запустите сгенерированный код в интерпретаторе Lua . Если вывод заканчивается «ОК», тест пройден.
  7. Нажмите на «Дарт».
    Скопируйте и запустите сгенерированный код в интерпретаторе Dart . Если вывод заканчивается «ОК», тест пройден.

Редактирование тестов генератора блоков

  1. Загрузите tests/generators/index.html в браузер.
  2. Выберите соответствующую часть системы из раскрывающегося меню и нажмите «Загрузить». Блоки должны появиться в рабочей области.
  3. Вносите любые изменения или дополнения в блоки.
  4. Нажмите «XML».
  5. Скопируйте сгенерированный XML в соответствующий файл tests/generators/ .