представлять
Vue-Test-Utils
даVue.js
Официальная библиотека утилит модульного тестирования, которая предоставляет рядAPI
чтобы нам было удобно писатьVue
Модульные тесты в приложении.
Существует много основных средств запуска модульных тестов, таких какJest
,Mocha
а такжеKarma
подождите, этоVue-Test-Utils
В документации есть соответствующие туториалы, здесь мы только знакомимVue-Test-Utils + Jest
Комбинированный пример.
Jest — это среда тестирования, разработанная Facebook. Vue описывает это так: это самый полнофункциональный тест-раннер. Он требует минимальной настройки, JSDOM установлен по умолчанию, утверждения встроены, а пользовательский интерфейс командной строки великолепен. Но вам нужен препроцессор, который может импортировать однофайловые компоненты в ваши тесты. Мы создали препроцессор vue-jest для обработки наиболее распространенных функций компонентов с одним файлом, но он по-прежнему не на 100% справляется с тем, что делает vue-loader.
Конфигурация среды
через строительные лесаvue-cli
Когда вы создаете новый проект, если вы выбираетеUnit Testing
модульный тест и выберитеJest
При выполнении тестов после создания проекта среда, необходимая для модульного тестирования, будет автоматически настроена и может использоваться напрямую.Vue-Test-Utils
а такжеJest
изAPI
Пришло время писать тест-кейсы.
Однако в начале нового проекта функция юнит-тестирования не была выбрана, если нужно добавить ее позже, есть два варианта:
Первая конфигурация:
Добавить прямо в проектunit-jest
Плагин автоматически установит и настроит необходимые зависимости.
vue add @vue/unit-jest
Вторая конфигурация:
Эта конфигурация будет немного хлопотной, ниже приведены конкретные шаги.
Установить зависимости
-
Установить
Jest
а такжеVue Test Utils
npm install --save-dev jest @vue/test-utils
-
Установить
babel-jest
,vue-jest
а также7.0.0-bridge.0
версияbabel-core
npm install --save-dev babel-jest vue-jest babel-core@7.0.0-bridge.0
-
Установить
jest-serializer-vue
npm install --save-dev jest-serializer-vue
настроитьJest
Jest
Конфигурацию можно найти вpackage.json
конфигурации; вы также можете создать новый файлjest.config.js
, поместите его в корневой каталог проекта. Здесь я выбираю настройку вjest.config.js
середина:
module.exports = {
moduleFileExtensions: [
'js',
'vue'
],
transform: {
'^.+\\.vue$': '<rootDir>/node_modules/vue-jest',
'^.+\\.js$': '<rootDir>/node_modules/babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: [
'jest-serializer-vue'
],
testMatch: ['**/__tests__/**/*.spec.js'],
transformIgnorePatterns: ['<rootDir>/node_modules/']
}
Описание каждого элемента конфигурации:
-
moduleFileExtensions
РассказыватьJest
суффикс файла, который должен совпадать -
transform
соответствовать.vue
файл при использованииvue-jest
обработка, соответствие.js
файл при использованииbabel-jest
иметь дело с -
moduleNameMapper
иметь дело сwebpack
псевдонимы, такие как:@
выражать/src
содержание -
snapshotSerializers
Сериализуйте сохраненные результаты тестов моментальных снимков, чтобы сделать их более красивыми -
testMatch
Какие файлы сопоставить для тестирования -
transformIgnorePatterns
Каталог не соответствует
настроитьpackage.json
Напишите командный скрипт, выполняющий тест:
{
"script": {
"test": "jest"
}
}
первый тест
Чтобы обеспечить согласованность среды, мы продемонстрируем шаги работы шаг за шагом, начиная с создания проекта.
использоватьvue-cli
Создать проект
В настоящее время я использую3.10.0
версияvue-cli
. Начните создавать проект:
vue create first-vue-jest
выберитеManually select features
Чтобы настроить функцию ручного выбора:
Vue CLI v3.10.0
┌───────────────────────────┐
│ Update available: 4.0.4 │
└───────────────────────────┘
? Please pick a preset:
VUE-CLI3 (vue-router, node-sass, babel, eslint)
default (babel, eslint)
❯ Manually select features
чек об оплатеBabel
,Unit Testing
:
? Check the features needed for your project:
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
◯ Router
◯ Vuex
◯ CSS Pre-processors
◯ Linter / Formatter
◉ Unit Testing
◯ E2E Testing
выберитеJest
:
? Pick a unit testing solution:
Mocha + Chai
❯ Jest
выберитеIn dedicated config files
Настройте каждую информацию о конфигурации в соответствующемconfig
В файле:
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys)
❯ In dedicated config files
In package.json
Введите n без сохранения пресета:
? Save this as a preset for future projects? (y/N) n
После создания проекта информация о конфигурации некоторых файлов выглядит следующим образом:
babel.config.js
:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
jest.config.js
, конфигурация этого файла по умолчанию является подключаемым модулем по умолчанию и может быть изменена на конфигурацию, указанную выше, в соответствии с фактическими потребностями.Jest
Конфигурация такая же.
module.exports = {
preset: '@vue/cli-plugin-unit-jest'
}
package.json
:
{
"name": "first-vue-jest",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"core-js": "^3.1.2",
"vue": "^2.6.10"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.0.0",
"@vue/cli-plugin-unit-jest": "^4.0.0",
"@vue/cli-service": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"vue-template-compiler": "^2.6.10"
}
}
выполнить тестовую команду
После завершения проекта с проектом, созданным на шагах выше, мы можемpackage.json
изscripts
я вижуtest:unit
, выполните его:
cd first-vue-jest
npm run test:unit
Затем вы увидите вывод в терминале,PASS
Указывает, что тестовый пример пройден, это официальный пример модульного теста. Теперь напишем что-нибудь свое.
Реализовать ToDoList
Глядя на диаграмму прототипа выше, можно выделить несколько четких требований:
- Введите то, что нужно сделать, в поле ввода с правой стороны головы, нажмите Enter, содержимое перейдет в список для завершения, и поле ввода одновременно будет очищено.
- Когда поле ввода пусто, нажмите Enter, не внося никаких изменений.
- Список ожидания поддерживает редактирование, но завершенный список редактировать нельзя.
- Справа от каждого элемента списка есть кнопка удаления,
-
означает, нажмите, чтобы удалить элемент - В списке дел есть кнопка, помеченная как выполненная, с
√
Знак указывает на то, что текущий элемент перемещается в завершенный список после нажатия - Завершенные списки имеют кнопки, помеченные как незавершенные с
x
Символ указывает на то, что текущий элемент перемещается в незавершенный список после нажатия - Номер списка увеличивается с 1
- Когда список дел пуст, слово to-do не отображается
- Когда заполненный список пуст, слово Completed не отображается.
Сначала напишите указанную выше страницу
Прежде чем писать страницу, сгенерируйте ее при создании проектаHelloWorld.vue
и соответствующий тестовый файлexample.spec.js
удалять; изменятьApp.vue
файл, импортToDoList
Компоненты:
<template>
<div id="app">
<ToDoList></ToDoList>
</div>
</template>
<script>
import ToDoList from './components/ToDoList'
export default {
components: {
ToDoList
}
}
</script>
существуетsrc/compoents
создать новый файлToDoList.vue
, я не буду публиковать его, если есть еще стили, вы можете увидеть его для деталей.Исходный код этого проекта:
<template>
<div class="todolist">
<header>
<h5>ToDoList</h5>
<input class="to-do-text"
v-model="toDoText"
@keyup.enter="enterText"
placeholder="输入计划要做的事情"/>
</header>
<h4 v-show="toDoList.length > 0">待完成</h4>
<ul class="wait-to-do">
<li v-for="(item, index) in toDoList" :keys="item">
<p>
<i>{{index + 1}}</i>
<input :value="item" @blur="setValue(index, $event)" type="text" />
</p>
<p>
<span class="move" @click="removeToComplete(item, index)">√</span>
<span class="del" @click="deleteWait(index)">-</span>
</p>
</li>
</ul>
<h4 v-show="completedList.length > 0">已完成</h4>
<ul class="has-completed">
<li v-for="(item, index) in completedList" :keys="item">
<p>
<i>{{index + 1}}</i>
<input :value="item" disabled="true" type="text" />
</p>
<p>
<span class="move" @click="removeToWait(item, index)">x</span>
<span class="del" @click="deleteComplete(index)">-</span>
</p>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
toDoText: '',
toDoList: [],
completedList: []
}
},
methods: {
setValue(index, e) {
this.toDoList.splice(index, 1, e.target.value)
},
removeToComplete(item, index) {
this.completedList.splice(this.completedList.length, 0, item)
this.toDoList.splice(index, 1)
},
removeToWait(item, index) {
this.toDoList.splice(this.toDoList.length, 0, item)
this.completedList.splice(index, 1)
},
enterText() {
if (this.toDoText.trim().length > 0) {
this.toDoList.splice(this.toDoList.length, 0, this.toDoText)
this.toDoText = ''
}
},
deleteWait(index) {
this.toDoList.splice(index, 1)
},
deleteComplete(index) {
this.completedList.splice(index, 1)
}
}
};
</script>
После того, как страница написана, примерно разрабатываются требования к прототипу, и страница выглядит так:
Изменить конфигурацию каталога
Следующий шаг — начать писать файлы модульных тестов.Перед записью мы сначала изменим каталог тестовых файлов как__tests__
, при измененииjest.config.js
Для следующей конфигурации обратите внимание наtestMatch
был изменен, чтобы соответствовать__tests__
все в каталоге.js
файл.
module.exports = {
moduleFileExtensions: [
'js',
'vue'
],
transform: {
'^.+\\.vue$': '<rootDir>/node_modules/vue-jest',
'^.+\\.js$': '<rootDir>/node_modules/babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: [
'jest-serializer-vue'
],
testMatch: ['**/__tests__/**/*.spec.js'],
transformIgnorePatterns: ['<rootDir>/node_modules/']
}
написать тестовые файлы
существует__tests__/unit/
Создайте новый файл в каталогеtodolist.spec.js
, мы договорились протестироватьvue
файл, то его файл модульного теста обычно называется*.spec.js
или*.test.js
.
import { shallowMount } from '@vue/test-utils'
import ToDoList from '@/components/ToDoList'
describe('test ToDoList', () => {
it('输入框初始值为空字符串', () => {
const wrapper = shallowMount(ToDoList)
expect(wrapper.vm.toDoText).toBe('')
})
})
Приведенный выше тестовый файл кратко описывает:
-
shallowMount
создаст файл, содержащий смонтированный и визуализированныйVue
компонентWrapper
, который заглушает только текущий компонент, а не дочерние компоненты. -
describe(name, fn)
Вот определение набора тестов,test ToDoList
имя набора тестов,fn
это конкретная исполняемая функция -
it(name, fn)
является тестовым случаем,输入框初始值为空字符串
имя тестового случая,fn
Это конкретная исполняемая функция; в наборе тестов можно защитить несколько тестовых случаев. -
expect
даJest
Встроенный стиль утверждения, в отрасли существуют и другие стили утверждения, такие какShould
,Assert
Ждать. -
toBe
даJest
Предоставленные методы утверждения, больше можно найти вJest Expect
См. конкретное использование.
it('待完成列表初始值应该为空数组', () => {
const wrapper = shallowMount(ToDoList)
expect(wrapper.vm.toDoList.length).toBe(0)
})
it('已完成列表初始值应该为空数组', () => {
const wrapper = shallowMount(ToDoList)
expect(wrapper.vm.completedList).toEqual([])
})
Списки подлежащих завершению и завершенных списков на самом деле являются списками, поэтому поле, в котором хранятся данные, должно бытьArray
тип, пустой список — это пустой массив. Если второй тестовый пример изменить на:
expect(wrapper.vm.completedList).toBe([])
выдаст ошибку, потому чтоtoBe
Внутри метода находится вызовObject.is(value1, value2)
сравнить 2 значения на равенство, и==
или===
Логика суждений другая. очевидноObject.is([], [])
вернусьfalse
.
it('输入框值变化的时候,toDoText应该跟着变化', () => {
const wrapper = shallowMount(ToDoList)
wrapper.find('.to-do-text').setValue('晚上要陪妈妈逛超市')
expect(wrapper.vm.toDoText).toBe('晚上要陪妈妈逛超市')
})
it('输入框没有值,敲入回车的时候,无变化', () => {
const wrapper = shallowMount(ToDoList)
const length = wrapper.vm.toDoList.length
const input = wrapper.find('.to-do-text')
input.setValue('')
input.trigger('keyup.enter')
expect(wrapper.vm.toDoList.length).toBe(length)
})
it('输入框有值,敲入回车的时候,待完成列表将新增一条数据,同时清空输入框', () => {
const wrapper = shallowMount(ToDoList)
const length = wrapper.vm.toDoList.length
const input = wrapper.find('.to-do-text')
input.setValue('晚上去吃大餐')
input.trigger('keyup.enter')
expect(wrapper.vm.toDoList.length).toBe(length + 1)
expect(wrapper.vm.toDoText).toBe('')
})
-
setValue
Вы можете установить значение текстового элемента управления и обновить его.v-model
связанные данные. -
.to-do-text
ЯвляетсяCSS
Селектор;Vue-Test-Utils
при условииfind
метод для возвратаWrapper
; селектор может бытьCSS
селектор, может бытьVue
Компонент также может быть объектом, содержащимname
илиref
свойства, например, можно использовать так:wrapper.find({ name: 'my-button' })
-
wrapper.vm
ЯвляетсяVue
пример, толькоVue
Обертка компонента имеет толькоvm
это имущество; поwrapper.vm
иметь доступ ко всемVue
Свойства и методы экземпляра. Например:wrapper.vm.$data
,wrapper.vm.$nextTick()
. -
trigger
метод может быть использован для запускаDOM
События, инициированные здесь события, все синхронны, поэтому нет необходимости помещать утверждение в$nextTick()
В то же время он поддерживает передачу объекта, и при захвате события можно получить свойства входящего объекта. Это можно написать так:wrapper.trigger('click', {name: "bubuzou.com"})
it('待完成列表支持编辑功能,编辑后更新toDoList数组', () => {
const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['跑步半小时']})
wrapper.find('.wait-to-do li').find('input').setValue('绕着公园跑3圈')
wrapper.find('.wait-to-do li').find('input').trigger('blur')
expect(wrapper.vm.toDoList[0]).toBe('绕着公园跑3圈')
})
Сначала сsetData
ДатьtoDoList
Установите начальное значение, чтобы оно отображало элемент списка; затем найдите элемент списка и используйтеsetValue
Задаем ему значение, имитируя редактирование, используется поле ввода элемента списка:value="item"
границаvalue
, такsetValue
Обновления не могут быть запущены; только черезtrigger
запустить обновлениеtoDoList
ценность .
it('待完成列表点击删除,同时更新toDoList数组', () => {
const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['睡前看一小时书']})
expect(wrapper.vm.toDoList.length).toBe(1)
wrapper.find('.wait-to-do li').find('.del').trigger('click')
expect(wrapper.vm.toDoList.length).toBe(0)
})
it('点击待完成列表中某项的已完成按钮,数据对应更新', () => {
const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['中午饭后吃一个苹果']})
expect(wrapper.vm.toDoList.length).toBe(1)
expect(wrapper.vm.completedList.length).toBe(0)
wrapper.find('.wait-to-do li').find('.move').trigger('click')
expect(wrapper.vm.toDoList.length).toBe(0)
expect(wrapper.vm.completedList.length).toBe(1)
})
it('点击已完成列表中某项的未完成按钮,数据对应更新', () => {
const wrapper = shallowMount(ToDoList)
wrapper.setData({completedList: ['唱了一首歌']})
expect(wrapper.vm.toDoList.length).toBe(0)
expect(wrapper.vm.completedList.length).toBe(1)
wrapper.find('.has-completed li').find('.move').trigger('click')
expect(wrapper.vm.toDoList.length).toBe(1)
expect(wrapper.vm.completedList.length).toBe(0)
})
it('列表序号从1开始递增', () => {
const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: ['早上做作业', '下午去逛街']})
expect(wrapper.vm.toDoList.length).toBe(2)
expect(wrapper.find('.wait-to-do').html()).toMatch('<i>1</i>')
expect(wrapper.find('.wait-to-do').html()).toMatch('<i>2</i>')
})
it('当待完成列表为空的时候,不显示待完成字样', () => {
const wrapper = shallowMount(ToDoList)
wrapper.setData({toDoList: []})
expect(wrapper.find('h4').isVisible()).toBeFalsy()
wrapper.setData({toDoList: ['明天去爬北山']})
expect(wrapper.find('h4').isVisible()).toBeTruthy()
})
Несколько тестовых случаев могут быть написаны в одном тестовом примереexpect
чтобы убедиться в правильности утверждения.
Асинхронное тестирование
Наконец, чтобы имитировать асинхронное тестирование, мы добавляем требование, то есть при загрузке страницы она будет запрашивать данные удаленного списка ожидания.
Создайте новый проект в корневом каталоге проекта__mocks__
каталог и создайте новыйaxios.js
:
const toToList = {
success: true,
data: ['上午去图书馆看书', '下去出去逛街']
}
export const get = (url) => {
if (url === 'toToList.json') {
return new Promise((resolve, reject) => {
if (toToList.success) {
resolve(toToList)
} else {
reject(new Error())
}
})
}
}
ИсправлятьToDoList.vue
, импортaxios
и увеличитьmounted
:
<script>
import * as axios from '../../__mocks__/axios'
export default {
mounted () {
axios.get('toToList.json').then(res => {
this.toDoList = res.data
}).catch(err => {
})
},
};
</script>
Тестовый случай записывается так:
it('当页面挂载的时候去请求数据,请求成功后应该会返回2条数据', (done) => {
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.toDoList.length).toBe(2)
done()
})
})
Для асинхронного кода при написании утверждений его нужно поместить вwrapper.vm.$nextTick()
, и вызовите его вручнуюdone()
.
Настроить тестовое покрытие
Тест-кейс написан частично, если смотреть на покрытие, то нужно настроить тестовое покрытие. существуетjest.config.js
Новая конфигурация в:
collectCoverage: true,
collectCoverageFrom: ["**/*.{js,vue}", "!**/node_modules/**"],
существуетpackage.json
изscripts
Добавлена новая конфигурация:
"test:cov": "vue-cli-service test:unit --coverage"
Затем запускаем в терминале:npm run test:cov
, результат следующий:
После запуска именования тестового покрытия оно будет сгенерировано в корневом каталоге проекта.coverage
каталог, браузер открываетindex.html
: