Создание приложения на Vue.js по TDD — обширное руководство для людей, у которых есть время — часть 1
Оригинальная статья: Daniel Kuroski Working an application in Vue.js with TDD — An extensive guide for people who have time — part 1
Это первая статья в серии статей:
- Part 1: Настройка и первый тест
- Part 2: Улучшаем UserView
- Part 3: Тестирование store и остальных компонентов слоя презентации
- Part 4: Тестирование службы запросов API
- Part 5: Добавление и тестирование со сторонними зависимостями
Хранилище с финальным кодом: kuroski/article-tdd-vue
Во время семинаров VueJS Summit 2018 я лично поговорил с Эддом Йербургом и показал ему, как я тестирую свои приложения, и после некоторой поддержки я решил написать эту статью 😅
Я надеюсь показать, как легко тестировать приложения на Vue.js!
Что мы будем делать
Проект будет очень простым. Мы собираемся создать приложение, которое будет искать пользователя на Github через его API.
Этот проект может показаться простым, но его будет достаточно для демонстрации всех видов тестов, которые обычно используются в разработке приложений на Vue.js. Здесь мы собираемся тестировать:
- Компоненты
- Vuex
- Сервисы
В этом проекте мы собираемся применить концепцию TDD!
С начало мы напишем тесты, которые будут проваливаться (красная фаза), затем приложим минимум усилий, чтобы они стали успешными (зеленая фаза). Далее, проведем рефакторинг кода (фаза рефакторинга), если это будет необходимо. Мы будем повторять этот цикл до тех пор, пока мы не завершим наше приложение.
Одним из преимуществ этого метода является то, что мы получаем гораздо более быструю обратную связь, что позволяет нам работать с гораздо меньшими циклами изменений во время разработки нашего проекта.
Вначале может показаться сложным следовать концепции TDD, потому что это требует изменения мышления, так как с начало мы пишем тесты перед написанием производственного кода.
Я хочу напомнить, что мы не собираемся строго следовать всему, что предлагается, но для этого проекта я хочу показать, что это может быть чем-то простым, что можно сделать.
Моделирование приложения
Прежде всего, давайте спланируем, то что мы собираемся сделать.
Мы собираемся разбить будущее приложение на три составляющие:
- UserView — «Умный» компонент, отвечающий за связь с хранилищем store и загрузку наших презентационных компонентов
- VUserSearchForm — «Тупые» компоненты, отвечающие за визуализацию формы, а также за передачу сообщения родительскому компоненту с условием поиска
- VUserProfile — «Тупой» компонент, отвечающий за предоставление информации о наших найденных пользователях
Мы также собираемся искать пользователей через Github API.
Скачайте vue-cli и давайте создадим наш проект:
npm i -g @vue/cli
vue create tdd-app
Ниже показаны функции, которые я выбрал через CLI. Выберите наиболее удобные для вас.
Vue CLI v3.0.5 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, PWA, Router, Vuex, Linter, Unit ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a linter / formatter config: Standard ? Pick additional lint features: Lint on save ? Pick a unit testing solution: Jest ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? (y/N) N
Давайте предложим задачу: мы будем запускать npm run serve только тогда, когда закончим наше приложение.
Очистим проект от лишнего
Генерация проекта через Vue CLI заканчивает тем, что туда добавляются некоторые начальные файлы. Давайте удалим все, что нам не нужно в этот первый момент.
Удалите:
- src/store.js
- src/assets/logo.png
- src/components/HelloWorld.vue
- src/views/About.vue
- src/views/Home.vue
- tests/unit/HelloWorld.spec.js
Очистите от лишнего App.vue
<template> <div id="app"> <router-view/> </div> </template><style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } </style>
Очистите router.js
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ mode: 'history', base: process.env.BASE_URL, routes: [] })
Создайте каталог store, а в нем файл для размещения в нашем store/index.js:
// src/store/index.js import Vue from 'vue' import Vuex from 'vuex' import state from '@/store/state' import mutations from '@/store/mutations' import actions from '@/store/actions' Vue.use(Vuex) export default new Vuex.Store({ state, mutations, actions, })
Далее создайте файлы: src/store/state.js
src/store/actions.js
src/store/mutations.js
и все они имеют один и тот же кусок кода:
export default {}
Добавим скрипт npm для наблюдения за нашими тестами в package.json
... "test:unit": "vue-cli-service test:unit", "test:unit:watch": "vue-cli-service test:unit --watchAll"
И, наконец, мы собираемся установить некоторые зависимости, которые мы будем использовать в проекте
npm i -d axios npm i -D flush-promises nock
Создание нашего первого теста
Теперь мы можем начать создавать наши тесты. Давайте начнем с нашего первого компонента, UserView, помня, что мы используем jest в качестве основы наших тестов.
Это будет интеллектуальный компонент, и он будет связываться с store для поиска пользователей Github.
Давайте создадим тогда файл: tests/unit/UserView.spec.js
describe('UserView', () => { it('works', () => {}) })
И мы можем запустить тесты: npm run test:unit
Но что именно мы тестируем в наших компонентах?
Мы можем проверять многие вещи, но мы должны опираться на следующие идеи:
Если компонент что то рендерит
Мы должны гарантировать, что по крайней мере компонент выполняет рендеринг правильно.
Если он правильно все рендерит
Если мы гарантируем, что компонент выполняет рендеринг, мы можем проверить, правильно ли он выполняет рендеринг. У нашего компонента есть кнопка и элемент input, или он может содержит кнопку и комментарий, говорящий «Input will be implemented in v2».
Его биндинги
Мы можем проверить все его возможные связи. Передает ли наш компонент правильные props своим дочерним компонентам?
События
При нажатии кнопки или получении определенного события наш компонент ведет себя ожидаемым образом?
Крайние случаи
В случае наличия списков нам нужно гарантировать, как он будет себя вести, с пустым списком, списком из 5 или 100 элементов.
Продолжим создания нашего компонента
Зная это, нам нужно сделать напишем наш тест. Давайте реализуем первый случай, все ли правильно рендериться в UserView:
import { shallowMount } from '@vue/test-utils' import UserView from '@/views/UserView' describe('UserView', () => { it('renders the component', () => { // arrange const wrapper = shallowMount(UserView) // assert expect(wrapper.html()).toMatchSnapshot() }) })
Мы используем jest в качестве нашей тестовой среды и vue-test-utils, чтобы помочь в работе с нашими компонентами.
В строке 7 мы используем shallowMount для создания экземпляра нашего компонента. Это означает:
Эта функция создает только первый уровень его зависимостей
Vue-test-utils также предоставляет другой метод mount, который создает полное дерево зависимостей.
Но для нашего случая уже достаточно shallowMount.
В строке 10 мы берем wrapper, который является представлением нашего компонента, созданного vue-test-utils, а затем мы «фотографируем» html нашего компонента (создаем snapshot). Этот HTML существует благодаря vue-test-utils.
Запустив npm run test:unit, мы получили первую ошибку
Компонента по-прежнему не существует. Теперь мы находимся в фазе TDD, где мы наконец можем создать наш файл.
//views/UserView.vue <script> export default { name: 'UserView' } </script> <template> <div> UserView </div> </template>
Здесь мы создали минимальное представление нашего компонента, и благодаря этому у нас есть первое прохождение теста, которое выполняется после запуска команды: npm run test:unit
Но что такое этот snapshot?
По сути, jest сделал «снимок» html нашего компонента, и если мы проверим каталог, внутри tests то там будет создан файл __snapshots__/UserView.spec.js.snap с содержим html компонента.
Теперь у нас есть ссылка на наш компонент, и в случае каких-либо изменений в его html наш тест не будет проходить.
Внести изменения в UserView.vue и убедитесь, что тест не будет проходить
<script> export default { name: 'UserView' } </script> <template> <div> Hello World </div> </template>
Теперь мы можем видеть, что в случае каких-либо изменений в нашем html, нам нужно исправить ошибку теста или вернуться к исходному состоянию.
В случае преднамеренного изменения мы можем обновить snapshot, заявив, что эта версия будет нашей новой ссылкой.
Для этого вам нужно после запуска команды npm run test:unit:watch всего лишь нажать u на терминале, и snapshot будет обновлен автоматически 😊. Чтобы увидеть больше опций, вам нужно нажать w в терминале, чтобы открыть список опций шутки.
НИКОГДА не изменяйте файл snapshot вручную.
Заключение
В этой первой статье мы сделали следующее:
- Описали в то, что мы будем делать
- Спланировали наше приложение
- Осуществили начальную настройку нашего проекта. Оставили только минимально необходимые файлы для работы
- Объяснили что необходимо тестировать
- Создали наш первый тест
Вторая часть
Твиттер автора оригинальной статьи : @DKuroski