Сквозное тестирование снимков

  1. Тестирование снимков
  2. Сквозное тестирование снимков
  3. Снимки элементов
  4. Последние мысли
Вернуться к блогу Cypress

Тестирование моментальных снимков захватило мир модульного тестирования JavaScript штурмом. Он удаляет большую часть ручного кодирования и большую часть шаблона, оставляя тестового бегуна для сохранения или сравнения полученных значений. В этой записи блога показано, как Cypress Test Runner может использовать ту же мощность в ваших комплексных тестах.

Экспериментальная особенность

Этот документ показывает очень раннее подтверждение концепции. API и функции, показанные здесь, могут измениться. Используйте на свой риск.

Я начну с обзора тестирования снимков, так как он используется в модульном тестировании. Затем я покажу, как мы можем использовать тот же подход в сквозных тестах для создания снимков объектов данных. Наконец, мы рассмотрим тестирование моментальных снимков элементов DOM.

Тестирование снимков

Во-первых, краткое введение в тестирование снимков. Представьте себе юнит-тест. Вы, наверное, думали о чем-то вроде этого:

const add = (a, b) => a + b it («добавляет числа», () => {ожидайте (добавьте (2, 3)). toBe (5)})

Тест вычисляет значение, используя вызов add (2, 3), и сравнивает его с жестко закодированным значением 5. Как мы получили это ожидаемое значение 5? Ну, я (разработчик) взял листок бумаги и карандаш и добавил 2 + 3. Легко.

Представьте себе реальный юнит-тест. Будет ли вычисление ожидаемого значения столь же простым, как сложение двух небольших чисел? Нет. Более сложный алгоритм будет генерировать большой объект в результате, а не одно число. Например, если мы проводим модульное тестирование ответа API, мы, вероятно, получим что-то вроде этого:

const api = требуется ('./api') api. лучший продавец ( ) . затем (console .log)

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

it ('возвращает товар верхнего продавца', () => {api. get (). then (item => console. log (JSON. stringify (item)))})

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

it ('возвращает товар самого лучшего продавца', () => {const Ожидается = {id: '1234-5678 -...', имя: 'Nerf Gun Zombie Blaster Dominator', displayName: '...'} return api . get (). then (item => Ожидается (элемент). toDeepEqual (ожидается))})

Печать, копирование и вставка значений в тест быстро становятся утомительными, и тесты увеличиваются в размере. К счастью, тестирование снимков (см. Ссылки 1 , 2 , 3 для большего количества примеров) стала главной вещью, благодаря тестеру Jest, который включает его по умолчанию. Тот же тест выше становится:

it ('возвращает товар верхнего продавца', () => api. get (). then (item => Ожидается (элемент). toMatchSnapshot ()))

Первый раз Шутка (или же Ava , или же Мокко с плагином snap-shot-it ) тестовый исполнитель выполняет этот тест, он сохраняет полученное значение в простой файл снимка JavaScript. В этом случае это будет выглядеть примерно так:

exports [`возвращает топ-продавца item 1`] =` {id: '1234-5678 -...', name: 'Nerf Gun Zombie Blaster Dominator', displayName: '...' // и остальные свойства } `;

Вы, разработчик, можете просмотреть этот файл, чтобы убедиться в правильности сохраненного значения. Затем вы должны зафиксировать файл снимка вместе с файлами спецификации. В следующий раз, когда тестовый исполнитель выполнит тест, он загрузит значение из снимка и сравнит его с полученным значением. Если они равны (используется глубокое равенство), утверждение проходит. Если они разные, утверждение выдает ошибку. Jest может разумно показать разницу, благодаря выбору плагина diff, подходящего для определенного типа данных (например, объект JSON против текста HTML).

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

Сквозное тестирование снимков

В моем предыдущий пост в блоге Я показал примеры тестов пользовательского интерфейса, тестов хранилища данных и даже тестов API на сервере. Взятые вместе, эти тесты дают мне огромную уверенность в том, что это приложение TodoMVC надежно и правильно. Но тесты в репо имеют большой недостаток - это полное сравнение сгенерированных значений с подробными объектами. Тест ниже является типичным подробным примером. Он добавляет несколько элементов todo, отправляя действия в хранилище данных, а затем утверждает, что центральное хранилище данных имеет ожидаемый объект состояния.

это ('может управляться диспетчерскими действиями', () => {store. dispatch ('setNewTodo', 'new todo') store. dispatch ('addTodo') store. dispatch ('clearNewTodo') getTodoItems (). should ('have.length', 1). first (). содержит ('новое задание') getStore (). must ('deep.equal', {loading: false, todos: [{title: 'новое задание) ', завершено: false, id:' 1 '}], newTodo:' '})})

В спецификационных файлах есть много таких мест, что заметно по утверждению must ('deep.equal'). Это было бы прекрасной возможностью заменить закодированные (вручную!) Значения на автоматическое сохранение и сравнение снимков. Но эти сквозные тесты выполняются в реальном браузере (Electron или любом другом, основанном на Chrome), и просто сохранить и загрузить файл снимка синхронно невозможно. Можем ли мы что-то сделать с этим?

Да мы можем!

Фактически, мы можем добавить тестирование моментальных снимков в наш комплексный тестовый запуск без изменения самого кода ядра Cypress - все это возможно с помощью стороннего кода. Мы опубликовали дополнение к снимку как @ Кипарис / снимок модуль npm. Просто сделайте эти три шага, чтобы начать:

  1. Установить как зависимость от разработчика

    нпм я -D @ кипарис / снимок
  2. Загрузите и зарегистрируйте дополнение

    требуют ('@ Cypress / снимок'). регистр ( )
  3. Нет шага 3!

Теперь у нас есть новая команда, которая может работать с любым объектом: объект, строка, массив или элемент DOM. Простейшим примером может быть наш «дополнительный» модульный тест.

const add = (a, b) => a + b it ('добавляет числа', () => {cy. wrap (add (2, 3)). snapshot ()})

После запуска теста сохраненный файл снимка выглядит следующим образом:

module .exports = {"добавляет числа": {"1": 5}}

Каждый снимок сохраняется под полным именем теста (включая имена родительских наборов описания). Плюс есть индекс на случай, если в одном тесте есть несколько снимков. Вы можете перезаписать имя более запоминающейся строкой:

const add = (a, b) => a + b it ('добавляет числа', () => {cy. wrap (add (2, 3)). snapshot () cy. wrap (add (1, 10) ). snapshot () cy. wrap (add (- 6, - 3)). snapshot ({имя: 'негативы'})})

Приведенный выше тест создает следующий файл снимка:

module .exports = {"добавляет числа": {"1": 5, "2": 11, "негативы": - 9}}

Cypress Test Runner не очень интересен, когда снимки соответствуют сохраненным значениям . Но если мы изменим некоторые числа в нашем примере, чтобы сделать тест неудачным, тогда это станет интересным. Давайте изменим тест:

cy. завернуть (добавить (1, 10)). снимок () cy. завернуть (добавить (1, 100)). снимок ()

Вычисленное значение будет 101, а не сохраненное значение, 11.

Область Command Log слева показывает разницу. Когда я нажимаю на команду «SNAPSHOT», она выводит ожидаемые и текущие значения в DevTools. Это позволяет мне быстро определить, что пошло не так; или, по крайней мере, он дает мне столько информации, сколько знает сам бегун.

Под капотом модуль @ cypress / snapshot загружает весь набор снимков до того, как начнутся все тесты, и во время выполнения он работает с хранилищем объектов, таким образом избегая проблем чтения и записи файлов. Я взял эту идею от карма-снимок плагин и реализовал его с помощью cy.readFile () а также cy.writeFile () команды.

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

const getStore = () => cy. окно ( ) . его ('app. $ store') it ('имеет свойства loading, newTodo и todos', () => {getStore (). its ('state'). should ('have.keys', ['loading', 'newTodo', 'todos']) getStore (). its ('state'). then (Object .keys). snapshot ()})

Файл снимка для этого теста:

module .exports = {"UI to Vuex store": {"имеет свойства загрузки, newTodo и todos": {"1": ["loading", "todos", "newTodo"]}}}

Чем крупнее объект - тем лучше! Наш ранее многословный тест стал чрезвычайно маленьким. Теперь у него есть два отдельных раздела: действия, утверждения и ничего больше.

это ('может управляться диспетчерскими действиями', () => {store. dispatch ('setNewTodo', 'new todo') store. dispatch ('addTodo') store. dispatch ('clearNewTodo') getTodoItems (). should ('have.length', 1). first (). содержит ('новая задача') getStore (). snapshot ()})

Важно: не забудьте проверить снимки из Cypress Test Runner или в сохраненном файле snapshots.js, чтобы убедиться, что они правильные - они становятся частью теста!

Обратите внимание, что в Cypress Test Runner, когда вы нажимаете на команду «SNAPSHOT», тестовый фрейм показывает временный снимок DOM в этот момент!

Снимки элементов

Важно: прежде чем мы продолжим, позвольте мне уточнить некоторую номенклатуру Cypress, потому что это может привести к путанице.

