Как провести модульное тестирование вашего первого компонента Vue.js

kak provesti modulnoe testirovanie vashego pervogo komponenta vuejs

автор Сара Даян

в Создайте свой первый компонент Vue.js мы создали компонент рейтинга звезд. Мы рассмотрели многие фундаментальные концепции, которые помогут вам создавать более сложные компоненты Vue.js.

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

Зачем модульное тестирование компонента?

Модульные тесты – это a решающее значение часть непрерывной интеграции. Они делают ваш код гораздо более надежным, сосредотачиваясь на небольших изолированных объектах и ​​обеспечивая их поведение, как ожидалось. Вы можете уверенно повторять свой проект, не опасаясь сломать вещи.

Модульные тесты не ограничиваются сценариями. Все, что мы можем протестовать по отдельности, можно проверить на единицу, если вы уважаете несколько хороших практик. Эти практики включают единую ответственность, предсказуемость и слабую связь.

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

Прежде чем начать

Кое-что изменилось после начального учебника. Выпущено Vue CLI 3. Vue Test Utils – официальная библиотека утилит модульного тестирования Vue.js – перешла к бета-версии. В первом учебнике мы использовали webpack-simple, шаблон прототипирования, не включающий функции тестирования. По этим причинам проще стереть список и перенести проект из учебника в более позднюю инсталляцию Vue.js.

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

Примечание: убедитесь, что вы установили Node.js, прежде чем идти дальше:

cd path/to/my/project npm install

Затем запустите проект:

npm run serve

Vue Test Utils и Jest

В этом учебнике мы будем использовать Vue Test Utils, официальный инструментарий тестирования Vue.js, а также Jest, программу для тестирования JavaScript, поддерживаемую Facebook.

Vue Test Utils позволяет монтировать компоненты Vue изолированно и имитировать взаимодействие пользователя. Он имеет все необходимые утилиты для тестирования однофайловых компонентов, включая использующие Vue Router или Vuex.

Jest – это полнофункциональный тестовый бегун, почти не требующий настройки. Он также дает встроенную библиотеку подтверждений.

Vue CLI 3 (который я использовал для создания шаблона) позволяет выбрать любимый тестовый бегун и настроить его для вас. Если вы хотите использовать другой тестовый бегун (например Mocha), установите Vue CLI 3 и создайте собственный стартовый проект. Тогда вы можете перенести исходные файлы из моего шаблона прямо в нем.

Что мы должны проверить?

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

Это также официальная рекомендация по руководству Vue Test Utils. Поэтому мы проверим только то, к чему можно получить доступ с внешней стороны компонента:

  • взаимодействия пользователей
  • смены реквизита

Мы не будем непосредственно тестировать вычисляемые свойства, методы или хуки. Они будут явно проверены путем тестирования общедоступного интерфейса.

Настройка файла спецификации

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

Спецификации – это файлы JavaScript. По условию, они имеют то же название, что и тестируемые компоненты, а также .spec суффикс.

Идите и создайте a test/unit/Rating.spec.js файл:

