Мы собираемся использовать реальное приложение, хакерский клон новостей, созданное Эваном, я просто раздвоил его, так как не хочу, чтобы изменения в нем нарушали эту статью, вот разветвленная версия . На момент написания этой статьи в этом проекте нет тестов, и нам хорошо научиться писать тесты, мы также собираемся создать конвейер CI / CD (в основном CI) с CircleCI, чтобы тесты запускались автоматически. на случай любых изменений в нашем репозитории git.
Вот темы, которые мы рассмотрим:

  1. Модульные тесты с Jest.
  2. Моментальные тесты с Jest.
  3. Настройка и понимание покрытия.
  4. Почему некоторые компоненты сложнее тестировать и как написать тестируемый код.
  5. E2E-тесты с TestCafe.
  6. Запись тестов E2E с помощью TestCafe Studio.
  7. Мультибраузерное тестирование с TestCafe.
  8. Настройка CI / CD с помощью CircleCI.

Зачем писать тесты?

Многие разработчики не любят писать тесты, но мне это нравится, тесты делают ваш код более устойчивым и подверженным ошибкам, большинство нежелательных изменений или ошибок могут быть легко обнаружены тестами до того, как они нанесут ущерб в производстве, и после настройки. В среде CI / CD гораздо проще проверять запросы на вытягивание и изменения.

Настройка шутки

Если вы используете vue-cli, вы можете либо начать новый проект с пресетом jest, либо добавить jest с помощью vue add @ vue / unit-jest, но поскольку В проекте нет vue-cli, мы собираемся настроить его с нуля.

Шаг 1: клонируйте репозиторий:

# with ssh
git clone [email protected]:liron-navon/vue-hackernews-2.0.git
# or with https
it clone https://github.com/liron-navon/vue-hackernews-2.0.git
# install dependencies
npm install

Шаг 2: добавляем наши зависимости для модульных тестов:

npm install --save-dev jest vue-jest jest-serializer-vue jest-transform-stub @vue/test-utils

Шаг 3: добавьте сценарий в package.json, назначьте переменной окружения NODE_ENV значение test, используя пакет cross-env, и вызовите jest.

"scripts": {
  "test:unit": "cross-env NODE_ENV=test jest",
  ... the other scripts
}

Шаг 4: настройте jest, создайте файл с именем jest.config.js и поместите его внутрь:

Шаг 5: мы используем Babel, поэтому нам нужно настроить среду узла при тестировании, это важно для шутки, измените содержимое .babelrc на это:

Шаг 6. Добавьте наш собственный каталог для тестов:
Добавьте каталог под названием tests, внутри него добавьте еще один под названием unit, а внутри каталог под названием компоненты. До этого момента - все будет готово для вас, если вы воспользуетесь плагином cli.

Модульные тесты с Jest

Модульные тесты просты, они предназначены для проверки функциональности мельчайших строительных камней, обычно называемых «тупыми компонентами», в нашем случае это компоненты в / components, хотя они не совсем тупые и имеют доступ к vuex и другим вещам, которые усложни их тестирование, но мы тоже пройдем через это 😁.

В компонентах создайте файл с именем Spinner.spec.js и напишите наш первый тест, используя @ vue / test-utils, давайте добавим этот тест:

И запустим наш скрипт с помощью «npm run test: unit». Вот и все, вы должны увидеть, что тест пройден:

PASS  tests/unit/components/spinner.spec.js
  Spinner
    ✓ has svg (16ms)
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.41s
Ran all test suites.

Теперь давайте добавим еще один тест для более интересного компонента, такого как «/components/Comment.vue», он использует внутреннее хранилище и фильтры, которые зарегистрированы глобально, поэтому нам нужно импортировать и имитировать их 🤔.
Теперь способ имитировать их довольно прост: мы можем создать наш собственный локальный экземпляр vue и смонтировать на нем компонент с хранилищем и маршрутизатором.
Давайте создадим новый файл для настройки такой вещи. , мы можем поместить его в /tests/unit/test-utils/mountWithPlugins.js.

Теперь мы можем использовать это для написания теста для компонента комментария, он ожидает, что в магазине будут конкретные данные, описывающие комментарий, мы можем легко смоделировать это и передать некоторые свойства компоненту, создав файл с именем «Комментарий. spec.js »и скопируйте туда этот тест:

Повторный запуск «npm run test: unit» должен привести к прохождению 4 тестов 🎉

PASS  tests/unit/components/spinner.spec.js
PASS  tests/unit/components/Comment.spec.js
Test Suites: 2 passed, 2 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        3.064s
Ran all test suites.

Мы можем использовать эту технику для тестирования каждого компонента в каталоге компонентов, к сожалению, поскольку Эван использует firebase с некоторой добавленной черной магией - это очень затрудняет модульное тестирование представлений 😓, нам придется имитировать множество других вещей, чтобы работать с ним. , к счастью, мы также можем писать тесты, которые реализуют взаимодействие, и нам не нужно ничего имитировать для них! (E2E-тесты), хотя всегда рекомендуется иметь как минимум dev и production среду для сервера и баз данных, которые мы используем, чтобы мы не засоряли их тестами. данные.

Тестирование снимков с помощью Jest

Тесты моментальных снимков довольно просты, и мы также можем выполнять их с помощью Jest. Тесты моментальных снимков проверяют, не меняется ли структура нашего компонента неожиданно.
Чтобы упростить задачу, давайте создадим каталог под названием snapshots в каталоге tests и добавьте файл с именем «ProgressBar.spec.js», вы можете использовать этот тест:

И мы можем написать еще один тест для Item.vue, мы можем назвать его «Item.spec.js», нам нужно передать ему свойство с именем Item, иначе он не будет отображаться, также он использует фильтры «timeAgo» и «host», чтобы мы могли повторно использовать нашу функцию «mountWithPlugins», обратите внимание, что Item может включать свойство «time», я проигнорирую его, так как он будет заставлять наш снимок каждый раз выглядеть по-разному, и мы этого хотим. быть воспроизводимым и предсказуемым.

Видеть? тесты моментальных снимков очень просты и интуитивно понятны, после повторного запуска «npm run test: unit» jest сгенерирует каталог рядом с нашим тестом с именем __snapshots__, где он сохранит наше прошлое. снимки и убедитесь, что они одинаковы. Если вы хотите изменить компонент, вы должны явно удалить этот каталог и создать новые снимки, что можно сделать, запустив:

npm run test:unit -- -u

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

PASS  tests/unit/components/spinner.spec.js
PASS  tests/snapshots/components/ProgressBar.spec.js
PASS  tests/snapshots/components/Item.spec.js
PASS  tests/unit/components/Comment.spec.js
Test Suites: 4 passed, 4 total
Tests:       6 passed, 6 total
Snapshots:   2 passed, 2 total
Time:        3.636s
Ran all test suites.

Настройка покрытия

Покрытие - это отчет, который определяет, какая часть вашего кода покрывается написанными нами тестами, и сообщает нам, какие еще тесты нам нужно написать. Мы установим покрытие только для шуток. Чтобы включить покрытие для тестов, мы можем просто добавить в наш jest.config.js еще несколько строк:

module.exports = {
    ....
    // we should collect coverage
    collectCoverage: true,
    // set a directory for coverage cache
    coverageDirectory: '<rootDir>/tests/__coverage__',
    // set patterns to ignore for coverage 
    coveragePathIgnorePatterns: ['/node_modules/']
};

Мы говорим шутке собрать отчет о покрытии, поместить данные о покрытии и кэш в каталог с именем __coverage__ при тестировании, и мы можем сказать ему, чтобы он игнорировал некоторые файлы или каталоги, по умолчанию jest игнорирует 'node_modules', но я добавил его в покажите эту опцию.

Теперь, когда мы снова запускаем наши модульные тесты с помощью «npm run test: unit», мы должны увидеть таблицу, в первый раз это займет больше времени, поскольку jest должен будет генерировать это файлы кеша. Давайте посмотрим на информацию о покрытии, которую мы получаем:

Мы можем использовать эту таблицу для анализа результатов тестов, проведенных с тех пор, как мы начали проект без тестов, это хорошее улучшение.
Jest использует Стамбул под капотом для создания этого отчета, вот объяснение для тех столбцы:

  • Файл: сообщает нам, какой файл / каталог мы смотрим.
  • Stmts, сокращение от Покрытие операторов: каждый оператор в программе был выполнен?
  • Ветвь: выполнялась ли каждая ветвь структуры управления, например операторы if / else / elseif?
  • Функции - это сокращение от Функции: вызывалась ли каждая функция?
  • Строки: каждая исполняемая строка в исходном файле была выполнена?
  • Непокрытые строки №. Сообщает нам, какие строки не были охвачены, поэтому мы знаем, что для них тоже нужно написать тесты.

Здесь три цвета: зеленый означает хорошее покрытие, желтый означает, что его следует улучшить, а красный означает, что тестирование практически отсутствует. покрытие.

Мы можем взглянуть на Все файлы (первая строка), где у нас покрыто 22,56% строк, не очень хорошо, но это лучше, чем ничего, и мы только что написали наши первые тесты 😎, вы можете улучшите это, написав больше тестов для каждого возможного компонента, но в этом конкретном проекте есть файлы, которые труднее тестировать, и мы расскажем почему в следующем разделе.

Почему некоторые компоненты сложнее тестировать и как писать тестируемый код.

Не все компоненты были написаны как равные, большинство компонентов, которые сложнее протестировать, находятся в каталоге src / views, их труднее тестировать, поскольку они генерируют мутации и действия в хранилище Vuex, которое, в свою очередь, запускается. функционирует через firebase и выполняет сумасшедшее ручное кэширование (в src / api / index.js).
Чтобы протестировать их, нам понадобится простой способ имитировать API, к сожалению, способ, которым файл api.js написан прямо сейчас, что усложняет задачу, а насмешка firebase - большая проблема, если мы не знаем точные структуры данных, которые мы ожидаем получить от него (и именно поэтому я всегда люблю использовать систему типов, например Typescript).
Итак, давайте определим некоторые правила, которые позволят нам писать больше тестируемых компонентов, что позволит нам иметь лучшее покрытие и более надежный код.

Разделяйте глупые и умные компоненты:
Мы видим, что авторы почти сделали это, но не совсем так, компоненты в каталоге компонентов по-прежнему имеют доступ к плагину $ store, что делает имитацию данных Более сложный, хорошая практика состоит в том, чтобы держать их настолько глупыми, насколько они могут, передавать им данные, которые им нужны в качестве реквизита, и позволять им использовать эти реквизиты, как им заблагорассудится - таким образом нам не нужно беспокоиться о высокоуровневых реализациях, когда написание наших тестов.

Отделите логику от компонентов и напишите тестируемые функции:
Авторы хорошо поработали над тем, чтобы максимально отделить логику от компонентов, мы можем посмотреть / src / util / filters. js, давайте посмотрим на timeAgo, оператор (~~) встречается реже, но пока вы можете относиться к нему как к Math.floor.

export function timeAgo (time) {
  const between = Date.now() / 1000 - Number(time)
  if (between < 3600) {
    return pluralize(~~(between / 60), ' minute')
  } else if (between < 86400) {
    return pluralize(~~(between / 3600), ' hour')
  } else {
    return pluralize(~~(between / 86400), ' day')
  }
}

Мы можем видеть, что мы можем принять новую дату и что мы получаем Date.now (), переменную и счетчик из нее, мы можем улучшить эту функцию несколькими способами: сделать ее более понятной (оператор ~~ очень сбивает с толку и будет разбиваться по числам, превышающим 2147483647 (32 бит)), если мы просто будем использовать текущую дату без деления, она сломается 🤷‍♀️, и мы можем передать необязательную переменную времени, это позволит нам сохранить интерфейс функции таким же (принимая одну переменную), добавляя функциональность, которая поможет нам ее протестировать, мы пока проигнорируем эти «ВОЛШЕБНЫЕ НОМЕРА», но для ясности рекомендуется дать им имена и рефакторинг, вот новая функция:

export function timeAgo (time, fromTime = null) {
    const between = (fromTime || Date.now()) / 1000 - Number(time)
    if (between < 3600) {
        return pluralize(Math.floor(between / 60) || 0, ' minute')
    } else if (between < 86400) {
        return pluralize(Math.floor(between / 3600) || 0, ' hour')
    } else {
        return pluralize(Math.floor(between / 86400) || 0, ' day')
    }
}

И с этим его намного проще проверить, вот тест для нашего фильтра timeAgo:

А теперь покрытие фильтров увеличилось с 84,62 строки до 92,31 строки!

Имитируйте все!
Чтобы мы могли полностью протестировать магазин, нам нужно имитировать, макетирование - это процесс передачи фальшивых (фальшивых) данных вместо реальных данных, нам нужно у вас есть mock firebase, я не буду описывать это здесь, так как это громоздко, но тестировать обычные API для отдыха очень просто, мы будем использовать fetch-utils, небольшую библиотеку, которую я написал сам, она будет охватывать большинство простых случаев для издевательства API для отдыха:

допустим, наш компонент / функция / хранилище использует выборку следующим образом:

fetch('/api/users')
  .then((response) => response.json())
  .then((json) => {
    this.users = json;
  });

мы можем очень легко смоделировать это в одном из наших тестов вот так:

import { Mocker } from 'fetch-utilities';

// setting up the mocker
const mocker = new Mocker(window.fetch);

// binds mocker.fetch to the window object
mocker.bindToWindow();

mocker.get('/api/users', [
  { name: 'john', id: 1, age: 22}
])

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

E2E-тесты с TestCafe

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

npm install --save-dev testcafe

И добавляем скрипт в наш package.json:

"scripts": {
  "test:e2e": "testcafe chrome tests/e2e/**/*.e2e.js",
  ... the other scripts
}

Теперь нам нужно написать наш первый тест, создать новый каталог с именем e2e и добавить файл с именем home.e2e.js, мы сейчас выполняем очень короткие и простые тесты. поскольку веб-приложение не имеет слишком большой функциональности, мы можем проверить, что навигация работает правильно, каждый из тестов тестового кафе предоставит нам тестовый контекст (t), и мы можем использовать его для проведения нашего теста, мы также можем создать ClientFunction, функцию, которую можно запустить в браузере.

На этом мы закончили. Чтобы TestCafe нашел наше веб-приложение, мы должны запустить его в другом терминале, мы можем сделать это с помощью «npm run dev», а затем запустить «npm run test: e2e »в другом терминале приведет к открытию страницы Chrome и запуску реальных тестов, мы можем изменить это на запуск в режиме без заголовка, изменив сценарий в package.json, в режиме без заголовка мы не увидим браузер, и тесты будут быстрее.

"scripts": {
  "test:e2e": "testcafe 'chrome:headless' tests/e2e/**/*.e2e.js",
  ... the other scripts
}

Тест должен пройти легко, теперь вы сможете написать больше тестов.

Запись тестов E2E с помощью TestCafe Studio

Написание тестов E2E требует определенного количества навыков, но большинство QA-инженеров не хотят писать код или, по крайней мере, хотят писать как можно меньше кода - здесь на помощь приходит запись теста, TestCafe сгенерировал инструмент для автоматизация процесса написания тестов под названием TestCafe Stodio.
Чтобы использовать TCS (так я буду называть его сейчас), нам сначала нужно его скачать, вы можете сделать это отсюда. < br /> После загрузки откройте каталог вашего проекта и создайте подкаталог в tests, который называется test-cafe-studio, здесь мы будем хранить наши тесты, затем откройте TCS и выберите каталог. , поместите URL-адрес нашего веб-приложения на место и нажмите Начать запись.

