Перевод статьи «Understanding code coverage in Cypress».
Покрытие кода – это одна из тех немногих вещей, которые не предоставляются в Cypress сразу из коробки. Чтобы настроить покрытие кода, необходимо обладать некоторыми знаниями в области веб-разработки.
Эта статься ориентирована на тех, кто только начинает знакомиться с тем, как создаются веб-приложения, поэтому если вам не нужна демонстрация того, что делают Browserify, Babel или Webpack, вы можете пролистать до части 4. Если вы ищете, как настроить покрытие кода с помощью Cypress, я рекомендую вам отличную документацию или вебинар команды Cypress на эту тему.
Содержание
- Из чего состоят веб-приложения?
- Упаковка JavaScript-файлов
- Преобразование JavaScript-файлов
- Что такое инструментирование?
- Настройка Cypress
- О чем говорит покрытие кода?
Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ
Из чего состоят веб-приложения?
Наше приложение (есть репозиторий, который вы можете клонировать) очень простое. Это просто сайт с кнопкой, которая генерирует случайный и абсолютно бесполезный факт. Знание этих фактов не приносит абсолютно никакой пользы в вашу жизнь. Но, используя эти факты в разговоре, вы, безусловно, станете более занудным. Так что вот так.
Это приложение содержит три файла, как и положено веб-приложениям: index.html, app.js и style.css.
Каждый из них играет свою роль в нашем приложении. Наши файлы .css
и .js
связаны между собой в нашем html-файле, внутри тега <head>
:
<head> <link rel="stylesheet" href="style.css"> <script src="app.js" defer></script> </head>
Файл app.js
отвечает за все самое интересное на сайте: нажав на кнопку, вы увидите наш случайный факт. Файл выглядит следующим образом:
const $ = document.querySelector.bind(document) $('button').addEventListener('click', () => { $('p').textContent = 'A rainbow can be seen only in the morning or late afternoon. It can occur only when the sun is 40 degrees or less above the horizon.' })
Наша страница содержит пустой абзац. Когда мы нажимаем на кнопку, появляется факт о радуге. Один факт не является таким уж случайным и делает наш сайт скучным. Или, по крайней мере, еще более скучным, чем сейчас.
К счастью для нас, существует API для случайных бесполезных фактов. Вы правильно поняли, я не шучу. Давайте им воспользуемся. Для этого я создам еще один файл, который будет использовать наш API для случайного бесполезного факта.
const axios = require('axios') module.exports.randomFact = async () => { const res = await axios.get('https://uselessfacts.jsph.pl/random.json?language=en') .then(function (response) { return response.data.text }) return res; }
Мы собираемся использовать наш модуль randomFact.js
внутри app.js
и получать случайный факт из API при помощи функции:
const $ = document.querySelector.bind(document) const { randomFact } = require("./randomFact"); $('button').addEventListener('click', () => { randomFact().then( (text) => { $('p').textContent = text }); })
Упаковка файлов JavaScript
Однако если мы попробуем открыть наше приложение в браузере, появится ошибка: require is not defined
. Это происходит потому, что require
не существует в контексте браузера. Создается впечатление, что мы не можем включить несколько файлов в наше приложение, но на самом деле это не так.
Поскольку мы хотим использовать наш randomFact.js
, обычным путем решения является объединение нашего файла app.js
и всего, на что он ссылается. Мы создадим один файл, на который будем ссылаться в нашем html-теге <head>
.
Для этого мы применим инструмент под названием Browserify. Он преобразует наш файл app.js
в бандл, который свяжет все в один файл в удобном для браузера виде. Мы сделаем это с помощью следующей команды:
npx browserify app/app.js -o app/bundle.js
На выходе (-o
) файла app.js
будет новый файл под названием bundle.js
. С этого момента мы будем использовать в нашем html именно bundle.js
:
<head> <link rel="stylesheet" href="style.css"> <script src="bundle.js" defer></script> </head>
Преобразование JavaScript-файлов
Теперь наше приложение работает, и это замечательно. Если мы заглянем в файл bundle.js
, то увидим, что он содержит код из app.js
, код randomFact
и код модуля axios
, который мы используем в нашей функции randomFact
.
Теперь, когда мы собрали наш код в пакет, мы можем его модифицировать. Мы можем убрать все пробелы, удалить комментарии и использовать короткие имена переменных. Это особенно полезно для больших проектов, содержащих сотни js-файлов.
Другим примером такой модификации может быть обеспечение совместимости кода со старыми версиями браузеров. Часто можно встретить различные инструменты, решающие эти задачи (вероятно, вы слышали о Babel, Webpack или Browserify).
Такую компоновку и преобразование часто называют билдом приложения. Для покрытия кода мы построим преобразованную версию нашего приложения. Но вместо того, чтобы уменьшить его размер, мы сделаем его больше.
Что такое инструментирование?
По-моему, Амир Рустамзаде достаточно хорошо изложил это в вебинаре о покрытии кода с помощью Cypress. Посмотрите его. Вместе с Глебом Бахмутовым они отлично объяснили, как все это работает на самом деле.
Я лишь вкратце расскажу, как работает покрытие кода.
Допустим, у нас есть очень простая функция, которая складывает числа, например, так:
const addition = (a, b) => { return a + b }
При покрытии кода мы хотим собрать данные о том, была ли эта функция действительно вызвана. Самый простой способ узнать это – поместить внутри нашей функции счетчик. При каждом вызове функции наша переменная i
будет увеличиваться.
let i = 0; const addition = (a, b) => { i++ return a + b } addition(1,2) console.log(i) // 1 addition(3,5) console.log(i) // 2 addition(14,8) console.log(i) // 3
По такому же принципу работает покрытие кода! Все очень просто. Считается, какие функции были вызваны, а какие пропущены.
Добавлять эти счетчики вручную, конечно же, не стоит. Вот тут-то и приходит на помощь процесс билда приложения. Мы можем преобразовать наше приложение таким образом, чтобы включить эти счетчики в каждую его функцию и условие.
Browserify не предназначен для решения подобных задач, но мы можем использовать Babelify, который представляет собой Babel-плагин для Browserify. Babelify преобразует наш код с помощью Babel.
Babel – это действительно мощный инструмент, позволяющий преобразовывать код различными способами. Самый простой способ использования Babel – это преобразование кода для совместимости со старыми браузерами, например Internet Explorer 11. Такое преобразование необходимо при использовании синтаксиса ES6 (например, стрелочных функций или async
-функций).
Babel обладает широкими возможностями настройки и поддерживает различные плагины, которые помогают преобразовывать код различными способами. Один из таких плагинов – babel-plugin-istanbul
.
Чтобы настроить наш плагин Babel, мы добавим следующую конфигурацию в файл babel.config.js
:
module.exports = { 'presets': [ [ '@babel/preset-env' ] ], plugins: [ ['babel-plugin-istanbul', { extension: ['.js'] }] ] };
Если у вас сейчас голова идет кругом, не волнуйтесь. Представьте, что ваш код проходит через несколько фильтров. Это как изделие на фабрике, которое начинается на одном конце конвейера и заканчивается на другом.
Ваш app.js
попадет в >>> Browserify, где все js-файлы будут объединены в bundle.js
.
После этого файл bundle.js
обрабатывается >>> Babel, который преобразует наш код с помощью >>> babel-plugin-istanbul
.
Плагин берет файл bundle.js
и инструментирует наш код (добавляет счетчики вызовов функций) в конечный файл bundle.js
. Это хорошо видно, когда мы вызываем команду browserify
с опцией transform (-t
).
npx browserify app/app.js -t babelify -o app/bundle.js
Загляните в файл bundle.js
и посмотрите, насколько он изменился с тех пор, как мы в последний раз комплектовали его с Browserify.
Настройка Cypress
Хотите верьте, хотите нет, но наше приложение теперь является инструментированным. Теперь собираются данные о том, какая функция была вызвана. Если открыть файл index.html
и ввести в браузер window.__coverage__
, то можно увидеть, что у нас есть объект, который ссылается на файлы app.js
и randomFact.js
. Именно здесь хранятся наши данные о покрытии.
На самом деле, если в нашем приложении нажать на кнопку “Give me” и снова вызвать window.__coverage__
, то можно увидеть, что данные внутри него изменились. Не очень читабельно, но это так.
Теперь мы можем указать Cypress собирать эти данные и формировать отчет. Для этого необходимо установить плагин покрытия кода Cypress. Установка довольно стандартна, а страница readme должна быть достаточной для объяснения. Как уже упоминалось, этот плагин не инструментирует наш код, но, к счастью, мы уже сделали это в части 4.
Если теперь открыть Cypress и запустить простой тест с командой .visit()
внутри, то можно увидеть, что в нашем тесте присутствует несколько новых команд.
Но не только это. После выполнения теста в корне нашего проекта появилась новая папка coverage
. В ней находится HTML-отчет о покрытии кода. Просто открыв наше приложение, мы смогли достичь его 50% покрытия. Обратите внимание, что в отчете о покрытии также показаны оригинальные имена наших файлов, app.js
и randomFact.js
, что делает наш отчет очень удобным для навигации.
Внутри этого отчета мы можем просмотреть каждый из файлов и каждую из строк. Мы можем увидеть, действительно ли эти строки кода были вызваны нашим сквозным тестом. Поскольку наш тест только открыл приложение, нам нужно добавить еще и нажатие на кнопку, чтобы охватить весь путь. Добавив это, мы сделаем наше приложение на 100% покрытым, поскольку больше в нем ничего не происходит.
О чем нам говорит покрытие кода?
Теперь, когда все готово, давайте немного пофилософствуем. Должны ли мы стремиться к 100% покрытию? Звучит, конечно, красиво, но 100% покрытие кода еще не означает, что мы избавились от ошибок. Обратите внимание, что в этом тесте нет ни одного утверждения:
it('generates a random fact', () => { cy .visit('/') cy .get('button') .click() });
Но в нашем приложении может произойти множество событий, которые приведут к плохому пользовательскому опыту. Макет может быть нарушен, символы могут отображаться неправильно, API может не работать и так далее. И все же мы достигли 100%-ного покрытия в нашем тесте.
Мы “гуляем” по нашему приложению, но фактически не делаем утверждений о значениях, которые оно нам возвращает. Важно иметь это в виду.
Покрытие кода помогает ориентироваться в проекте и выявлять места, не охваченные тестами. Это отличный инструмент, который не требует много времени на настройку и приносит огромную пользу.
Пингбэк: Большой учебник по Cypress