предосторожность

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

  • Снимок DOM - временная копия DOM тестового приложения, сохраненная после каждого шага теста при работе в режиме графического интерфейса . Когда вы наводите курсор мыши на шаг теста, в области теста отображается соответствующий снимок DOM, показывающий, как выглядела страница до и после этого шага теста .
  • снимок - любой объект, сохраненный на диске, когда мы выполняем команду cy.wrap (...). snapshot ().
  • элемент снимка - сериализованный элемент DOM, сохраненный на диске при выполнении команды cy.get ('<selector>'). snapshot (). Элементом может быть <div>, <main class = "todo"> или даже весь <body>.

Итак, в этом разделе рассказывается о том, как мы можем расширить идею тестовых снимков для захвата элементов DOM и почему это так полезно.

Давайте пройдемся по сложному тестовому сценарию. Мы добавим пару пунктов в список, затем отметим два из них как завершенные. Сколько тестов и утверждений нам нужно написать? Что ж, благодаря извлеченным функциям утилит манипулировать страницей легко.

it («отмечает завершенные элементы», () => {enterTodo («первый элемент») enterTodo («второй элемент») enterTodo («элемент 3») enterTodo («элемент 4») переключает («элемент 3») 'пункт 4')})

Но делать утверждения могут стать утомительными. Мы можем написать так много индивидуальных утверждений: проверить количество элементов, проверить статус проверки каждого элемента и т. Д.

it ('отмечает завершенные элементы', () => {getTodoItems (). should ('have.length', 4) getCompleted (). should ('have.length', 2) getTodo ('first item'). find ('[type = "checkbox"]'). should ('not.be.checked') getTodo ('second item'). find ('[type = "checkbox"]'). should ('not.be. check ') getTodo (' item 3 '). find (' [type = "checkbox"] '). should (' be.checked ') getTodo (' item 4 '). find (' [type = "checkbox"] '). should (' be.checked ')})

Это быстро становится утомительным. Но, как и раньше, мы можем заменить множество отдельных утверждений, захватив значение, описывающее всю страницу, ее заголовок или элементы списка. Мы сравним весь элемент с помощью cy.get ('ul.todo-list'). Snapshot (). По умолчанию HTML-код элемента DOM будет сохранен в виде снимка (с удаленной временной информацией, такой как атрибут React «id»). В качестве альтернативы вы можете захотеть сериализовать структуру DOM как объект JSON. Посмотрите на этот прекрасный тест:

it («отмечает завершенные элементы», () => {enterTodo («первый элемент») enterTodo («второй элемент») enterTodo («элемент 3») enterTodo («элемент 4») переключает («элемент 3») 'item 4') getCompleted (). should ('have.length', 2) cy. get ('ul.todo-list'). snapshot ({имя: 'список задач с 2 завершенными элементами'})})

Важно: мы не знаем, когда приложение отображает список , но хотим сделать его снимок после того, как флажки закончили рендеринг. Таким образом, мы помещаем утверждение Cypress getCompleted (). Should ('have.length', 2), которое повторяется исполнителем теста до тех пор, пока два элемента не проверят наличие полей ввода. Затем мы берем снимок элемента. Если мы нажмем на шаг «SNAPSHOT», мы увидим захваченный HTML.

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

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

Журнал команд показывает (используя алгоритм, похожий на git-diff из модуля под названием несоответствие ) что строка <li class = "todo complete"> пропала, и вместо этого есть строка <li class = "todo">. Если щелкнуть команду «SNAPSHOT» в журнале команд, все ожидаемые и полученные значения выводятся на консоль DevTools.

Последние мысли

Вы можете найти исходный код этого сообщения в блоге в Cypress-пример-рецепт репозиторий , В некоторых случаях файлы спецификаций становились меньше (store-spec.js), но в других случаях тестирование моментальных снимков позволило мне писать гораздо более сложные сценарии тестирования, сохраняя при этом код теста коротким и легким для чтения.

На данный момент мы выпустили @ Кипарис / снимок как сторонний модуль. Но у нас большие планы по внедрению тестирования моментальных снимков в ядро. Мы широко использовали моментальные снимки модульных тестов в Mocha в самом репозитории Cypress Test Runner, и считаем, что опыт разработчика должен быть существенно улучшен, предоставив обзор снимков и сравнение с хорошим графическим пользовательским интерфейсом! Оставайтесь с нами и следите за GitHub № 772 ,

Поделиться этим блогом
5. Как мы получили это ожидаемое значение 5?
Будет ли вычисление ожидаемого значения столь же простым, как сложение двух небольших чисел?
Можем ли мы что-то сделать с этим?
Сколько тестов и утверждений нам нужно написать?