Использование Node.js и mock-fs

Обратите внимание, что в этой статье мы попытаемся рассказать о некоторых проблемах высокого уровня, связанных с внедрением сценариев проверки в процесс непрерывной интеграции. Хотя будут даны некоторые технические примеры, мы не будем предоставлять каждую отдельную деталь (например, как настроить систему непрерывной интеграции с нуля, использовать менеджеры пакетов и т. д., и предполагается наличие некоторых предварительных знаний).

Автоматизированный охват

Мы начали внедрять сценарии линтинга и проверки в наш процесс непрерывной интеграции в Mavenlink (благодарность моему коллеге Хуанке, который первым представил их). Эти скрипты помогают нам не только обеспечивать соблюдение стиля кода — то, что линтеры делают целую вечность, — но также подталкивают всю нашу команду к соблюдению определенных правил кодирования; автоматический охват, если хотите.

Мы обнаружили, что, несмотря на то, что обед и обучение, RFC, руководства по обучению, рецензирование коллег, объединение в пары и т. п. — все это может помочь направить культуру команды на внедрение определенных передовых методов, самих по себе этого недостаточно. . Лучшие практики должны быть интегрированы прямо в вашу систему непрерывной интеграции, чтобы люди обращали на них внимание в ходе своего обычного рабочего процесса, возможно, соблазняя их принять рассматриваемое руководство по кодированию.

Сочувствие

Может показаться немного макиавеллистским, если кто-то отклонит чей-то pull request GitHub, потому что он не следовал практике, которую поддерживает какой-то всезнающий Code Gatekeeper. Нам нужно проявить сочувствие и понять, что неудачная сборка этого разработчика может означать, что им придется пойти и сказать своему менеджеру, что функция X будет реализована немного дольше, чем было обещано… очевидно, это не весело!

Мы должны сделать все возможное, чтобы направить их в правильном направлении с помощью содержательных сообщений об ошибках и, желательно, ссылки на любую соответствующую документацию, которая может помочь им быстро решить проблему:

Этот разработчик, который сталкивается с этим сообщением, выигрывает, показывая:

  • как запустить скрипт проверки из своей локальной среды разработки
  • куда обратиться, чтобы получить дополнительную информацию о правилах использования SVG

Такой консольный вывод полезен как начинающим разработчикам, так и забывчивым старожилам.

Проверка

Как уже упоминалось, для проверки мы используем сценарии Node.js. Обычно они включают следующее:

  • возможность как требоваться как модуль, так и запускаться из командной строки
module.exports = {
  EXPORTED_METHODS
};
// Script is being ran off the command line
if (require.main === module) {
  // exit code causing circleci to fail the PR
  const exitStatus = main();
  process.exit(exitStatus);
}
  • подстановка в файлах, которые мы хотим проверить
  • Одно или несколько регулярных выражений для работы с содержимым вышеуказанных файлов
  • Совпадения с регулярным выражением могут означать нарушение. Те собираются, а потом выводятся в консоль
  • Ненулевые коды выхода выводятся для 1 или более указанных нарушений. В нашем случае это означает для нашей системы CI, что произошло нарушение и, по сути, не удается выполнить соответствующий запрос на вытягивание (обсуждается далее в статье).

Вкус деталей

Не вдаваясь во все подробности того, как работают эти сценарии проверки, мы можем изучить одну процедуру, которая берет список частичных данных на стороне сервера и ищет любые вызовы svg_iconhelper с жестко заданное имя значка. В нашей практике это является нарушением, так как мы предпочитаем использовать загрузчик Webpack svg-sprite-loader — это позволяет нам импортировать зависимость svg прямо из того же файла, который используется, а также не жестко кодировать SVG. имя значка, которое требует глобальной зависимости от значка, фактически помещаемого в спрайт SVG страницы.

function getAllSVGUsageInSSR(serverSideRenderingFiles) {
  function getSvgIconSVGsUsages(filePath) {
    const contents = fs.readFileSync(filePath, 'utf8');
    const matches = [];
    let myArray = [];
    const regex = /(?:svg_icon).?["']([^',"]*)["']/g;
    while ((myArray = regex.exec(contents)) !== null) { // eslint-disable-line no-cond-assign
      matches.push(myArray[1]);
    }
    return matches;
  }
return serverSideRenderingFiles.reduce((usedSVGs, file) => (
    usedSVGs.concat(getSvgIconSVGsUsages(file))
  ), []);
}

Этот метод берет список серверных файлов, считывает их содержимое один за другим, а затем сопоставляет регулярное выражение, которое указывает на вышеупомянутое нарушение нарушения. Наконец, он возвращает список этих неправильно используемых значков SVG.

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

Тестирование

Когда мы впервые представили эти сценарии проверки, мы взволнованно подключили их к нашей системе непрерывной интеграции, написав их в очень типичной процедурной манере сценариев командной строки оболочки и Node.js.

Не потребовалось много времени, чтобы понять, что качество кода в этих сценариях с точки зрения рецензирования и охвата тестами не соответствует остальным нашим стандартам кодирования приложений (обычно мы обеспечиваем покрытие спецификаций для представленных код). Появились баги 😞.

Одна из причин, по которой тесты не были введены, заключалась в том, что может быть непонятно, как реплицировать файловое дерево, в котором эти скрипты глобализируются — mock-fs спешит на помощь!

Эти спецификации обычно включают следующее:

  • mock-fs для упрощения заглушки файловой системы
  • соответствующий сценарий реализации (также известный как тестируемая система или SUT)

Вот пример настройки спецификации, использующей mock-fs:

const fs = require('fs');
const mock = require('mock-fs');
const scraper = require('./verify-svg-scraper.js');
describe('server rendered files', () => {
  beforeEach(() => {
    mock({
      frontend: {
        'erb-svg.js': '',
      },
      app: {
        views: {
          'foo.html.erb': 'svg_icon("icon-bar")',
          'bar.html.erb': 'svg_icon("icon-baz")..svg_icon("icon-second-misuse")',
          ...
        },
      },
    });
  });
  afterEach(() => {
    mock.restore();
  });

Выше мы создали своего рода скелет файловой системы, который является минимальным для использования и запуска сценария проверки.

Хотя я считаю, что использование mock довольно очевидно, здесь есть несколько интересных моментов:

  • frontend/erb-svg.js — это файл, в который пишет наша SUT. Он начинается пустым, но мы можем вызывать определенные методы для нашей реализации, а затем утверждать против написания определенного содержимого.
  • Файлы app/views/*.html.erb являются входными данными для нашей SUT.
  • bar.html.erb намеренно имеет два вызова svg_icon, которые мы используем для проверки хитрого использования в JavaScript несколько глобальных захваченных групп.

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

Наши юнит-тесты, как правило, довольно просты. Например:

describe('unit tests', () => {
  it('should get erb files', () => {
    const actual = scraper.getErbFiles();
    expect(actual.length).toEqual(2);
    ['foo', 'bar'].forEach((name) => {
      expect(actual.some((filePath) => filePath.includes(`${name}.html.erb`))).toBe(true);
    });
  });
});

Учитывая скелетное дерево файлов, которое мы создали в предыдущем примере с помощью mock-js, вполне логично, что парсер нашел 2 файла: app/views/foo.html.erb и app/views/bar.html.erbсоответственно.

Интеграционные тесты

Иногда вводится интеграционный тест (не всегда), так как проверяемые скрипты, как правило, довольно маленькие. Вот пример:

describe('integration tests', () => {
  it('reports all svgIcon calls with hard coded icons', () => {
    const actual = scraper.verifyServerSideSVGs();
    expect(actual).toEqual(3);
  });
});

Возвращаемое значение 3 при вызове verifyServerSideSVGs представляет собой общее количество обнаруженных нарушений. Это, в свою очередь, будет использоваться сценарием для вызова process.exit узла с ненулевым кодом выхода, что, в свою очередь, заставит нашу выбранную систему непрерывной интеграции CircleCI сообщить об ошибке на Гитхаб:

Непрерывная интеграция

Прекрасная ошибка запроса на вытягивание github, которую вы видите выше (asset_linters), происходит из-за того, что наш скрипт подключен от нашего .circle/config.yml как шаг выполнения к нашему asset_linters работа:

- run:
    name: Lint SVGs as Webpack modules
    command: yarn lint:svgs

Где команда, определенная в нашем разделе package.json scripts , является фактическим вызовом командной строки нашего сценария проверки:

“lint:svgs”: “node script/node/verify-svg-scraper.js”,

Хотя приведенная выше специфика CircleCI может не относиться к выбранным вами непрерывным инструментам, общая идея выхода с ненулевым кодом выхода, скорее всего, может быть универсально применима к любой системе CI, которую вы решили использовать.

Вывод

Эта статья была преднамеренно поверхностной для краткости, но, надеюсь, предоставила вам обзор вещей, которые следует учитывать в отношении использования сценариев проверки. Эти сценарии действительно могут помочь вам направить вашу команду к принятию определенных правил кодирования в автоматическом режиме. Стоя на плечах mock-fs, мы обнаружили, что этот подход достаточно эффективен и прост в реализации.