// Rating.spec.js
import { shallowMount } from '@vue/test-utils'import Rating from '@/components/Rating'
describe('Rating', () => {  // your tests go here})

Мы импортировали свое Rating компонент и shallowMount. Последняя является функцией Vue Test Utils, которая позволяет нам монтировать наш компонент, не монтируя его дочерних.

The describe Вызов функции оборачивает весь тест, который мы собираемся написать – он описывает наш тестовый набор. Он имеет свою область и сам может обертывать другие вложенные наборы.

Достаточно сказано, начнем писать тесты.

Определение сценариев тестирования

Когда мы смотрим на Rating внешне мы видим, что он выполняет следующее:

  • он отображает список звезд, равный значению maxStars поддержка, передаваемая пользователем
  • это добавляет active класс к каждой звезде, индекс которой ниже или равен stars поддержка, передаваемая пользователем
  • он переключает active класс на звездочке, когда пользователь нажимает ее и удаляет на следующих звездах
  • он переключает значки star и star-o когда пользователь нажимает звездочку
  • он отображает счетчик, если пользователь устанавливает параметр hasCounter поддержка к trueскрывает его, если они устанавливают его falseи отображает текст с указанием количества звездочек из максимального количества активных звездочек.

Обратите внимание, что мы смотрим только на то, что делает компонент извне. Нас не волнует, что щелчок звездочки выполняет функцию rate метод, или что внутрь stars изменения свойств данных. Мы могли бы их переименовать, но это не должно нарушить наши тесты.

Наш первый тест

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

Смонтированный компонент является объектом, поставляемым с несколькими полезными методами утилиты:

describe('Rating', () => {  const wrapper = shallowMount(Rating, {    propsData: {      maxStars: 6,      grade: 3    }  })  it('renders a list of stars with class `active` equal to prop.grade', () => {    // our assertion goes here  })})

Тогда мы можем написать наше первое утверждение:

it('renders a list of stars with class `active` equal to prop.grade', () => {  expect(wrapper.findAll('.active').length).toEqual(3)})

Давайте проанализируем, что здесь происходит. Во-первых, мы используем Jest’s expect функция, принимающая значение, которое мы хотим проверить как аргумент. В нашем случае мы называем findAll метод на нашем wrapper чтобы получить все элементы с помощью an active класс. Это возвращает a WrapperArrayкоторый является объектом, содержащим массив Wrappers.

А WrapperArray имеет два свойства: wrappers (содержится Wrappers) и length (количество Wrappers). Остальное – это то, что нам нужно, чтобы иметь ожидаемое количество звезд.

The expect функция возвращает объект, для которого мы можем вызвать методы для проверки переданного значения. Эти методы называются спички. Здесь мы используем toEqual matcher и передайте ему ожидаемое значение как в аргументах. Метод возвращает логическое значение, то есть то, что тест ожидает либо пройдет, либо провалится.

Подытоживая, здесь мы говорим, что ожидаем общее количество элементов с классом active мы находим в нашей обертке равным 3 (значение, которое мы назначили для grade опора).

В своем терминале запустите тест:

npm run test:unit

Вы должны увидеть, как это прошло?

Пора написать еще.

Имитация ввода пользователя

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

it('adds `active` class on an inactive star when the user clicks it', () => {  const fourthStar = wrapper.findAll('.star').at(3)  fourthStar.trigger('click')  expect(fourthStar.classes()).toContain('active')})

Здесь мы сначала получаем нашу четвертую звезду findAll и atвозвращающий a Wrapper от а WrapperArray по переданному индексу (нумерация с нуля). Затем мы моделируем click событие на нем – мы имитируем действие пользователя, щелкнувшего или коснувшегося четвертой звездочки.

Поскольку мы установили grade prop до 3, четвертая звездочка должна быть неактивной до того, как мы щелкнем, поэтому событие нажатия должно сделать ее активным. В нашем коде это представлено классом active которые мы добавляем к звездам, только когда они активированы. Мы тестируем это, позвонив по телефону classes метод на звездочке, возвращающий имена его классов в виде массива строк. Затем мы используем toContain ответчик, чтобы убедиться, что active класс здесь.

Настройка и демонтаж

Поскольку мы инициировали щелчок нашего компонента, мы изменили его состояние. Проблема в том, что мы используем тот же компонент для всех наших тестов. Что произойдет, если мы изменим порядок наших тестов и перенесем это на первое место? Тогда второй тест провалился.

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

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

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

let wrapper = null
beforeEach(() => {  wrapper = shallowMount(Rating, {    propsData: {      maxStars: 6,      grade: 3    }  })})
afterEach(() => {  wrapper.destroy()})
describe('Rating', () => {  // we remove the `const wrapper = …` expression  // …}

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

Специальные идентификаторы для тестов

Никогда не следует смешивать селекторы для стилизации и других целей, таких как тестовые крючки.

Что делать, если изменить название тега или класса?

Что делать, если у вас нет конкретного идентификатора элемента, который вы хотите проверить, например, в нашем случае, счетчика?

Вы не хотите загрязнять свой производственный код классами, которые там были бы бесполезными. Было бы гораздо лучше иметь специальные хуки для тестов, такие как специальный атрибут данных, но только во время тестов. Таким образом, в окончательном сборнике не останется беспорядок.

Один из способов справиться с этим – создать специальную директиву Vue.

Экземпляр Vue имеет a directive метод, принимающий два аргумента — a имяи an объект функций для каждого крючка жизненного цикла компонента при вводе в DOM. Вы также можете передать одну функцию, если вас не беспокоит определенный хук.

Давайте создадим новый каталог под названием directives в src/и добавьте a test.js файл. Мы экспортируем функцию, которую мы хотим передать в нашу директиву.

// test.js
export default (el, binding) => {  // do stuff}

Директивный хук может принимать несколько аргументов, и в нашем случае нам нужны только первые два: el и binding. The el аргумент ссылается на элемент, к которому привязана директива. The binding аргумент – это объект, содержащий данные, переданные в директиве. Таким образом, мы можем манипулировать элементом, как нам нравится.

export default (el, binding) => {  Object.keys(binding.value).forEach(value => {    el.setAttribute(`data-test-${value}`, binding.value[value])  })}

Мы передаем объект нашей директиве, чтобы мы могли генерировать атрибуты данных, начиная с data-test-. В функции обработчика мы берем каждое свойство bindingи мы устанавливаем атрибут данных на основе имени и значения для нашего элемента.

Теперь нам нужно зарегистрировать нашу директиву, чтобы мы могли ее использовать. Мы можем сделать это глобально, но в нашем случае мы собираемся зарегистрировать это только локально – прямо в нашем Rating.vue компонент.

<script>import Test from '@/directives/test.js'
export default {  // …  directives: { Test },  // …}</script>

Наша директива теперь доступна по ссылке v-test имя. Попытайтесь установить такую ​​директиву на счетчике:

<span v-test="{ id: 'counter' }" v-if="hasCounter">  {{ stars }} of {{ maxStars }}</span>

Теперь проверьте HTML в своем браузере с помощью инструментов разработчика. Ваш счетчик должен выглядеть так:

<span data-test-id="counter">2 of 5</span>

Прекрасно, это работает! Теперь нам это не нужно ни в режиме разработчика, ни при создании проекта. Единственной целью этого атрибута данных является возможность таргетинга элементов во время тестов, поэтому мы хотим настроить его только при их запуске. Для этого мы можем использовать NODE_ENV сменная среда, предоставляемая Webpack, пакетом модулей, обеспечивающим работу нашего проекта.

Когда мы проводим тесты, NODE_ENV установлено на 'test'. Поэтому мы можем использовать его, чтобы определить, когда устанавливать атрибуты теста или нет.

export default (el, binding) => {  if (process.env.NODE_ENV === 'test') {    Object.keys(binding.value).forEach(value => {      el.setAttribute(`data-test-${value}`, binding.value[value])    })  }}

Обновите приложение в браузере и проверьте счетчик снова: атрибут data исчез.

Теперь мы можем использовать v-test директивы для всех элементов, на которые мы должны ориентироваться. Давайте проведем наш предварительный тест:

it('adds `active` class on an inactive star when the user clicks it', () => {  const fourthStar = wrapper.findAll('[data-test-id="star"]').at(3)  fourthStar.trigger('click')  expect(fourthStar.classes()).toContain('active')})

Мы заменили .star селектор с [data-test-id="star"]что позволяет нам изменять классы для презентационных целей, не нарушая тестов. Мы получаем одно из преимуществ принципа единой ответственности и слабой связи — когда ваши абстракции имеют лишь одну причину для изменения, вы избежите всяческих неприятных побочных эффектов.

Должны ли мы использовать эти крючки для классов, которые мы тестируем?

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

expect(wrapper.findAll('.active').length).toEqual(3)

Должны ли мы использовать v-test на элементы с active класса и заменить селектор в утверждение? Прекрасный вопрос.

Модульные тесты – это тестирование одной вещи за один раз. Первый аргумент it функция – это строка, с помощью которой мы описываем то, что мы делаем с точки зрения потребителя.

Тест, завершающий наше утверждение, говорит renders a list of stars with class active equal to prop.grade. Именно этого ожидает потребитель. Когда они передают число в grade собственность, они ожидают получить an равное количество активных или избранных звезд. Однако, по логике нашего компонента, active класс – это именно то, что мы используем для определения этого признака. Мы назначаем его в зависимости от конкретного состояния, чтобы зрительно отличить активные звезды от других. Здесь наличие этого конкретного класса — именно то, что мы хотим проверить.

Итак, решая, следует ли использовать селектор, который у вас уже есть, или установить a v-test директивы, задайте себе вопрос: что я тестирую, и имеет ли смысл использования этого селектора с точки зрения бизнес-логики?

Чем он отличается от функциональных или сквозных тестов?

Сначала это может выглядеть удивительно для компонент модульного тестирования. Почему вы проводили модульное тестирование пользовательского интерфейса и взаимодействия с пользователем? Разве не для этого здесь многофункциональные тесты?

Существует фундаментальная, но тонкая разница между тестированием публичного API компонента – он же потребитель перспектива — и тестирование компонента по a пользователь перспектива. Сначала подчеркнем нечто важное: мы тестируем четко определенные функции JavaScript, а не части интерфейса.

Когда вы смотрите на однофайловый компонент, легко забыть, что компонент компилируется в JavaScript. Мы не тестируем основной механизм Vue, который с помощью этой функции вызывает ориентированные на пользовательский интерфейс побочные эффекты, такие как ввод HTML в DOM. Именно об этом уже заботятся собственные тесты Vue. В нашем случае наш компонент ничем не отличается от какой-либо другой функции: он принимает вход и возвращает выход. Именно эти причины и последствия мы проверяем и ничего больше.

Сбивает с толку то, что наши тесты выглядят несколько иначе, чем обычные модульные тесты. Обычно мы пишем такие вещи, как:

expect(add(3)(4)).toEqual(7)

Здесь нет дискуссии. Ввод и вывод данных – это все, что нас волнует. Что касается компонентов, мы ожидаем, что вещи будут отображаться визуально. Мы обходим виртуальную DOM и тестируем наличие узлов. Это также то, что вы делаете с функциональными или сквозными тестами с помощью таких инструментов как Selenium или Cypress.io. Итак, чем это отличается?

Не надо путать что мы делаем, чтобы получить данные, которые мы хотим проверить, и фактические цели теста. С помощью модульных тестов мы тестируем изолированное поведение. С помощью функциональных или сквозных тестов мы тестируем сценарии..

Единичный тест убеждает, что a единица программы ведет себя, как ожидалось. Это адресовано потребитель компонент — программист, использующий компонент в своем программном обеспечении. Функциональный тест гарантирует особенность или а рабочий процесс ведет себя, как ожидалось, от a пользователь перспектива – конечный пользователь, использующий полное программное обеспечение.

Идя дальше

Я не буду вдаваться в детали каждого теста, поскольку все они имеют схожую структуру. Вы можете найти полный файл спецификации на GitHub, и я настоятельно рекомендую вам сначала попытаться реализовать их самостоятельно. Тестирование программного обеспечения – это искусство настолько, насколько это наука, и требует вдвое больше практики, чем теории.

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

Первоначально опубликовано на frontstuff.io.

Добавить комментарий

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