Должен сказать, что это пятерка лучших моментов в моей карьере разработчика программного обеспечения. Я сделал несколько крутых вещей, но эта действительно классная. На этой неделе мне было интересно, как получить тестовое покрытие внешнего кода… Я овладел искусством внутреннего покрытия кода, но не внешнего интерфейса. И, конечно же, не было тестового покрытия внешнего кода, который автоматически запускался Selenium WebDriver. Эта статья является результатом моего стремления выяснить, как это сделать и как сделать это правильно. Что мы здесь делаем, так это обеспечиваем себе тестовое покрытие внешнего кода, и самое интересное, что наш внешний код запускается Selenium автоматически.
Вот как это работает.
- Инструментируйте свой интерфейсный код с помощью команды
istanbul instrument
. (Насколько я знаю,istanbul instrument
записывает инструментированный код на диск, аistanbul cover
все делает в памяти.) - Вместо отправки исходного JS-кода в браузер отправьте инструментированный JS-код. Что действительно приятно, так это то, что в Стамбуле вам вообще не нужно вручную изменять исходный код, чтобы все это заработало. Istanbul делает почти всю работу за нас в браузере автоматически.
- Запустите свои тесты на основе Selenium и для каждого отдельного драйвера в ваших тестах запустите хук, который отправит результаты покрытия из браузера в процесс внутреннего тестирования.
- Как только вы получите данные о покрытии в процессе тестирования, вы можете делать с ними все, что захотите. В этом случае мы отправим данные по протоколу HTTP POST на сервер, который сможет интерпретировать и отображать результаты покрытия.
- И это все!
Итак, в деталях, как это работает.
Когда в браузере запускается JS-код с инструментами Istanbul, глобальный объект во внешнем интерфейсе заполняется данными о покрытии:
window.__coverage__ = {};
На бэкенде Selenium WebDriver может прочитать этот объект в браузере и отправить данные в наш тестовый процесс, используя этот фрагмент кода:
let obj = await driver.executeScript('return window.__coverage__;');
Теперь все, что вам нужно сделать, это отправить эти данные о покрытии на сервер, который сможет их интерпретировать. На помощь у нас есть кое-что, написанное самим стамбульским автором:
Итак, у нас где-то работает сервер, который может хранить результаты покрытия в памяти, и у него есть обработчики HTTP, которые могут автоматически обрабатывать стандартные запросы, связанные с покрытием Стамбула.
Поэтому мы используем этот код для HTTP POST на сервер, работающий с промежуточным ПО Istanbul:
exports.loadCoverage = function (driver, yourHost, yourPort) { return async cb => { await driver.switchTo().defaultContent(); const obj = await driver.executeScript( 'return window.__coverage__;' ); const str = JSON.stringify(obj); const options = { port: yourPort, host: yourHost, path: '/coverage/client', method: 'POST', headers: { 'Content-Type': 'application/json', } }; const req = http.request(options, res => { let data = ''; // you *must* listen to data event // so that the end event will fire... res.on('data', d => { data += d; }); res.once('end', function () { // Finished sending coverage data cb(); // fire the final callback }); }); req.write(str); req.end(); } };
Таким образом, вы могли бы вызвать приведенную выше процедуру с крючком в своем тесте следующим образом:
const {loadCoverage} = require('./coverage-helpers'); after.cb('send coverage data', loadCoverage(driver, host, port));
Сервер, на котором работает промежуточное ПО Istanbul, выглядит так:
const cov = require('istanbul-middleware'); const isCollectCoverage = process.env.COLLECT_COVERAGE === 'yes'; const express = require('express'); const app = express(); if (isCollectCoverage) { app.use(express.static(path.join(__dirname, 'public-coverage'))); } else { app.use(express.static(path.join(__dirname, 'public'))); } if (isCollectCoverage) { // mount istanbul middleware here app.use('/coverage', cov.createHandler()); } app.listen(8080);
Если вы хотите, вы можете клонировать этот простой сервер Node.js Express с промежуточным ПО Istanbul, которое я создал для вас:
Я использую этот bash-скрипт для инструментирования кода:
#!/usr/bin/env bash set -e # abort script on first error cd $(dirname "$0") # make sure we are in the right directory rm -rf public-coverage mkdir public-coverage cp -a public/. public-coverage/ # copy all files over istanbul instrument public \ --output public-coverage \ --embed-source true # done
После того, как все сделано, мы можем увидеть результаты покрытия на нашем сервере, работающем с istanbul-middleware, в GET ‹host›:‹port›/coverage.
Там написано public-temp/
вместо public/
, потому что я делаю временную работу в своем собственном скрипте bash. И, как видите, все красное — нам предстоит много работы, чтобы улучшить тестовое покрытие!
Наконец, одна замечательная вещь, которая упростила весь этот процесс, заключалась в том, как мы доставляем наши статические активы через наш сервер Express. Во внешнем интерфейсе мы делаем запросы на статические ресурсы следующим образом:
<!-- shared services --> <script type="text/javascript" src="shared/js/services/notification-service.js"></script> <!-- home --> <script type="text/javascript" src="pages/home/controller/home.controller.js"></script>
Другими словами, мы не делаем это (с предустановленным «общедоступным» каталогом):
<!-- shared services --> <script type="text/javascript" src="public/shared/js/services/notification-service.js"></script> <!-- home --> <script type="text/javascript" src="public/pages/home/controller/home.controller.js"></script>
Тот факт, что мы настроили сервер таким образом, делает его намного проще, потому что тогда вы можете сделать что-то очень простое, как это было упомянуто выше:
if (isCollectCoverage) { app.use(express.static(path.join(__dirname, 'public-coverage'))); } else { app.use(express.static(path.join(__dirname, 'public'))); }
Как уже упоминалось, перед запуском нашего сервера и перед запуском тестов мы инструментируем код с помощью сценария bash. Простой каталог общедоступного покрытия содержит файлы .js, которые были инструментированы Стамбулом.
Любые вопросы, идеи, комментарии или советы, пожалуйста, добавьте комментарий к истории! Цените любые голоса.
Кредиты:
Я должен отдать должное этому мошеннику attodorov в следующем треде, https://github.com/gotwarlost/istanbul/issues/132, за то, что он дал мне достаточно информации, чтобы заставить это работать. Слава маэстро!
Для будущего:
- Как мы увидим результаты теста, если наш сервер «тестового покрытия» работает на Jenkins или других серверах CI/CD? Прямо сейчас все это действительно работает, только если работает на «localhost». Большинство серверов CI/CD не позволяют вам просто предоставить свой собственный сервер покрытия, TMK.
- Хотелось бы, чтобы это произошло: https://github.com/gotwarlost/istanbul-middleware/issues/51
- Хотелось бы инструментировать файлы на лету, чтобы нам не приходилось использовать временный каталог
public-coverage.
См.: https://stackoverflow.com/questions/47880168/instrumenting-files-on-the-fly-with-istanbul