Тестирование, инжиниринг и научный метод связаны между собой. «Инженерия — это предположение, что все пойдет не так, как надо, и извлечение из этого уроков (затем написание тестов), а также предсказание путей, по которым все может пойти не так, и защита от этого (и написание еще большего количества тестов)». — говорит Дэйв Фарли. Это мои золотые правила в тестировании:
- Это всегда стоимость против уверенности
- стоимость = создание + выполнение + сопровождение
- То, что вы можете избежать тестирования, важнее того, что вы тестируете.
В области front-end тестирования 2022 года у нас есть несколько уровней подхода к нашей стратегии тестирования. Покрытие модульных тестов дает нам высокую уверенность на уровне исходного кода, Jest & React Testing Library являются доминирующими в этом пространстве. UI-(компонентные)-интеграционные тесты с Cypress могут выходить в сеть с помощью intercept
api и покрывать взаимодействие компонентов; будь то переходы состояний, сценарии использования функций или рабочие процессы приложения. Идея заключается в том, чтобы изолировать приложение от back-end. С помощью e2e-тестов мы можем позволить клиенту UI взаимодействовать с бэкендом и получить общую уверенность в работе всей системы. В Cypress 10 у нас появилась новая суперспособность под названием компонентное тестирование, когда мы можем подключить сам компонент к реальному DOM и взаимодействовать с ним. По нашему скромному мнению, это позволит перенести большинство тестов на более низкий уровень, чем e2e, без потери уверенности, и на более высокий уровень, чем Jest, без увеличения стоимости.
Учитывая, что мы растем на плечах гигантов со всеми этими инструментами, позволяющими создавать комплексные стратегии тестирования, какими метриками мы можем оценить нашу уверенность? Покрытие — это оценка тщательности или полноты тестирования по отношению к модели. Нашей моделью может быть покрытие исходного кода, покрытие функций, оценка мутаций, комбинаторное покрытие, покрытие нефункциональных требований — что угодно. Хотя покрытие исходного кода не является универсальной метрикой, мы не можем отрицать ее популярность и эффективность. Мы привыкли получать покрытие кода из модульных тестов, а что если бы мы могли также получать покрытие исходного кода из e2e-тестов Cypress, а также компонентных тестов Cypress? У нас уже давно есть комбинированные unit & e2e покрытия, и добавление к ним компонентного тестирования Cypress является новинкой в Cypress 10. Представьте себе, что вы можете добавить любой вид тестирования по вашему выбору для новых функций и сохранить покрытие кода выше 95% без особых усилий. Нужно ли нам будет отслеживать каждое требование до каждого теста? Сколько бы нам пришлось беспокоиться об изменениях, которые мы вносим, пока все тесты проходят и покрытие не регрессирует? Давайте рассмотрим приложение React среднего размера и покажем, как этого добиться. Как всегда, блог без кода не имеет смысла, поэтому код для этого блога можно найти в этом репозитории, а PR покрытия кода тестов компонента можно найти здесь.
- Установка Cypress Component & E2e coverage
- Добавьте пакеты
- Инструментируйте приложение для E2e
- Настройте nyc для локальной оценки покрытия
- Настройте cypress.config.js для покрытия кода, инструментируйте приложение для тестирования компонентов
- Настройте оба файла cypress/support/e2e.js и cypress/support/component.js.
- Протестируйте настройку
- Добавьте скрипты для удобства работы с покрытием в package.json
- Объединение модульных, e2e & покрытие компонентных тестов (выполнение на локальной машине)
- Императивное прохождение локальных скриптов
- Комбинированное покрытие в CI
- DYI
- Сервис CodeCov
- Заключение
Установка Cypress Component & E2e coverage
Добавьте пакеты
Предположим, что у нас есть приложение React (созданное с помощью CRA), в котором уже есть Jest & Cypress, нам нужно несколько пакетов и их одноранговые зависимости для Cypress e2e и покрытия кода тестирования компонентов:
yarn add -D @bahmutov/cypress-code-coverage istanbul-lib-coverage @cypress/instrument-cra nyc babel-loader @babel/preset-env @babel/preset-react
Инструментируйте приложение для E2e
Измените package.json
/scripts
/start
так, чтобы наше приложение CRA инструментировало код без извлечения react-scripts.
"start": "react-scripts -r @cypress/instrument-cra start"
Настройте nyc
для локальной оценки покрытия
Добавьте файл .nycrc
для конфигурации. Мы задаем каталог отчета о покрытии как coverage-cy
, чтобы изолировать его от Jest. Свойство all
использует даже те файлы, которых не касаются тесты. excludeAfterRemap
имеет значение true, согласно документации пакета покрытия кода Cypress, чтобы не пропускать исключенные файлы. Вот краткая ссылка на документацию nyc.
"all": true,
"excludeAfterRemap": true,
"report-dir": "coverage-cy",
"reporter": ["text", "json", "html"],
"extension": [".js"],
"include": "src/**/*.js",
"exclude": [
"any files you want excluded"
]
Настройте cypress.config.js
для покрытия кода, инструментируйте приложение для тестирования компонентов
Ключевым инструментом здесь является статья Глеба Бахмутова в блоге Component Code Coverage in Cypress v10. Он подробно описал, как добиться покрытия кода компонентного тестирования в Cypress 10. Он также написал улучшенную версию плагина code-coverage с дополнительными исправлениями. Обратите внимание, что нижеприведенная информация может быть изменена, если команда Cypress включит покрытие кода компонентных тестов в новых версиях Cypress.
const { defineConfig } = require("cypress");
const codeCoverageTask = require("@bahmutov/cypress-code-coverage/plugin");
module.exports = defineConfig({
projectId: "your cypress dashboard project id",
e2e: {
setupNodeEvents(on, config) {
// note: in the linked repo, the plugins/index.js was large
// it did not get migrated, but instead gets imported here
// the below is how we would do it from scratch
return Object.assign({}, config, codeCoverageTask(on, config));
},
baseUrl: "http://localhost:3000",
specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}",
},
component: {
devServer: {
framework: "create-react-app",
bundler: "webpack",
// here are the additional settings from Gleb's instructions
webpackConfig: {
mode: "development",
devtool: false,
module: {
rules: [
// application and Cypress files are bundled like React components
// and instrumented using the babel-plugin-istanbul
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: [
"istanbul",
[
"@babel/plugin-transform-modules-commonjs",
{ loose: true },
],
],
},
},
},
],
},
},
},
setupNodeEvents(on, config) {
return Object.assign({}, config, codeCoverageTask(on, config));
},
specPattern: "src/**/**/*.cy.{js,ts,jsx,tsx}",
},
});
Вышеописанное также помогает решить открытую проблему с заглушками импортированных модулей в компонентном тест-раннере Cypress.
@babel/plugin-transform-modules-commonjs
позволяет нам настроить конфигурацию webpack так, чтобы сделать все импортируемые модули доступными из любого файла, включая specs. Вот как мы применили его для имитации флагов функций LaunchDarkly в тестах компонентов Cypress.
Настройте оба файла cypress/support/e2e.js
и cypress/support/component.js
.
import "@bahmutov/cypress-code-coverage/support";
Протестируйте настройку
На данном этапе, когда мы выполняем e2e или компонентные тесты, мы должны видеть блок after в тест-раннере, и мы должны видеть, как заполняется папка coverage-cy
.
Не забудьте добавить в gitignore файлы, связанные с покрытием. Вы можете повторить .gitignore примера репозитория.
Добавьте скрипты для удобства работы с покрытием в package.json
Основными из них являются cov:reset
для очистки всех файлов покрытия и cov:combined
для локальной генерации комбинированного отчета после выполнения юнит-тестов, тестов компонентов Cypress и Cypress e2e. Остальные скрипты компонуются друг с другом и также могут использоваться в CI. Мы подробно расскажем о них в следующем разделе
"cov:combined": "yarn copy:reports && yarn combine:reports && yarn finalize:combined-report",
"copy:reports": "(mkdir reports || true) && cp coverage-cy/coverage-final.json reports/from-cypress.json && cp coverage/coverage-final.json reports/from-jest.json",
"combine:reports": "(mkdir .nyc_output || true) && yarn nyc merge reports && mv coverage.json .nyc_output/out.json",
"finalize:combined-report": "yarn nyc report --reporter html --reporter text --reporter json-summary --report-dir combined-coverage",
"cov:reset": "rm -rf .nyc_output && rm -rf reports && rm -rf coverage && rm -rf coverage-cy && rm -rf combined-coverage",
Объединение модульных, e2e & покрытие компонентных тестов (выполнение на локальной машине)
Мы выполняем набор юнит-тестов с помощью yarn test
, используя Jest, и получаем отчет в папке coverage
. Аналогично, мы выполняем e2e тесты или компонентные тесты с помощью yarn cy:run-e2e
& cy:run-ct
и получаем отчет в папке coverage-cy
. Мы можем выполнять компонентные & e2e тесты в любом порядке, покрытие объединяется в папке coverage-cy
. Это происходит потому, что nyc
не может определить, какие тесты сгенерировали файлы покрытия, и добавляет их, если есть дополнительное покрытие. Это похоже на то, как если запустить один юнит-тест за другим, не получив дополнительного покрытия, а затем запустить несколько разных юнит-тестов и получить большее покрытие. Теоретически это означает, что Jest и Cypress могут совместно использовать папку coverage
, но нам нравится более упорядоченный порядок.
Давайте запустим полный локальный рабочий процесс для проверки всех наших чисел покрытия.
Сбросим покрытие с помощью yarn cov:reset
. Это удалит все соответствующие папки.
Запустите тесты компонентов с помощью yarn cy:run-ct
. Папка coverage-cy
будет заполнена. При создании приложения мы использовали TDD с компонентными тестами Cypress, и покрытие выше 88% показывает, насколько это мощный инструмент.
Если мы выполним e2e тесты поверх CT, или наоборот, покрытие будет комбинированным. Мы можем сделать это позже, а пока давайте сделаем резервную копию покрытия компонентов и сбросим покрытие снова с помощью yarn cov:reset
, чтобы увидеть, насколько большое покрытие исходного кода обеспечивают тесты e2e. Запустим yarn cy:run-e2e
, пока не существует папки coverage-cy
, и получим довольно хорошее число — более 78%. Мы использовали e2e тесты в этом приложении только тогда, когда компонентного тестирования было недостаточно, или те вещи, в которых мы хотели быть уверены, не могли быть покрыты на низком уровне. Соотношение компонентных и e2e тестов составляет 80 : 20, и мы считаем, что в будущем это соотношение может стать реальным для производственных приложений. Для мизерного количества тестов и объема кода, написанного для e2e-тестов, 78% покрытие исходного кода является довольно высоким. Это означает, что они могут быть хорошим выбором при восполнении недостающего покрытия исходного кода.
В этом репозитории есть тесты для Applitools, Percy и LaunchDarkly. Вы можете попросить у меня файл
.env
или просто отключить эти e2e тесты. Покрытие e2e будет немного ниже.
Наполнив папку coverage-cy
тестами e2e, теперь мы можем повторно выполнить тесты компонентов, чтобы увидеть комбинированное покрытие между E2e и CT. Выполните yarn cy:run-ct
и посмотрите coverage-cy/lcov/index.html
. 94,5% комбинированного покрытия e2e и ct — это достойно уважения. Есть некоторое избыточное покрытие исходного кода между тестами, но мы знаем, что мы написали e2e тесты, чтобы получить уверенность в функциях, которые мы не могли эффективно проверить компонентными тестами. Мы не пытались накрутить цифры покрытия, мы сделали то, что должны были, и получили покрытие кода в качестве побочного преимущества.
Наконец, мы можем запустить модульные тесты Jest с помощью yarn test
. Это покрывает простую функцию суммы, повторяя уже существующий набор модульных тестов в производственном приложении. Папка coverage
генерируется с html-отчетом по адресу coverage/lcov/index.html
.
Теперь нам нужно извлечь определенные файлы из папок coverage
& coverage-cy
и объединить их в один отчет. Все, что нам нужно, это запустить yarn cov:combined
. Будет создана папка combined-coverage
. Мы можем убедиться, что файлы хорошо объединились, по общему количеству утверждений/строк и функций.
Императивное прохождение локальных скриптов
- Нам нужны файлы
coverage-final.json
из соответствующих папок покрытия в новой папкеreports
. Создайте временную папкуreports
. Мы используем|| true
, чтобы не возникало ошибок при повторных выполнениях скрипта:(mkdir reports || true)
. Сохраните два файлаcoverage-final.json
из двух папокcoverage
&coverage-cy
. Переименуйте их так, чтобы они не перезаписывали друг друга.cp coverage-cy/coverage-final.json reports/from-cypress.json && cp coverage/coverage-final.json reports/from-jest.json
.
"copy:reports": "(mkdir reports || true) && cp coverage-cy/coverage-final.json reports/from-cypress.json && cp coverage/coverage-final.json reports/from-jest.json",
- Объедините отчеты с помощью
nyc
. В Nyc есть утилита для указания местоположения папки для объединяемых отчетов. Наши файлы покрытия находятся в папкеreports
. После объединения, по умолчанию, nyc генерирует файл с именемcoverage.json
в корне проекта. Мы переименуем его и перезапишем в папку.nyc/
. Обратите внимание, что папка.nyc
заполняется файломout.json
по мере выполнения тестов Cypress e2e или CT, поскольку покрытие кода Cypress использует его под капотом. Мы можем без проблем перезаписать его комбинированными данными покрытия.
"combine:reports": "(mkdir .nyc_output || true) && yarn nyc merge reports && mv coverage.json .nyc_output/out.json",
- Завершите отчет. В
nyc
есть команда для создания отчета с помощьюyarn nyc report
. Для этого используется файл.nyc_output/out.json
. (Не путайте его с нашей временной папкойreports
, которую мы использовали для объединения отчетов). Мы можем указать несколько типов отчетов, а также выходной каталог. Мы сохраним окончательный отчет в папке под названиемcombined-coverage
.
"finalize:combined-report": "yarn nyc report --reporter html --reporter text --reporter json-summary --report-dir combined-coverage",
Сценарий yarn cov:combined
, который мы использовали, представляет собой просто эти три подскрипта, соединенные вместе. Убедитесь, что сначала запустили несколько тестов, прежде чем пытаться объединить их покрытие, иначе скрипт будет жаловаться, что не может найти файлы, которые он пытается переместить или скопировать.
Комбинированное покрытие в CI
DYI
В предыдущей записи блога Combined Unit & E2E Code Coverage: case study на реальной системе с использованием Angular, Jest, Cypress & GitLab / CircleCi эта тема была подробно рассмотрена, показано, как объединить покрытие самостоятельно. Это большая работа, и результаты зависят от успеха инструментов с открытым исходным кодом в нашем проекте. Мы пока не смогли заставить это работать в контексте React и начали обсуждение на форумах Cypress, потому что nyc merge reports выдает пустой файл coverage.json
при работе в CI по сравнению с локальной машиной. Существует полное описание и даже видео с описанием обязательных шагов, которые находятся в yml-файле и выполняются с каждым PR. Если появится новая информация, репо и запись в блоге будут обновлены.
Сервис CodeCov
По нашему искреннему мнению, это лучший вариант. Да, он платный, но окупается установкой, настройкой, обслуживанием, аналитикой и множеством функций. Давайте вместе разберемся с настройкой.
Заходим на Github по адресу https://app.codecov.io/login/gh, и видим репозитории, к которым мы даем доступ CodeCov.
Нажмите на setup repo, и если мы используем Github Actions, то здесь нам понадобится только токен из Шага 2.
Вставьте его в разделе Github Settings > Secrets в переменную CODECOV_TOKEN
.
Далее мы добавим удобное действие Github в конец наших заданий unit, e2e и CT. Каталог coverage-cy
для E2e и CT, coverage
для Jest. Свойство flags
служит для различения 3 видов покрытия в PR-сообщении. Токен используется для связи между репо и Codecov, в случае, если мы используем приватное репо. Мы показываем соответствующий раздел одного из заданий, а полный текст yml вы можете посмотреть здесь.
cypress-e2e-test:
steps:
#...
- name: Cypress e2e tests 🧪
#...
- name: ✅ Upload e2e coverage to Codecov
uses: codecov/codecov-action@v3
with:
directory: coverage-cy/
flags: cypress-e2e-coverage
token: ${{ secrets.CODECOV_TOKEN }}
Почти готово. Нам нужен файл codecov.yml
в корне репо. Мы применяем один из рецептов в документации CodeCov.
coverage:
status:
project:
default:
# auto compares coverage to main branch
target: auto
# this allows a 2% drop from the previous base commit coverage
threshold: 2%
ignore:
# you can copy the files & folders from nyc to here
# the format is the same
# makes it so that unit, cy ct and cy e2e reports finish running before the report is shown
codecov:
notify:
after_n_builds: 3
Наконец, давайте сделаем значок readme. В разделе Настройки > Значки & Графики скопируйте разметку в начало файла readme.
Позже мы получим бейдж, показывающий покрытие кода репозитория на основной ветке.
После публикации PR мы можем просмотреть аналитику в веб-приложении CodeCov. Подобные данные не невозможны при локальном тестировании, но очень неудобны, поскольку нам пришлось бы выполнять все наборы юнитов, компонентов и e2e по одному, а затем объединять покрытие. Затем нам пришлось бы копаться в html-отчетах, чтобы найти источник недостаточного покрытия. С помощью солнечной диаграммы Codecov отсутствие покрытия в BookableEdit.js
(красная нижняя часть) легко обнаружить. Давайте посмотрим.
Похоже, что если мы впишем bookable в уже существующую группу, мы можем столкнуться с этим кодом. Это будет трудно сделать с помощью модульного или компонентного теста, но мы можем повторить существующий e2e-тест и не задавать случайное имя группы, а использовать существующую группу. Размышляя о том, как лучше решить проблему отсутствия покрытия для этих частных функций, мы можем заметить, что граница между слоями пирамиды тестов становится неактуальной; нам нужно покрытие исходного кода, и мы добавляем тот тип теста, который будет наиболее простым и удобным. Тип теста или слой пирамиды больше не имеют значения. Тест — это тест, код — это код, и решение принимается только по принципу «затраты против уверенности».
После нескольких PR мы можем наблюдать увеличение охвата по времени и более зеленый график солнечных лучей. Нам нравится такой вид анализа, дающий нам представление о высоком и низком уровне одновременно.
В PR мы видим лаконичное сообщение с тремя видами покрытия, комбинированным покрытием и разницей в покрытии по сравнению с основным. Ожидайте небольшое отклонение от локального покрытия nyc
на основе игнорируемых файлов. Имейте в виду, что разница обновляется в реальном времени по мере выполнения различных видов тестовых покрытий, а затем объединяется; дождитесь завершения всех тестов, прежде чем отчет Codecov будет в окончательном состоянии. Чтобы отчет обновлялся только после завершения всех различных тестов, можно использовать функцию уведомления после n сборок. Вы можете посмотреть на окончательную версию файла codecov.yml
здесь, с добавлением этого свойства.
Заключение
Стандартом де-факто в производственных приложениях в мире фронт-энда является 70% покрытие модульных тестов. Раньше это считалось оптимальным соотношением стоимости и уверенности. Компонентные тесты Cypress полностью меняют эту парадигму; они высоконадежны, недороги и с точки зрения разработчика могут немедленно заменить Storybook. Благодаря возможности работать с реальным DOM и взаимодействовать с ним через текучий API, как реальный пользователь, естественно желание проводить больше тестов, что в итоге приводит к увеличению покрытия кода.
Многие приложения используют Cypress для e2e-тестирования, но получение покрытия исходного кода с помощью e2e-тестов не совсем норма. В Cypress 10 появились компонентные тесты, которые могут напрямую заменить большую часть нашей работы с модульными тестами, которые должны были полагаться на виртуальный домен. Нам буквально приходилось проводить модульное тестирование в темноте, не видя того, что мы тестируем. Было бы жаль не измерить покрытие исходного кода, которое могут обеспечить компонентные тесты Cypress. Как только мы получим покрытие кода компонентных тестов, добавление покрытия кода e2e станет легкой добычей. Как только у нас есть два покрытия Cypress, объединение их с покрытием любых модульных тестов также не требует больших усилий.
С тройным комбинированным покрытием мы можем объединить покрытие нашего старого набора модульных тестов с нашим набором e2e-тестов Cypress и начать добавлять компонентные тесты Cypress для новых функций. Перенос старых модульных тестов на компонентные тесты Cypress не является обязательным требованием, поскольку то, что работало раньше, уже обеспечивает покрытие кода. Мы можем переносить модульные тесты на тесты компонентов Cypress по желанию, медленно или вообще не переносить. Комбинируя покрытие от всех видов тестирования, мы меньше беспокоимся о типе тестов или пирамиде; вместо этого мы можем взглянуть на ситуацию с высоты 10 тысяч футов и решить, какой вид затрат на тестирование имеет больше смысла для необходимой нам уверенности, и измерить результаты этого решения. Такой процесс принятия решений ставит любую команду и организацию в хорошее положение с точки зрения качества. Мы можем вносить изменения, продолжать увеличивать покрытие и пробовать новые подходы к нашему исходному коду без опасений. Сегодня в мире фронт-эндового тестирования 95% и выше покрытия исходного кода — это уже не роскошь, а достижение, не требующее усилий.