Создание компонента Vue с использованием TDD: краткое введение

Spread the love

Статья для новичков в TDD с Vue.js. В ней описываются первые шаги разработки через тестирование.

Оригинальная статья: Andrea StagiWriting a Vue component using TDD: a gentle introduction

В этом руководстве я хочу рассмотреть основные концепции разработки через тестирование (Test Driven Development – TDD). Для этого мы создадим простой компонент Vue с TypeScript, протестируем его с использованием Jest, и настроим coverage и Continuous Integration.

Введение

Разработка через тестирование (TDD) – это процесс разработки, при котором вы пишете тесты перед написанием кода. Сначала вы пишете тест, описывающий ожидаемое поведение, и запускаете его, гарантируя, что он потерпит неудачу, затем вы пишете минимальный код для его прохождения. После этого, если вам нужно, вы можете изменить код, чтобы сделать его правильным (делаете рефакторинг кода). Вы повторяете все эти шаги для каждой функции, которую хотите реализовать, пока не завершите свой проект. Этот процесс сразу заставляет разработчиков писать модульные тесты перед написанием кода, создавая надежный и модульный код.

Итак давай те напишем немного кода для создания компонента image placeholder, который будет извлекает изображения из LoremFlickr – простого сервиса для получения случайных изображений, использующий такие параметры, как ширина, высота, категории (значения, разделенные запятыми) и фильтры … внутри URL. Например что бы получить изображение 320×240 из Бразилии или Рио, нужно использовать следующую ссылку https://loremflickr.com/320/240/brazil,rio

Несмотря на то, что в LoremFlickr есть много опций, в этом руководстве мы сосредоточимся на разработке простого компонента для получения изображения из LoremFlickr только с использованием width и height и фильтрации по categories.

https://loremflickr.com/<width>/<height>/<categories>

Создание проекта

Используя Vue CLI создайте прокт vue-image-placeholder

vue create vue-image-placeholder

Виберите Manually select features и опции TypeScript и 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 как тестовый фреймворк.

🧹 Очистите папки assetscomponents  и содержимое файла  App.vue внутри src.

Написание первого теста

В папке tests/unit переименуйте файл example.spec.ts в imageplaceholder.spec.ts.

Мы ожидаем что наш компонент ImagePlaceholder  будет отображать тег <img> с src со свойствами widthheight и категории images.

<ImagePlaceholder width=500 height=250 images="dog" />

Компонент должен будет генерировать следующий HTML

<img src="https://loremflickr.com/500/250/dog">

Давайте напишем наш первый тест, чтобы проверить, отображает ли компонент ImagePlaceholder со свойствами width: 500, height: 200, images: ‘newyork’ img с src = https: //loremflickr.com/500/200/newyork.

import { shallowMount } from '@vue/test-utils'
import ImagePlaceholder from '@/ImagePlaceholder.vue'

describe('ImagePlaceholder.vue', () => {
  it('renders the correct url for New York images', () => {
    const wrapper = shallowMount(ImagePlaceholder, {
      propsData: { width: 500, height:200, images: 'newyork' }
    })
    expect(
      wrapper.findAll('img').at(0).attributes().src
    ).toEqual('https://loremflickr.com/500/200/newyork')
  })
})

Если мы попытаемся запустить тесты командой:

yarn test:unit

❌ Все тесты терпят неудачу, как и ожидалось, потому что компонент ImagePlaceholder пока еще не существует.

Чтобы тесты прошли успешно, нам нужно создать компонент ImagePlaceholder.vue

<template>
  <img :src="url">
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class ImagePlaceholder extends Vue {

  @Prop({required: true}) readonly width!: number
  @Prop({required: true}) readonly height!: number
  @Prop({required: true}) readonly images!: string

  get url() {
    return `https://loremflickr.com/${this.width}/${this.height}/${this.images}`;
  }

}
</script>

Теперь снова запустим тест yarn test:unit.

yarn run v1.19.2
$ vue-cli-service test:unit
 PASS  tests/unit/imageplaceholder.spec.ts
  ImagePlaceholder.vue
    ✓ renders the correct url for New York images (46ms)


Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.428s
Ran all test suites.
✨  Done in 2.40s.

✅ Ура! Теперь тесты запускаются без ошибок!

Мы только что создали минимальный компонент ImagePlaceholder с использованием TDD!
Используем его в действии: скопируйте и вставьте следующий код в main.ts

import Vue from 'vue'
import ImagePlaceholder from './ImagePlaceholder.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(
    ImagePlaceholder,
    {
      props : {
        width: 500,
        height:200,
        images: 'newyork'
      }
    }),
}).$mount('#app')

и запустим yarn serve!

Улучшение компонента с помощью TDD

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

Использовав таким образом компонент

<ImagePlaceholder width=500 height=200 />

должно получиться

<img src="https://loremflickr.com/500/200/random">

Первым делом изменим тест.

  
it('renders the correct url for Random images if not specified', () => {
    const wrapper = shallowMount(ImagePlaceholder, {
      propsData: { width: 500, height:200 }
    })
    expect(
      wrapper.findAll('img').at(0).attributes().src
    ).toEqual('https://loremflickr.com/500/200/random')
  })

❌ После запуска теста yarn test:unit: мы получим ошибку

  ● ImagePlaceholder.vue › renders the correct url for Random images if not specified

    expect(received).toEqual(expected) // deep equality

    Expected: "https://loremflickr.com/500/200/random"
    Received: "https://loremflickr.com/500/200/undefined"

Теперь изменим компонент: теперь prop images больше не требуется, и значение по умолчанию должно быть «random».

  //...
  @Prop({required: false, default: 'random'}) readonly images!: string
  //...

✅ Запустим тесты снова, и они пройдут, как ожидалось!

Как насчет поддержки квадратных изображений. Сделаем height равным width, если не указано иное?

  
it('renders a square image if height is not specified', () => {
    const wrapper = shallowMount(ImagePlaceholder, {
      propsData: { width: 500 }
    })
    expect(
      wrapper.findAll('img').at(0).attributes().src
    ).toEqual('https://loremflickr.com/500/500/random')
  })

И напишем минимальный код, чтобы он прошел.

@Component
export default class ImagePlaceholder extends Vue {

  @Prop({required: true}) readonly width!: number
  @Prop({required: false}) readonly height!: number
  @Prop({required: false, default: 'random'}) readonly images!: string

  get url(): string {
    let height = this.height;
    if (!this.height) {
      height = this.width;
    }
    return `https://loremflickr.com/${this.width}/${height}/${this.images}`
  }

}

✅ Тесты проходят!

Сейчас у нас есть тест для этой новой функции и минимальный код для ее прохождения. Теперь мы можем заняться рефакторингом! 👨🏻‍💻

export default class ImagePlaceholder extends Vue {

  @Prop({required: true}) readonly width!: number
  @Prop({required: false}) readonly height!: number
  @Prop({required: false, default: 'random'}) readonly images!: string

  get url(): string {
    return `https://loremflickr.com/${this.width}/${this.height || this.width}/${this.images}`;
  }

}

✅ Тесты снова пройдены! Мы успешно провели рефакторинг кода, не влияя на результат!

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

Вы можете найти весь код проекта на GitHub

Добавление покрытия кода -coverage

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

Jest может генерировать покрытие кода легко без внешних инструментов. Чтобы включить эту функцию, добавьте несколько строк в файл jest.config.json, указав, какие файлы будут покрыты

module.exports = {
  preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
  collectCoverage: true,
  collectCoverageFrom: ["src/**/*.vue", "!**/node_modules/**"]
}

Запустите снова yarn test:unit, и вы получите отчет о покрытии до результатов тестирования.

