Skip to content

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

Зачем нужны тесты?

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

В этом руководстве мы рассмотрим основную терминологию и дадим рекомендации по выбору инструментов для вашего приложения на Vue 3.

Один раздел, посвящённый Vue, посвящён composable элементам. Подробнее об этом см. ниже в разделе тестирование composables.

Когда проводить тестирование

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

Типы тестирования

При разработке стратегии тестирования приложения Vue следует использовать следующие типы тестирования:

  • Модульные (unit): Проверяет, что входные данные данной функции, класса или composable дают ожидаемый результат или побочные эффекты.
  • Компонентные: Проверяет, что ваш компонент монтируется, отображается, с ним можно взаимодействовать и он ведет себя так, как ожидается. Эти тесты содержат больше кода, чем модульные тесты, более сложны и требуют больше времени для выполнения.
  • End-to-end: Проверяет функции, которые охватывают несколько страниц и выполняют реальные сетевые запросы, на примере вашего собранного приложения Vue. Такие тесты часто включают в себя работу с базой данных или другим бэкендом.

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

Обзор

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

Модульное тестирование

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

В целом модульные тесты позволяют выявить проблемы с бизнес-логикой и логической корректностью функции.

Возьмём, к примеру, функцию increment:

js
// helpers.js
export function increment (current, max = 10) {
  if (current < max) {
    return current + 1
  }
  return current
}

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

Если ни одно из этих утверждений не сработает, то ясно, что проблема кроется в функции increment.

js
// helpers.spec.js
import { increment } from './helpers'

describe('increment', () => {
  test('увеличивает число на 1', () => {
    expect(increment(0, 10)).toBe(1)
  })

  test('не увеличивает число выше максимального', () => {
    expect(increment(10, 10)).toBe(10)
  })

  test('имеет максимальное значение по умолчанию 10', () => {
    expect(increment(10)).toBe(10)
  })
})

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

Как правило, это обычные модули JavaScript / TypeScript, не связанные с Vue. В целом написание модульных тестов для бизнес-логики в приложениях на Vue не сильно отличается от приложений на других фреймворках.

Есть два случая, когда необходимо проводить модульное тестирование специфических для Vue функций:

  1. Composables
  2. Компоненты

Composables

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

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

Компонент может быть протестирован двумя способами:

  1. Белый ящик: Модульное тестирование

    Тесты, которые являются "Whitebox-тестами", знают о деталях реализации и зависимостях компонента. Они нацелены на изоляцию тестируемого компонента. Такие тесты обычно включают в себя имитацию некоторых, если не всех, дочерних компонентов, а также настройку состояния и зависимостей плагинов (например, Pinia).

  2. Чёрный ящик: Компонентное тестирование

    Тесты, которые являются "Blackbox-тестами", не знают деталей реализации компонента. Эти тесты имитируют как можно меньше, чтобы протестировать интеграцию вашего компонента и всей системы в целом. Обычно они отображают все дочерние компоненты и считаются скорее "интеграционными тестами". Смотрите рекомендации по тестированию компонентов ниже.

Рекомендации

  • Vitest

    Поскольку официальная настройка, создаваемая create-vue основана на Vite, мы рекомендуем использовать фреймворк для модульного тестирования, который может использовать ту же конфигурацию и конвейер преобразования непосредственно из Vite. Vitest - специально разработанный для этих целей фреймворк для модульного тестирования, созданный и поддерживаемый членами команды Vue/Vite. Он интегрируется с проектами на базе Vite с минимальными усилиями и обладает потрясающей скоростью.

Другие варианты

  • Jest — популярный фреймворк для модульного тестирования. Однако мы рекомендуем Jest только в том случае, если у вас есть существующий набор тестов Jest, который необходимо перенести в проект на основе Vite, поскольку Vitest предлагает более плавную интеграцию и лучшую производительность.

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

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

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

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

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

ДЕЛАЙТЕ

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

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

    В приведенном ниже примере мы демонстрируем компонент Stepper, который содержит элемент DOM с надписью "increment", и на который можно нажать. Мы передаем параметр max, который не позволяет увеличить значение Stepper более чем на 2, поэтому, если мы нажмем на кнопку 3 раза, в пользовательском интерфейсе все равно будет указано 2.

    Мы ничего не знаем о реализации Stepper, только то, что "входом" является параметр max, а "выходом" - состояние DOM в том виде, в котором его увидит пользователь.

Vue Test Utils
Cypress
Testing Library
js
const valueSelector = '[data-testid=stepper-value]'
const buttonSelector = '[data-testid=increment]'

const wrapper = mount(Stepper, {
  props: {
    max: 1
  }
})

expect(wrapper.find(valueSelector).text()).toContain('0')

await wrapper.find(buttonSelector).trigger('click')