Откроется браузер, просто поиграйте в нем, нажмите что-то вроде навигации, если бы у нас был какой-либо ввод, вы могли бы ввести текст и многое другое.
Когда вы будете довольны, просто выйдите из браузера или нажмите «Остановить запись в TCS», вы должно получиться что-то вроде этого:

Из TCS в правом меню вы можете добавлять действия браузера, утверждения, утверждения и т. Д. Когда вы будете довольны результатом, TCS сохранит их как файлы .testcafe, теперь вы можете использовать эти тестовые файлы для запуска тестов, мы можем изменить команду в package.json, нам просто нужно добавить еще один шаблон glob, чтобы он соответствовал файлам .testcafe.

"test:e2e": "testcafe 'chrome:headless' tests/e2e/**/*.e2e.js tests/test-cafe-studio/**/*.testcafe"

Теперь QA и другие члены команды могут записывать тесты, добавлять утверждения, и они должны работать как по волшебству! 🧙‍♂️

Тестирование в нескольких браузерах

Благодаря TestCafe, это очень короткий раздел, нам просто нужно изменить скрипт в package.json:

"test:e2e": "testcafe 'chrome:headless,firefox:headless' tests/e2e/**/*.e2e.js tests/test-cafe-studio/**/*.testcafe"

Обратите внимание, что я могу просто указать через запятую список браузеров, поддерживаемых TestCafe:

'safari,edge,chrome:headless,firefox:headless'

Обратите внимание, что на нашем компьютере должны быть браузеры для TestCafe, чтобы использовать их, поэтому вам может потребоваться несколько сценариев, которые будут запускаться на нескольких машинах, некоторые для OSX с Safari, а некоторые для Windows с IE / Edge.
Эти тесты будут проверять удобство использования продукта, но если вы хотите также проверить внешний вид, вы должны сделать скриншоты и сравнить их в разных браузерах.
Для следующего шага оставим 'chrome: headless, firefox: headless ', поскольку CircleCI предоставляет нам их со своими образами Docker.

CI / CD с CircleCI

CI / CD означает непрерывную интеграцию и непрерывную D развертывание,
обычно используются вместе для обозначения конвейера, который запускает тесты, а затем развертывает код, вы можете прочитать больше о точных определениях здесь, но я просто скажу, что CI - это часть, которая запускает наши тесты, а CD - это Часть, которая развертывает проект на сервере или где бы вы ни хотели его использовать, мы сосредоточимся только на части CI.

Чтобы создать нашу среду CI, мы будем использовать CircleCI, вам нужно перейти на их веб-сайт и войти в систему с помощью Bitbucket или GitHub, CircleCI покажет вам список ваших репозиториев, выберите репозиторий, который вы хотите настроить автоматически. тесты и внизу нажмите Follow, CircleCI будет прослушивать изменения, внесенные в репозиторий, и запускать тесты для каждого изменения.

Теперь следующий шаг - настроить файл конфигурации для использования CircleCI, создать каталог с именем .circleci и внутри него файл с именем config.yml.
Вставьте это определение в config.yml, оно определяет, какой докер-контейнер CircleCI следует использовать (я использую узел 11.8, так как это версия, которую я использую локально, вам может понадобиться более низкая версия для некоторых проектов) и какие шаги нужно сделать, чтобы протестируйте наш проект, посмотрите на название каждого шага, чтобы понять, что он делает:

Вот и все! Теперь вы можете добавить файлы, отправить их в репозиторий и увидеть, что тест прошел, вы должны перейти на вкладку заданий в CircleCI и увидеть что-то вроде этого:

Заключение

Тестирование - это весело, просто и всегда полезно. Здесь, в Clockwork, мы очень стараемся производить качественное программное обеспечение и тестируем большинство наших продуктов.

Если вы хотите присоединиться к нам, проверьте открытые вакансии на https://clockwork.homerun.co/.