Предупреждение: эта статья в основном посвящена техническому искусству и ремеслу.

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

Мы используем Mocha для запуска модульных тестов в Node (nodejs). Дженкинс наблюдает за нашим репозиторием git и запускает тесты, когда мы вводим новый код.

Конечный продукт (github и пакет npm) просматривается в браузере, автоматически обновляется и выглядит примерно так:

Введение

С M ocha мы можем запустить $ mocha ./src/**/*.test.js для создания консольного отчета, который выглядит примерно так:

Но знаете ли вы, что с помощью репортера можно выводить результаты тестов в любом формате? Mocha даже предоставляет множество примеров / забавных репортеров.

Мы можем использовать:

mocha ./src/**/*.test.js --reporter <reporterName>

флаг --reporter позволяет нам использовать настраиваемый репортер.

Один файл или пакет?

Написание собственного настраиваемого репортера может быть таким же простым, как создание одного файла Javascript в существующем проекте и ссылки на него с помощью флага -reporter.

Для демонстрации Mocha предоставляет пример кода настраиваемого репортера, который мы можем сохранить в корне нашего проекта и ссылаться на него, например: mocha ./src/**/*.test.js -reporter reporter.js

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

Начиная

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

# Example code from Mocha - we just export this one function
module.exports = MyReporter;
function MyReporter(runner) {
  ...
}

Наш пользовательский репортер будет иметь следующий формат:

function MyReporter(runner) {
  // 1. Store intermediate values 
  //    to be inserted into the HTML template later
  
  // 2. Update these values when tests run, pass or fail.
  
  // 3. When the runner ends, update the HTML file
}

Окончательный результат можно увидеть в apb-mocha-reporter / src / apbmochareporter.js на github.

Расчет значений, которые мы хотим отобразить

На основе примера кода я начал с написания перехватчиков для событий прошел и не прошел, суммировав количество тестов в каждой категории, чтобы получить значения, которые мы вставим в наш HTML. Позже я обнаружил, что эти значения уже подсчитаны для меня runner.stats, поэтому я воспользуюсь этим.

Однако я также хочу выводить подробные результаты тестов в формате JSON, поэтому мне все равно нужно написать несколько ловушек. Я начинаю с объявления некоторых массивов с ограниченной областью действия для хранения результатов тестирования, а затем вставляю в эти массивы всякий раз, когда срабатывает перехватчик событий. Вот пример ловушки для проверки «пройден»:

var json_passes = [];
runner.on('pass', function(test){
    json_passes.push(test);
    update() // print to the console.
});

Теперь у нас есть полный контроль над выводом - давайте отформатируем его как HTML.

Вывод HTML - шаблонизатор в 12 строк кода.

Программная генерация или создание HTML - это не ракетостроение. Типичный подход:

  1. Создайте шаблон HTML, близкий к тому, что вы хотите.
  2. Определите переменные части шаблона, которые вы хотите изменить.
  3. Напишите свой код, чтобы получить запасные части.
  4. Используйте механизм шаблонов, чтобы загрузить HTML и заменить переменные запасными частями.

Существует множество движков для создания шаблонов. Усы едины. Handlebars, doT и EJS тоже делают свою работу. Все они в основном работают одинаково, но все они представляют собой дополнительную зависимость, которая нам не нужна. Мы не делаем здесь ничего сложного, и я бы предпочел оставить этот пакет без зависимостей. Так что строим сами.

Замена Regex

Регулярные выражения (regex) - это замечательная, мощная и сложная концепция, которую я не буду вдаваться в подробности. Регулярные выражения не уникальны для Javascript, и даже в JS существует несколько способов их использования. В нашем случае мы воспользуемся методом string.replace(). (Полная документация здесь)

str.replace(regexp|substr, newSubstr|function)

Мы собираемся взять строку с именем template и найти все вхождения слов, заключенных в двойные волнистые скобки, например: {{word}}. Затем мы собираемся заменить это слово созданным нами значением - в данном случае результатом нашего теста.

Например, предположим, что мы вычислили процент успешно выполненных тестов как: run_percent, и в нашем шаблоне мы написали {{run_percent}}. Мы могли бы написать: template.replace(/{{run_precent}}/g, run_percent)

Литерал регулярного выражения окружен косой чертой. Буква g означает поиск по всему миру.

Но мы можем добиться большего. Мы можем найти любое слово, окруженное волнистыми линиями, с помощью следующего регулярного выражения: /{{.*?}}/g

. означает любой символ, кроме новой строки.
* означает соответствие предыдущему элементу 0 или более раз.
? означает, что предыдущий элемент должен быть «нежадным», т.е. Самый короткий матч.

Наконец, мы не собираемся просто заменять каждую работу в волнистых линиях одним значением. Если мы передадим функцию replace() в качестве второго параметра, мы сможем стать намного умнее ...

let newContent = template.replace(/{{.*?}}/g, function(match){
  return replacements[match]
})

Здесь Match - это слово, которое находит регулярное выражение. replacements - это объект с ключами для каждой из переменных нашего шаблона (волнистые линии) и значениями того, чем мы хотим их заменить. Вот пример:

// Example of replacements object
{
      "{{lastrun_date}}": new Date(),
      "{{run_percent}}": run_percent,
      "{{run_numerator}}": sum,
      "{{run_denominator}}": total
}

Мы завершаем все это с помощью кода чтения / записи файла в небольшой красивой функции, называемой updateBillboard, и у нас есть механизм создания шаблонов в 12 строках Javascript.

Наконец, функция updateBillboard вызывается из события runners end.

Обработка аргументов командной строки

Я хочу, чтобы пользователь мог контролировать работу репортера. Самый простой пример - запретить вывод в консоль.

Mocha предоставляет основной способ передачи аргументов командной строки репортеру через флаг —-reporter-options. Этот вариант плохо документирован - в основной документации только сказано:

Usage: mocha [debug] [options] [files]
Options:
...
-O, --reporter-options <k=v,k2=v2,...>  reporter-specific options
...

Это означает, что мы можем использовать либо -O, либо —- reporter-options, за которым следует список параметров, разделенных запятыми (без пробелов!). У каждой опции может быть значение, указанное после знака равенства. Это значение не является обязательным.

Что нигде не описано (кроме примера кода), так это то, как reporter-options передаются репортеру:

function MyReporter(runner, options) {
  const SILENT = options.reporterOptions.silent
  ...
}

Второй параметр параметров передается в функцию-репортер как объект, содержащий объект reporterOptions, который содержит каждый параметр и его значение.
Пример:

// Test command
mocha ./**.test.js --reporter MyReporter --reporter-options silent,savejson=example

Предоставляет нам следующее в нашем пользовательском репортере:

// console.log(options) inside MyReporter()
{ 
  reporterOptions: { silent: true, savejson: 'example' },
  globals: [],
  files: [] 
}

Если значение не указано (например, для silent), по умолчанию используется логическое значение true.

Использование аргумента командной строки для предотвращения журналов

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

const SILENT = options.reporterOptions.silent
function write(str){
  if(!SILENT) process.stdout.write(str);
}

Теперь мы заменяем все журналы на write (), и когда передается опция silent, ничего не происходит!

Я использую process.stdout.write(str);, поэтому могу вручную обрабатывать новые строки (\n) и символы возврата каретки (\r) для перезаписи предыдущего вывода.

Вывод

И вуаля!

Статус нашего теста отображается где-то в типично унылой офисной среде. Поддельный завод и все такое.

Я быстро поискал существующие плагины jenkins / репортеры мокко, но не нашел ничего на 100% подходящего. К тому же это было весело.

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

Если у вас есть предложения по функциям, дайте мне знать на github!

Я Эйдан Брин, и я руковожу консалтингом по программному обеспечению в Дублине, Ирландия. Если вам понравился этот пост, подумайте о том, чтобы подписаться на меня в твиттере или подписаться на мой личный список рассылки для получения обновлений реже, чем раз в месяц.