expect(wrapper.find(valueSelector).text()).toContain('1')
  • НЕ ДЕЛАЙТЕ

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

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

    Не стоит полагаться исключительно на snapshot тесты. Сравнение HTML-строк не описывает корректность. Пишите тесты со смыслом.

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

Рекомендации

  • Vitest для компонентов или composables, которые отрисовываются в режиме headless (например, функция useFavicon во VueUse). Компоненты и DOM могут быть протестированы с помощью @vue/test-utils.

  • Компонентное тестирование с Cypress для компонентов, чье ожидаемое поведение зависит от правильной отрисовки стилей или срабатывания собственных событий DOM. Может использоваться с библиотекой тестирования с помощью @testing-library/cypress.

Основные различия между Vitest и браузерными раннерами заключаются в скорости и контексте выполнения. Вкратце, браузерные раннеры, такие как Cypress, могут отлавливать проблемы, которые инструменты на Node.js, такие как Vitest, не могут (например, проблемы со стилями, реальные собственные события DOM, cookies, локальное хранилище и сбои в сети), но браузерные раннеры на порядки медленнее Vitest, поскольку они открывают браузер, компилируют таблицы стилей и т.д. Cypress - это браузерный раннер, поддерживающий тестирование компонентов. Последняя информация о сравнении Vitest и Cypress приведена на странице сравнения Vitest.

Монтируемые библиотеки

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

  • @vue/test-utils - это официальная низкоуровневая библиотека тестирования компонентов, которая была написана для предоставления пользователям доступа к специфическим API Vue. На ней также построена библиотека нижнего уровня @testing-library/vue.

  • @testing-library/vue - это библиотека тестирования Vue, ориентированная на тестирование компонентов без привязки к деталям реализации. Созданная с учетом требований доступности, она также позволяет легко проводить рефакторинг. Основной принцип библиотеки заключается в том, что чем больше тесты похожи на то, как используется программное обеспечение, тем больше уверенности они могут обеспечить.

Мы рекомендуем использовать @vue/test-utils для тестирования компонентов в приложениях, так как ее направленность лучше соответствует приоритетам тестирования в приложениях. Используйте @vue/test-utils только в том случае, если вы создаете сложные компоненты, требующие тестирования специфичных для Vue внутренних компонентов.

Другие варианты

  • Nightwatch - это E2E тест-раннер с поддержкой тестирования компонентов Vue. (Пример проекта в Nightwatch v2)

  • WebdriverIO для кроссбраузерного тестирования компонентов, основанного на взаимодействии с пользователем на основе стандартизированной автоматизации. Его также можно использовать с библиотекой Testing Library.

E2E тестирование

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

E2E-тесты фокусируются на поведении многостраничного приложения, которое выполняет сетевые запросы к Vue-приложению, созданному для продакшн. Они часто включают в себя работу с базой данных или другим бэкендом и даже могут проводиться в живой среде.

С помощью E2E-тестов часто выявляются проблемы с маршрутизатором, библиотекой управления состояниями, компонентами верхнего уровня (например, App или Layout), публичными ресурсами или любой обработкой запросов. Как было сказано выше, они позволяют выявить критические проблемы, которые невозможно выявить с помощью модульных тестов или тестов компонентов.

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

E2E-тесты проверяют многие уровни приложения. Они могут быть направлены как на локально собранное приложение, так и на живую среду Staging. Тестирование в среде Staging включает в себя не только код фронтенда и статический сервер, но и все связанные с ним сервисы и инфраструктуру бэкенда.

Чем больше ваши тесты похожи на то, как используется ваше программное обеспечение, тем больше уверенности они могут дать вам. - Kent C. Dodds - автор библиотеки тестирования

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

Выбор решения для E2E-тестирования

Хотя E2E-тестирование получило негативную репутацию ненадежных (flaky) тестов и замедления процессов разработки, современные инструменты E2E сделали шаг вперед в создании более надежных, интерактивных и полезных тестов. При выборе фреймворка для E2E-тестирования в следующих разделах приведены рекомендации, которые следует учитывать при выборе фреймворка для тестирования вашего приложения.

Кроссбраузерное тестирование

Одним из основных преимуществ E2E-тестирования является возможность тестирования приложения в нескольких браузерах. Хотя может показаться желательным иметь 100% покрытие, важно отметить, что кроссбраузерное тестирование имеет убывающую отдачу от ресурсов команды из-за дополнительных затрат времени и машинной мощности, необходимых для его последовательного выполнения. Поэтому при выборе объема кросс-браузерного тестирования, необходимого вашему приложению, следует учитывать этот компромисс.

Быстрый сбор фидбека

Одна из основных проблем E2E-тестирования и разработки заключается в том, что запуск всего набора занимает много времени. Как правило, это делается только в конвейерах непрерывной интеграции и развертывания (CI/CD). Современные фреймворки E2E-тестирования помогли решить эту проблему, добавив такие функции, как распараллеливание, что позволяет выполнять CI/CD-конвейеры зачастую в разы быстрее, чем раньше. Кроме того, при локальной разработке возможность выборочного запуска одного теста для страницы, над которой ведется работа, а также "горячая" перезагрузка тестов позволяют повысить производительность труда разработчика.

