Покрытие кода в Cypress

Перевод статьи «Understanding code coverage in Cypress».

Покрытие кода – это одна из тех немногих вещей, которые не предоставляются в Cypress сразу из коробки. Чтобы настроить покрытие кода, необходимо обладать некоторыми знаниями в области веб-разработки.

Эта статься ориентирована на тех, кто только начинает знакомиться с тем, как создаются веб-приложения, поэтому если вам не нужна демонстрация того, что делают Browserify, Babel или Webpack, вы можете пролистать до части 4. Если вы ищете, как настроить покрытие кода с помощью Cypress, я рекомендую вам отличную документацию или вебинар команды Cypress на эту тему.

Содержание

  1. Из чего состоят веб-приложения?
  2. Упаковка JavaScript-файлов
  3. Преобразование JavaScript-файлов
  4. Что такое инструментирование?
  5. Настройка Cypress
  6. О чем говорит покрытие кода?
Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

Из чего состоят веб-приложения?

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

Это приложение содержит три файла, как и положено веб-приложениям: 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() внутри, то можно увидеть, что в нашем тесте присутствует несколько новых команд.

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

Но не только это. После выполнения теста в корне нашего проекта появилась новая папка coverage. В ней находится HTML-отчет о покрытии кода. Просто открыв наше приложение, мы смогли достичь его 50% покрытия. Обратите внимание, что в отчете о покрытии также показаны оригинальные имена наших файлов,  app.js и randomFact.js, что делает наш отчет очень удобным для навигации.

Отчет о покрытии.

Внутри этого отчета мы можем просмотреть каждый из файлов и каждую из строк. Мы можем увидеть, действительно ли эти строки кода были вызваны нашим сквозным тестом. Поскольку наш тест только открыл приложение, нам нужно добавить еще и нажатие на кнопку, чтобы охватить весь путь. Добавив это, мы сделаем наше приложение на 100% покрытым, поскольку больше в нем ничего не происходит.

О чем нам говорит покрытие кода?

Теперь, когда все готово, давайте немного пофилософствуем. Должны ли мы стремиться к 100% покрытию? Звучит, конечно, красиво, но 100% покрытие кода еще не означает, что мы избавились от ошибок. Обратите внимание, что в этом тесте нет ни одного утверждения:

it('generates a random fact', () => {

  cy
    .visit('/')

  cy
    .get('button')
    .click()

});

Но в нашем приложении может произойти множество событий, которые приведут к плохому пользовательскому опыту. Макет может быть нарушен, символы могут отображаться неправильно, API может не работать и так далее. И все же мы достигли 100%-ного покрытия в нашем тесте.

Мы “гуляем” по нашему приложению, но фактически не делаем утверждений о значениях, которые оно нам возвращает. Важно иметь это в виду.

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

1 комментарий к “Покрытие кода в Cypress”

  1. Пингбэк: Большой учебник по Cypress

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *