В этой статье я покажу вам еще один способ тестирования вашего UI-приложения с помощью Puppeteer.

Хотя я лично использую Angular в этой статье, все примеры Puppeteer и сообщения действительны и для других UI-фреймворков.
Он просто делает вид, что ваш пользовательский интерфейс можно запустить в Chrome / Chromium.

Тестовая пирамида

Чтобы получить обзор, в котором Puppeteer может помочь вам в тестировании вашего UI-приложения, давайте кратко рассмотрим хорошо известную пирамиду тестирования.

Сообщение обычно такое:

  • Покройте большую часть своего приложения модульными тестами
  • Напишите меньшие интеграционные тесты

Только если это еще не покрыто предыдущими тестами, начните писать некоторые тесты E2E / UI.

Я считаю, что приведенная выше тестовая пирамида имеет следующие недостатки:

  1. Обычно они не делают разницы между тестами UI и E2E. Но для меня это разные вещи.
  2. Слишком много внимания уделяется написанию модульных тестов. Но есть и другие способы более разумного тестирования вашего приложения с меньшими усилиями.

Разница между тестами E2E и UI

E2E-тесты означают полное тестирование развернутой среды, включая интерфейс, бэкэнд и базу данных. Вы, конечно, можете (и часто должны) немного обмануть, используя mocks, особенно для сторонних сервисов, не являющихся частью вашей развернутой среды.
Для меня не имеет значения, тестируете ли вы изменения в базе данных, напрямую обращаясь к и запрашивая базу данных, или проверяя правильные значения, которые были возвращены через серверные службы или отображены пользовательским интерфейсом.
В отличие от тестов E2E, тест пользовательского интерфейса просто тестирует пользовательский интерфейс. Их можно запускать только в части пользовательского интерфейса вашего приложения при отсутствии какой-либо серверной системы.

Недостатки модульного тестирования

Для Angular наиболее распространенным способом модульного тестирования вашего приложения является Angular Unit Testing. Обычно у вас есть отдельный тестовый файл для каждого компонента / сервиса angular. Другие фреймворки пользовательского интерфейса следуют аналогичным подходам. Преимущества:

  • Все тесты инкапсулируются с соответствующими компонентами / сервисами. Такой уровень изоляции упрощает управление тестами. У вас нет побочных эффектов от других частей вашего приложения, и вы можете сосредоточиться только на небольшой части.
  • Тесты надежны, то есть они всегда показывают одни и те же результаты независимо от того, где они выполняются (обычно локально или на платформе CI).
  • Скорость исполнения обычно очень высока. Вы получаете быструю обратную связь, и они часто выполняются перед проверкой кода.

Таким образом, тесты из-за того, что они являются модульными тестами, вряд ли согласованы с кодом. В этом нет ничего плохого, если в вашем проекте действительно есть сложные части или функции, которые нужно тестировать явно.
Но почему бы всегда не писать модульные тесты?

  • Написание модульных тестов обычно занимает некоторое время. Вы должны имитировать многие аспекты вашего приложения, такие как другие компоненты, службы и т. Д., Которые использует ваша SUT. Я вижу модульные тесты, которые имитируют 90% кода, просто чтобы протестировать оставшиеся 10%.
    Постоянная синхронизация этой имитирующей инфраструктуры с вашим кодом во время ваших проектов требует значительного количества времени и усилий. Для большинства разработчиков, с которыми я столкнулся, это обычно самая нежелательная часть работы.
  • В результате такого жесткого согласования кода рефакторинг становится непонятным. Честно говоря, большинство тестов терпят неудачу из-за рефакторинга существующего кода или разработки новых функций (забыли макет, да?). Вы должны их переписывать, изменять, настраивать используемые mocks и т. Д. Это приводит к следующему недостатку.
  • Кто скажет вам после рефакторинга кода, что приложение работает как раньше, когда вы изменяете большие части тестов? Какую ценность они для вас придают?

Я думаю, что есть способ объединить преимущества без излишних недостатков, показанных выше, при использовании тестов пользовательского интерфейса.

Кукольник спешит на помощь

Puppeteer - это библиотека Node, которая предоставляет высокоуровневый API для управления Chromium или Chrome по протоколу DevTools.

Puppeteer часто рассматривается как эквивалент Selenium, который более стабилен, быстрее, имеет больший контроль над браузером, но полагается на Chrome.
Но особенно Point Control the Browser позволяет использовать Puppeteer в сценариях, недоступных для Selenium: Тесты пользовательского интерфейса.

Недостаток головоломки - это способность кукловодов имитировать запросы браузера и отвечать фальшивыми данными. Это помогает запускать тесты пользовательского интерфейса независимо от серверной части.

Чтобы имитировать запросы, вы просто:

await page.setRequestInterception(true);
page.on('request', async request => {

  if (request.url().endsWith("/api/backendcall")) {
    await request.respond({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ key: 'value' })
    });
  } else {
    await request.continue();
  }
});

Теперь вы можете запускать свое приложение в отсутствие какой-либо серверной системы и можете контролировать ответы на запросы, которые отправляет ваш пользовательский интерфейс.
Это позволяет полностью тестировать пользовательский интерфейс вашего приложения с помощью Puppeteer. В качестве фреймворка для тестирования я использовал Jest.

Сравнение UI-тестов Puppeteers с модульными тестами

  • Тесты пользовательского интерфейса написаны на более высоком уровне, чем модульные тесты. Это делает их более устойчивыми к рефакторингу. Вы можете просто поменять текущую структуру пользовательского интерфейса, например, Переходите с Angular на Vue или Flex, и тесты по-прежнему будут работать, делая вид, что используемые вами селекторы остаются стабильными.
  • Поэтому вы можете положиться на свой набор тестов после завершения рефакторинга кода. Если все тесты запускаются зеленым, значит, приложение ведет себя как раньше.
  • Вам не нужно писать и поддерживать фиктивную инфраструктуру. В большинстве случаев достаточно просто имитировать XHR-запрос в браузере к серверным службам. Но с Puppeteer вы даже можете контролировать больше частей своего браузера, если этого недостаточно.
  • UI-тесты, написанные на Puppeteer, обычно очень стабильны и быстры, сравнимы с модульными тестами.

Запустите Puppeteer на CI-сервере (например, Jenkins)

Чтобы запустить сеанс Jest / Puppeteer на вашем любимом сервере пользовательского интерфейса, у вас есть несколько возможностей.
Для меня имело смысл сначала создать проект, чтобы все статическое содержимое было в одной папке. В случае Angular npm run build или ng serve выполнят трюк, оставив все перенесенные исходные файлы и другие файлы в папку /dist .

Имея все свои требуемые файлы, готовые к доставке, в папке /dist , вы можете настроить веб-сервер по вашему выбору для обслуживания статического содержимого, чтобы Jest / Puppeteer запускал свои тесты поверх него.

Я использую экспресс-сервер. Трудность состоит в том, чтобы на самом деле перестать экспрессировать, но есть несколько библиотек, которые могут вам помочь. Stoppable использует оболочку вокруг экземпляра HttpServer для реализации функции close ().

Чтобы установить экспресс (с Stoppable), просто выполните следующее.

npm i express stoppable --save-dev

Теперь вам нужно настроить и запустить свой экземпляр Express из кода.

const express = require('express');
const stoppable = require('stoppable');

async function createServer(port) {
  const app = express();
  app.use(express.static('dist'));
  return new Promise((resolve, reject) => {
    const server = app.listen(port, () => resolve(stoppable(server)));
  });
}

Чтобы использовать функцию createServer() , вы можете просто повесить Jests beforeAll() и afterAll() функции обратного вызова.

let server;

beforeAll(async () => {
  server = await createServer(4200);
});

afterAll(async () => {
  await server.close();
});

Заключение и предложения

Angular-тестирование - это здорово, и я долгое время был его большим поклонником. Но у него есть свои недостатки, и я часто сталкиваюсь с тем, что проекты полагаются на большую имитацию инфраструктуры, чтобы изолировать свои компоненты и сделать их пригодными для модульного тестирования. Сами по себе макеты должны поддерживаться и синхронизироваться с текущей реализацией.
Это затрудняет рефакторинг кода, и вы не можете полагаться на свой тест после рефакторинга кода. Они просто тестируют текущий рабочий снимок вашего кода.

Большая часть возможностей тестирования теперь может быть выполнена с помощью тестирования пользовательского интерфейса с помощью Puppeteer. Вы тестируете свой код на более высоком уровне, поскольку он более устойчив к рефакторингу.
Конечно, вы теряете возможность явно проверять функции белого ящика. Но никто не разрешает вам их писать.

Итак, я предлагаю быть прагматичным. Тщательно настройте свое тестирование на следующие аспекты:

  • Насколько велика команда, которая на самом деле пишет UI-часть вашего приложения?
  • Какое влияние и количество пользователей имеет приложение?
  • Как быстро вы разработаете свое приложение и достигнете отметки? Вам нужно почти 100% покрытие модульным тестом, если вы не уверены, выживет ли ваше приложение (подход MVP)?
  • Каков TTL вашего приложения в современном быстро меняющемся мире?
  • Насколько реорганизован код / ​​стабильность кодовой базы (- ›Больше UI-тестов, меньше юнит-тестов)?

Поэтому я не люблю обобщать тестовую пирамиду для каждого проекта.