Первоклассный опыт отладки

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

Видимость в headless режиме

При выполнении E2E-тестов в конвейерах непрерывной интеграции и развертывания они часто выполняются в headless-браузерах (т.е. не открывается видимый браузер для просмотра пользователем). Важной особенностью современных систем E2E-тестирования является возможность просмотра снимков и/или видеозаписей приложения во время тестирования, что позволяет понять причины возникновения ошибок. Исторически сложилось так, что поддерживать такие интеграции было очень утомительно.

Рекомендации

  • Cypress

    В целом мы считаем, что Cypress предоставляет наиболее полное решение E2E, обладающее такими возможностями, как информативный графический интерфейс, удобную отладку, встроенные утверждения и заглушки, flake-устойчивость, распараллеливание и моментальные снимки. Как уже упоминалось выше, в нем также реализована поддержка компонентного тестирования. Однако он поддерживает только браузеры на базе Chromium и Firefox.

Другие варианты

  • Playwright также является отличным решением для E2E-тестирования с более широким спектром поддержки браузеров (в основном WebKit). Более подробную информацию см. в разделе Почему Playwright.

  • Nightwatch то решение для E2E-тестирования на базе Selenium WebDriver. Это обеспечивает ему самую широкую поддержку браузеров.

  • WebdriverIO это фреймворк автоматизации тестирования для web и мобильного тестирования, основанный на протоколе WebDriver.

Рецепты

Добавление Vitest в проект

В проекте Vue, основанном на Vite, выполните команду:

sh
> npm install -D vitest happy-dom @testing-library/vue

Далее обновите конфигурацию Vite, добавив в нее блок опцию test:

js
// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  // ...
  test: {
    // включение jest-подобных глобальных тест API
    globals: true,
    // имитация DOM с помощью happy-dom
    // (требует установки happy-dom в качестве peer dependency)
    environment: 'happy-dom'
  }
})

Совет

Если вы используете TypeScript, добавьте vitest/globals в поле types в файле tsconfig.json.

json
// tsconfig.json

{
 "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

Затем создайте в проекте файл, заканчивающийся *.test.js. Все тестовые файлы можно разместить в каталоге test в корне проекта или в каталогах test рядом с исходными файлами. Vitest будет автоматически искать их, используя соглашение об именовании.

js
// MyComponent.test.js
import { render } from '@testing-library/vue'
import MyComponent from './MyComponent.vue'

test('это должно работать', () => {
  const { getByText } = render(MyComponent, {
    props: {
      /* ... */
    }
  })

  // утверждение вывода
  getByText('...')
})

Наконец, обновите файл package.json, добавив в него тестовый сценарий, и запустите его:

json
{
  // ...
  "scripts": {
    "test": "vitest"
  }
}
sh
> npm test

Тестирование Composables

В этом разделе предполагается, что вы прочитали раздел Composables.

Когда речь идет о тестировании composables, их можно разделить на две категории: composables, которые не зависят от экземпляра хост-компонента, и composables, которые зависят от экземпляра хост-компонента.

Composable зависит от экземпляра хост-компонента, если он использует следующие API:

  • Хуки жизненного цикла
  • Provide / Inject

Если composable использует только Reactivity API, то его можно протестировать путем прямого обращения к нему и утверждения возвращаемого состояния / методов:

js
// counter.js
import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return {
    count,
    increment
  }
}
js
// counter.test.js
import { useCounter } from './counter.js'

test('useCounter', () => {
  const { count, increment } = useCounter()
  expect(count.value).toBe(0)

  increment()
  expect(count.value).toBe(1)
})

Composable, который полагается на хуки жизненного цикла или Provide / Inject, должен быть обернут в компонент-хост для тестирования. Мы можем создать помощника, как показано ниже:

js
// test-utils.js
import { createApp } from 'vue'

export function withSetup(composable) {
  let result
  const app = createApp({
    setup() {
      result = composable()
      // подавление предупреждения об отсутствии шаблона
      return () => {}
    }
  })
  app.mount(document.createElement('div'))
  // возвращение результата и экземпляра приложения
  // для тестирования provide/unmount
  return [result, app]
}
js
import { withSetup } from './test-utils'
import { useFoo } from './foo'

test('useFoo', () => {
  const [result, app] = withSetup(() => useFoo(123))
  // имитация provide for для тестирование инъекций
  app.provide(...)
  // запуск проверок
  expect(result.foo.value).toBe(1)
  // при необходимости запускать хук onUnmounted
  app.unmount()
})

Для более сложных composables можно также упростить тестирование, написав тесты для компонента-обертки с использованием методов компонентного тестирования.

ТестированиеУже загружено