- Оригинальный адрес:Dev.to/блэк соник/…
- Оригинальный автор: Габор Соос
- Переводчик: Ма Сюэцинь
- Отказ от ответственности: этот перевод используется только для обучения и общения, пожалуйста, указывайте источник при перепечатке
Когда вы собираетесь закончить проект, вдруг во многих местах проекта появляются ошибки, и вы заканчиваете один, а затем появляется новый, как будто играете в игру «Ударь крота»… После нескольких раундов, вы почувствуете беспорядок. На данный момент есть спасательное решение, которое может заставить ваш проект снова засиять, а именно написать тесты для функций, которые будут разработаны и которые уже существуют. Написание тестов гарантирует, что функции не содержат ошибок.
В этом руководстве я покажу вам, как писать модульные, интеграционные и сквозные тесты для приложения Vue.
Для получения дополнительных тестовых примеров вы можете проверить мойРеализация Vue TodoApp.
1. Тип
Мы можем написать три типа тестов: модульные тесты, интеграционные тесты и сквозные тесты. Следующая пирамида может помочь нам понять эти типы тестов.
Тесты в нижней части пирамиды легче писать, быстрее запускать и проще поддерживать. Но почему мы не можем просто написать модульные тесты? Потому что тесты на вершине пирамиды могут помочь нам проверить, хорошо ли работают вместе различные компоненты системы, что делает нас более уверенными в системе.
Модульное тестирование можно использовать только в одной единице кода (класс, функция); интеграционное тестирование может проверить, что несколько единиц работают вместе, как ожидается (иерархия компонентов, компонент + хранилище); сквозное тестирование выполняется извне. мир Наблюдайте за приложением: браузером и его взаимодействием.
2. Тестировщик
Для новых проектов Vue самый простой способ добавить тесты — использоватьVue CLI. В проекте сборки (выполнитьvue create myapp), вам нужно вручную выбрать модульные тесты и E2E-тесты.
После завершения установки в package.json появятся следующие дополнительные зависимости:
- @vue/cli-plugin-unit-mocha: использоватьMochaПлагины для модульного/интеграционного тестирования
- @ Vue/Test-Utils: модульный/интегрированный тестБиблиотека инструментов
- chai: библиотека утвержденийChai
Отныне файлы модульных/интеграционных тестов могут использовать*.spec.jsсуффикс, написанный вtests/unitв каталоге. Тестовый каталог не запрограммирован, вы можете изменить его с помощью следующих аргументов командной строки:
vue-cli-service test:unit --recursive 'src/**/*.spec.js'
recursiveЭтот параметр указывает средству запуска теста искать файлы теста на основе следующего шаблона подстановочных знаков.
3. Модульное тестирование
Пока все хорошо, но мы еще не написали никаких тестов. Далее мы напишем наш первый модульный тест!
describe('toUpperCase', () => {
it('should convert string to upper case', () => {
// 准备
const toUpperCase = info => info.toUpperCase();
// 操作
const result = toUpperCase('Click to modify');
// 断言
expect(result).to.eql('CLICK TO MODIFY');
});
});
Приведенный выше пример подтверждаетtoUpperCaseПреобразует ли функция входящую строку в верхний регистр.
Первый — это подготовка, импорт функции, создание экземпляра объекта и настройка его параметров, чтобы целевой объект (здесь — функция) перешел в тестируемое состояние. Затем используйте функцию/метод. Наконец, мы делаем утверждение о результате, возвращаемом функцией.
Мокко обеспечиваетdescribeа такжеitдва метода.describeФункциональное представление организует тестовые примеры вокруг тестовых модулей: тестовыми модулями могут быть классы, функции, компоненты и т. д. Mocha не имеет встроенной библиотеки утверждений, поэтому мы должны использовать Chai: он может установить ожидания в отношении результата. Chai имеет много различных встроенных утверждений, но не все варианты использования охватываются, а отсутствующие утверждения можно импортировать через систему плагинов Chai.
Большую часть времени вы также будете писать модульные тесты для бизнес-логики вне иерархии компонентов, например для управления состоянием или обработки серверного API.
4. Компонентный дисплей
Следующим шагом будет написание интеграционных тестов для компонента. Интеграционные тесты проверяют не только код Javascript, но и взаимодействие между DOM и логикой соответствующего компонента.
// src/components/Footer.vue
<template>
<p class="info">{{ info }}</p>
<button @click="modify">Modify</button>
</template>
<script>
export default {
data: () => ({ info: 'Click to modify' }),
methods: {
modify() {
this.info = 'Modified by click';
}
}
};
</script>
Первый компонент, который мы тестируем, — это компонент, который отображает свое состояние и изменяет его при нажатии кнопки.
// test/unit/components/Footer.spec.js
import { expect } from 'chai';
import { shallowMount } from '@vue/test-utils';
import Footer from '@/components/Footer.vue';
describe('Footer', () => {
it('should render component', () => {
const wrapper = shallowMount(Footer);
const text = wrapper.find('.info').text();
const html = wrapper.find('.info').html();
const classes = wrapper.find('.info').classes();
const element = wrapper.find('.info').element;
expect(text).to.eql('Click to modify');
expect(html).to.eql('<p class="info">Click to modify</p>');
expect(classes).to.eql(['info']);
expect(element).to.be.an.instanceOf(HTMLParagraphElement);
});
});
Чтобы визуализировать компонент в тесте, мы должны использовать библиотеку инструментов тестирования Vue.shallowMountилиmount. Оба метода будут отображать компонент, ноshallowMountДочерние компоненты не будут отображаться (дочерние элементы будут пустыми элементами). Когда нам нужно представить компонент для тестирования, мы можем указать на него относительный путь.../../../src/components/Footer.vueили используйте псевдоним@, путь начинается с@Символ представляет собой ссылку на исходную папку src.
мы можем использоватьfindСелектор ищет представленные DOM и получает свой HTML, текст, имя класса или собственного элемента DOM. Если поиск для фрагмента, который может не существовать, мы можем использоватьexistsспособ определить, существует ли он. Вышеупомянутые утверждения предназначены только для обозначения различных ситуаций, и достаточно действительно написать одно из утверждений в тестовом примере.
5. Взаимодействие компонентов
Мы протестировали рендеринг DOM, но еще не взаимодействовали с компонентом. Мы можем взаимодействовать с компонентами через DOM или экземпляр компонента:
it('should modify the text after calling modify', () => {
const wrapper = shallowMount(Footer);
wrapper.vm.modify();
expect(wrapper.vm.info).to.eql('Modified by click');
});
В приведенном выше примере показано, как использовать экземпляры компонентов для реализации взаимодействий. мы можем использоватьvmАтрибуты получают доступ к экземплярам компонентов, а к компонентам также можно получить доступ через экземпляры компонентов.methodМетоды в иdataСвойства в объекте (состояние).
Другой способ — взаимодействовать с компонентом через DOM, мы можем запускать событие click на кнопке и смотреть, отображается ли текст:
it('should modify the text after clicking the button', () => {
const wrapper = shallowMount(Footer);
wrapper.find('button').trigger('click');
const text = wrapper.find('.info').text();
expect(text).to.eql('Modified by click');
});
вызыватьbuttonизclickСобытия эквивалентны вызову экземпляра компонентаmodifyметод.
6. Взаимодействие компонентов «родитель-потомок»
Выше мы тестировали компоненты по отдельности, но реальное приложение состоит из нескольких частей. родительский компонент черезpropsОбщайтесь с дочерними компонентами, которые взаимодействуют с родительскими компонентами, запуская события.
Мы можем изменить входящий компонентpropsобновить отображаемую копию компонента и уведомить родительский компонент об изменении посредством события.
export default {
props: ['info'],
methods: {
modify() {
this.$emit('modify', 'Modified by click');
}
}
};
В следующем тесте нам нужно поставитьpropsв качестве входных данных и прослушивать инициированные события.
it('should handle interactions', () => {
const wrapper = shallowMount(Footer, {
propsData: { info: 'Click to modify' }
});
wrapper.vm.modify();
expect(wrapper.vm.info).to.eql('Click to modify');
expect(wrapper.emitted().modify).to.eql([
['Modified by click']
]);
});
shallowMountа такжеmountВторой параметр метода является необязательным параметром, мы можем использоватьpropsDataустановить вводprops. Инициированные события могут быть вызваны вызовомemittedметод, результатом является объект, ключом является имя события, а значением является массив параметров события.
6. интеграция магазина
В предыдущих примерах все состояние было внутри компонента. А в сложных приложениях нам нужно получить доступ и изменить одно и то же состояние в разных местах.Vuex— это библиотека управления состоянием для Vue, которая помогает вам организовать управление состоянием в одном месте и обеспечить предсказуемость его изменений.
const store = {
state: {
info: 'Click to modify'
},
actions: {
onModify: ({ commit }, info) => commit('modify', { info })
},
mutations: {
modify: (state, { info }) => state.info = info
}
};
const vuexStore = new Vuex.Store(store);
Вышеупомянутый магазин имеет одно свойство статуса, такое же, как мы установили в вышеуказанных компонентах. мы можем использоватьonModifyОперация изменяет состояние, которое передает входной параметр вmodifyмутация для изменения состояния.
Во-первых, мы можем написать отдельные модульные тесты для каждого метода в хранилище:
it('should modify state', () => {
const state = {};
store.mutations.modify(state, { info: 'Modified by click' });
expect(state.info).to.eql('Modified by click');
});
Мы также можем создать хранилище для написания интеграционных тестов, чтобы убедиться, что все работает без ошибок:
import Vuex from 'vuex';
import { createLocalVue } from '@vue/test-utils';
it('should modify state', () => {
const localVue = createLocalVue();
localVue.use(Vuex);
const vuexStore = new Vuex.Store(store);
vuexStore.dispatch('onModify', 'Modified by click');
expect(vuexStore.state.info).to.eql('Modified by click');
});
Во-первых, мы должны создать локальный экземпляр Vue, а затем использоватьuseутверждение. если мы не позвонимuseметод выдаст ошибку. Создавая локальную копию Vue, мы также избегаем загрязнения глобального объекта.
мы можем пройтиdispatchметод смены магазина. Первый параметр указывает, какое действие вызывать, второй параметр передается как параметр действия. Мы можем проверить текущее состояние в любое время через свойство состояния.
При использовании хранилища компонента мы должны передать локальный экземпляр Vue и экземпляр хранилища функции монтирования.
const wrapper = shallowMount(Footer, { localVue, store: vuexStore });
8. Маршрутизация
Настройка тестового маршрута чем-то похожа на тестовый магазин, вы должны создать частичную копию экземпляра Vue и экземпляра маршрута, использовать экземпляр маршрута в качестве плагина, а затем создать компонент.
<div class="route">{{ $router.path }}</div>
Приведенная выше строка шаблона компонента будет отображать текущий путь маршрута. В тесте мы можем утверждать содержимое этого элемента.
import VueRouter from 'vue-router';
import { createLocalVue } from '@vue/test-utils';
it('should display route', () => {
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter({
routes: [
{ path: '*', component: Footer }
]
});
const wrapper = shallowMount(Footer, { localVue, router });
router.push('/modify');
const text = wrapper.find('.route').text();
expect(text).to.eql('/modify');
});
мы используем*Путь добавляет к компоненту маршрут с полным соответствием. имеютrouterПосле экземпляра нам также нужно использовать маршрутизаторpushМетод настраивает навигацию для приложения.
Создание всех маршрутов может занять много времени, мы можем реализовать псевдомаршрутизатор и передавать его как фиктивные данные:
it('should display route', () => {
const wrapper = shallowMount(Footer, {
mocks: {
$router: {
path: '/modify'
}
}
});
const text = wrapper.find('.route').text();
expect(text).to.eql('/modify');
});
мы также можемmocksопределить$storeсвойства для имитации магазина.
9. HTTP-запрос
Начальное состояние обычно получается через HTTP-запрос. Нам легко делать настоящие запросы в наших тестах, но это делает тесты хрупкими и внешними зависимостями. Чтобы этого избежать, мы можем изменить реализацию запроса во время выполнения. Изменение реализации во время выполнения называется насмешкой, и мы будем использоватьSinonЭто издевательская структура для достижения.
const store = {
actions: {
async onModify({ commit }, info) {
const response = await axios.post('https://example.com/api', { info });
commit('modify', { info: response.body });
}
}
};
Мы изменили реализацию хранилища в приведенном выше коде: сначала входные параметры отправляются через запрос POST, а затем результат этого запроса передается в мутацию. Код становится асинхронным и имеет внешнюю зависимость, которую мы должны изменить (смоделировать) перед запуском тестов.
import chai from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
chai.use(sinonChai);
it('should set info coming from endpoint', async () => {
const commit = sinon.stub();
sinon.stub(axios, 'post').resolves({
body: 'Modified by post'
});
await store.actions.onModify({ commit }, 'Modified by click');
expect(commit).to.have.been.calledWith('modify', { info: 'Modified by post' });
});
мыcommitметод создает фиктивную реализацию и изменяетaxios.postОригинальная реализация. Эти псевдореализации могут захватывать переданные им параметры и реагировать на возвращаемое ими содержимое. Мы неcommitМетод указывает возвращаемое значение, поэтому он вернет нулевое значение.axios.postвернетpromise,Долженpromiseанализируется сbodyсвойства объекта.
мы должны поставитьsinonChaiдобавлен как плагин кChai, так что сигнатура вызова может быть подтверждена. Этот плагин расширяетChaiизto.have.beenсвойства иto.have.been.calledWithметод.
если мы вернемPromise, тестовая функция станет асинхронной. Mocha может обнаруживать и ждать завершения асинхронных функций. Внутри функции ждемonModifyметод завершается, затем утверждает псевдоcommitбыл ли метод вызван и передан вpostПараметр, возвращаемый вызовом.
10. Браузер
С точки зрения кода мы протестировали каждый аспект приложения. Но есть один вопрос, на который мы до сих пор не можем ответить: может ли приложение работать в браузере? Сквозные тесты, написанные на Cypress, могут дать нам ответ.
Vue CLI позволяет запускать приложение и запускать тесты Cypress в браузере, а затем закрывать приложение. если вы хотитебезголовый режимДля запуска тестов Cypress необходимо установитьheadlessтеги добавляются к команде.
describe('New todo', () => {
it('it should change info', () => {
cy.visit('/');
cy.contains('.info', 'Click to modify');
cy.get('button').click();
cy.contains('.info', 'Modified by click');
});
});
Организация приведенного выше тестового кода такая же, как и для модульных тестов:describeпредставляет тестовую группу,itПредставляет тестовый запуск. глобальная переменнаяcyПредставляет кипарисовый бегун. Мы можем синхронно командовать бегуном, что делать в браузере.
После посещения главной страницы (visit), мы можем получить доступ к HTML на странице через селекторы CSS. мы можем использоватьcontainsУтверждать содержимое элементов. Взаимодействие со страницей такое же: сначала выберите элемент (get), затем взаимодействуйте (click). В конце теста мы проверяем, изменилось ли содержимое.
Суммировать
Мы рассмотрели все тестовые примеры, от базовых модульных тестов функции до сквозных тестов, выполняемых в реальном браузере. В этой статье мы создали интеграционные тесты для строительных блоков приложения Vue (компоненты, хранилище, маршрутизация) и рассмотрели некоторые основы реализации имитации. Вы можете использовать эти методы в существующих или будущих проектах, чтобы избежать процедурных ошибок. Надеемся, что эта статья снизит порог входа для написания тестов для приложений Vue.
Примеры в этой статье иллюстрируют многое о тестировании, надеюсь, вам понравится!
Если вы считаете, что этот контент ценен для вас, пожалуйста, поставьте лайк и подпишитесь на нашуОфициальный сайтА на нашем официальном аккаунте WeChat (WecTeam) каждую неделю публикуются качественные статьи: