В течение прошлого года я был в команде, создающей прогрессивное веб-приложение (PWA) с использованием Vue.js. Фреймворк относительно новый, поэтому не существует стандартного руководства по правильному тестированию крупномасштабного приложения Vue.

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

Проблема масштабирования

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

Затем следует естественный разговор об улучшении качества кода, который приводит к разговору о добавлении тестового покрытия в наш интерфейс. Итак, разработчики засучили рукава и начали решать проблему написания тестов Javascript против довольно большого PWA в Vue. Эксперименты за другим, проверка кода за другой, мы пришли к нашему решению, но в нашем открытии остаются верными две мысли: одна хорошая, одна плохая.

Плохое: инструментарий для тестирования Javascript

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

Диспетчер пакетов: Webpack, Yarn и Конвейер ресурсов Rails.

Тестовый запуск: Карма

Утверждение: Chai.js

Имитаторы и заглушки: Sinon.js

Решение для тестирования, которое мы придумали, кажется разрозненным, что отражает фрагментированную природу среды тестирования Javascript. В сообществе Vue.js люди работали над специальной средой тестирования Vue, такой как Vue test utils, но на момент написания этой статьи проект был довольно молодым.

Пример теста внутри приложения выглядит так, как показано ниже, блоки describe и it предоставлены Karma.js. Предложение expect предоставлено Chai.js. Обратите внимание, что это разрозненное решение должно применяться и к другим фреймворкам, таким как Angular и React.

describe("formatDates", () => 
  { 
    const app = window.app 
    const expect = window.chai.expect 
    const subject = window.app.mixins.formatDates 
    const moment = window.moment 
    describe("#formatDate", () => { 
      it("returns a date formatted as MM.DD.YYYY", () => { 
        const date = moment().format() 
        const formattedDate = moment.utc(date).format("MM.DD.YYYY")   
        expect(subject.methods.formatDate(date))
         .to.eq(formattedDate)  
      }) 
    }) 
  
   describe("#formatUtcDate", () => { 
     it("returns an utc formatted date", () => { 
       let startDate = moment().format() 
       let value = subject.methods.formatUtcDate(startDate) 
       let formattedDate = moment.utc(startDate).format()
       
       expect(value).to.eql(formattedDate) 
     }) 
   }) 
})

Хорошие: компонентные тесты и тесты взаимодействия

Хорошо, давайте погрузимся в хорошую мысль. Мы понимаем, что компонентная структура позволяет нам по-разному тестировать код Javascript. Традиционно мы пишем unit test, integration test и end to end test вокруг приложения Javascript. Хотя эти области тестирования могут по-прежнему оставаться верными при тестировании с помощью Vue, нам нравится думать, что мы можем написать два типа тестов с помощью Vue.js: компонентные тесты и тесты взаимодействия. .

По своей сути приложение Vue состоит из компонентов и их взаимодействий, и приведенные ниже идеи справедливы для каждого компонента:

  1. каждый компонент имеет свое представление
  2. каждый компонент получает свои данные локально (реквизит) или глобально (Vuex)
  3. каждый компонент обрабатывает события, генерируя события или обновляя локальные или глобальные данные.

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

Тесты компонентов

Компонентные тесты фокусируются на функциональности компонента, что похоже на модульные тесты. В частности, мы хотим убедиться, что:

  1. Данные. Компонент имеет правильные data, computed property, props, загруженные в заданном жизненном цикле. Для этого может потребоваться правильная настройка хранилища Vuex, а также вызов правильного метода жизненного цикла, такого как mount.
  2. События. Компонент может правильно обрабатывать различные события, либо генерируя события, либо изменяя данные. Это может потребовать правильной настройки хранилища Vuex, а также правильных заглушек и шпионов прослушивателей событий.

Ниже приведен пример проверки правильности данных.

describe("Account Setting Component", () => { 
  const Vue = window.Vue 
  const Vuex = window.Vuex 
  const AccountSettingComponent = window.app.components.AccountSetting 
  const store = new Vuex.Store({ 
    state: { 
      account: { settings: { display_settings: true } } 
     } 
  }) 
  let vm
  beforeEach(() => { 
    const Ctor = Vue.extend(AccountSettingComponent) 
    vm = new Ctor({ 
      template: "<div id='vue-account-settings-component'></div>",
      propsData: {}, 
      store: store 
    }) 
    vm.$mount() 
   }) 
   describe("#mounted()", () => { 
     it("fetches display settings from the store", () => { 
       expect(vm.displaySettings).to.equal(true) 
     }) 
   }) 
})

В начале теста нам явно требуются зависимости вокруг Vue, включая Vue, Vuex и компонент, который мы пытаемся отобразить. Кроме того, мы настроили const store с объектом JSON, в котором мы имитируем только минимальный объем данных (т. е. состояние), который требуется в рамках теста.

Внутри блока beforeEach() мы вызываем конструктор компонента с помощью Vue.extend, а AccountSettingComponent не что иное, как объект JSON, более подробная документация находится здесь. В конце концов, мы можем $mount() таким образом активировать компонент.

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

describe("Account Setting Component", () => { 
  const Vue = window.Vue 
  const Vuex = window.Vuex 
  const AccountSettingComponent = window.app.components.AccountSetting 
  const store = window.app.store 
  let vm 
  beforeEach(() => { 
    const Ctor = Vue.extend(AccountSettingComponent) 
    vm = new Ctor({ 
      template: "<div id='vue-account-settings-component'></div>", 
      propsData: {}, 
      store: store 
    }) 
    vm.$mount() 
  }) 
  ... 
 describe("#methods", () => {  
   describe("handleDisplaySettingsToggle", () => { 
     it("toggles display settings", () => { 
       vm.handleDisplaySettingsToggle(false) 
       expect(vm.displaySettings).to.eql(false) 
     }) 
    }) 
  }) 
})

Тесты взаимодействия

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

  1. Изменение состояния. Событие внутри одного компонента отправляет действия другим компонентам или в хранилище, что приводит к изменению состояния других компонентов.
  2. Взаимодействие с сервером и клиентом. Отправленное действие запускает ajax с определенными параметрами. Получив ожидаемый ответ от сервера, который мы можем заглушить, приложение отображает правильные данные в представлении.

describe("Interaction tests: account settings and account review", () => { 
  const Vue = window.Vue 
  const Vuex = window.Vuex 
  const AccountSettingComponent = window.app.components.AccountSetting 
  const AccountReviewComponent = window.app.components.AccountReview 
  let vm 
  const store = new Vuex.Store({ 
    state: { 
     account: { settings: { display_settings: true } } 
    } 
  }) 
  beforeEach(() => { 
    const Ctor1 = Vue.extend(AccountSettingComponent) 
    vm1 = new Ctor1({ 
      template: "<div id='vue-account-settings-component'></div>",   
      propsData: {}, 
      store: store 
    }) 
   vm1.$mount() 
   const Ctor2 = Vue.extend(AccountReviewComponent) 
   vm2 = new Ctor2({ 
     template: "<div id='vue-account-review-component'></div>", 
     propsData: {}, 
     store: store 
   }) 
   
   vm2.$mount() }) 
   ... 
   describe("display_seetings", () => { 
     it("toggles display settings", () => { 
       vm1.handleDisplaySettingsToggle(false)
       expect(vm2.displaySettings).to.eql(false) 
     }) 
   }) 
})

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

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

Маршалл Шен © 2017

Первоначально опубликовано на сайте himarsh.org 28 августа 2017 г.