----------------------|----------|----------|----------|----------|-------------------|
File                  |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------------------|----------|----------|----------|----------|-------------------|
All files             |      100 |      100 |      100 |      100 |                   |
 ImagePlaceholder.vue |      100 |      100 |      100 |      100 |                   |
----------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        5.688s
Ran all test suites.
✨  Done in 8.70s.

⚠️ На забудьте добавит папку /coverage в .gitignore.

Непрерывная интеграция

Непрерывная интеграция (CI) – это практика разработки, при которой разработчики часто интегрируют код в общий репозиторий, предпочтительно несколько раз в день. Каждая интеграция может быть проверена с помощью автоматической сборки и автоматических тестов. Целью является создание более стабильного программного обеспечения путем разработки и тестирования с меньшими фазами исправления багов. Вот где появляется платформа непрерывной интеграции, такая как TravisCI.

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

TravisCI и Codecov интегрированы с Github, вам просто нужно зарегистрироваться и добавить проект в сервисы. Внутри вашего проекта вам нужно будет создать специальный файл .travis.yml, чтобы активировать CI и сказать TravisCI, как выполнять сборки:

language: node_js
node_js:
  - 10
before_script:
  - yarn add codecov
script:
  - yarn test:unit
after_script:
  codecov

Кратное описание файла:

  • настройка окружения (node_js 10)
  • установка зависимостей (секция before_script)
  • выполнение тестов с покрытием (секция script)
  • отправка отчета по покрытию в Codecov(секция after_script)

Установка

Теперь, когда у нас есть готовый компонент, нам нужно настроить процесс сборки. В вашем файле package.json измените секцию build и удалите строку с serve.

  "scripts": {
    "build": "vue-cli-service build --target lib --name vue-image-placeholder src/main.ts",
    "test:unit": "vue-cli-service test:unit",
    "lint": "vue-cli-service lint"
  },

С –target lib файл main.ts должен быть соответствующим образом изменен для экспорта вашего компонента

import ImagePlaceholder from './ImagePlaceholder.vue'

export default ImagePlaceholder

Добавим папку types с файлом index.d.ts, содержащим

declare module 'vue-image-placeholder' {
  const placeholder: any;
  export default placeholder;
}

Добавим ссылки main и typings в package.json

  "main": "./dist/vue-image-placeholder.common.js",
  "typings": "types/index.d.ts",

Нам так же нужно запретить автоматический внедрение polyfill  в babel.config.js

module.exports = {
  presets: [
    ['@vue/app', {
      useBuiltIns: false
    }]
  ]
}

И удалим test файлы из секции “include” файла tsconfig.json.

Далее осталось запустить команду сборки:

yarn build
⠦  Building for production as library (commonjs,umd,umd-min)...

 DONE  Compiled successfully in 20857ms                                                               11:37:47 PM

  File                                     Size             Gzipped

  dist/vue-image-placeholder.umd.min.js    8.50 KiB         3.16 KiB
  dist/vue-image-placeholder.umd.js        42.33 KiB        11.76 KiB
  dist/vue-image-placeholder.common.js     41.84 KiB        11.60 KiB

📦 Сборка готова!

Чтобы поиграть с ней, установите проект vue-image-placeholder в какое-нибудь другое локально приложение, используя команду

yarn add ../vue-image-placeholder

и используйте компонент следующим образом

<template>
  <div id="app">
    <h1>Welcome to the Vue Image Placeholder demo!</h1>
    <ImagePlaceholder width=500 />
  </div>
</template>

<script>
import ImagePlaceholder from 'vue-image-placeholder';

export default {
  name: 'App',
  components: {
    ImagePlaceholder
  }
}
</script>
Final result

✨ Здесь вы найдете официальный репозитарий vue-image-placeholder.

Была ли вам полезна эта статья?
[4 / 5]

Spread the love
Подписаться
Уведомление о
guest
0 Комментарий
Inline Feedbacks
View all comments