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

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

Недавно наша команда получила код от нового клиента, и нам было поручено после небольшого рефакторинга быстро переходить к реализации нового функционала. Мы понимали, что могли бы улучшить код, перенеся клиентские стили на Sass, и это упростило бы нам поддержку в будущем.

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

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

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

На этот раз было решено построить визуально регрессионный набор тестов.

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

Это может показаться немного нелогичным. Мы изменяем CSS, потому что мы сами хотим, чтобы наш интерфейс выглядел как-то иначе. Проще говоря, зачем нам строить какой-то процесс, который будет уведомлять нас каждый раз, что мы что-то сломали, когда мы что-то изменяем в наших стилях?

Если вы изменяете свои клиентские стили или работаете в команде, то довольно легко сделать изменения в CSS, которое, как вы думаете, повлияло только на 1 компонент. И лишь позже вы обнаруживаете, что ваши изменения сломали что-то на другой странице.

Чтобы понять полезность визуальных регрессионных тестов, важно понять, с чем человек плохо справляется.

Человек против Машины


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

Существует даже игра на основе этого. Вы помните картинки из серии «найдите несколько отличий»?



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

Один важный феномен, который нужно здесь рассмотреть — это слепота к изменениям.

Слепота к изменениям


Исследования в этой области начались еще в 1970 году. В 1996 году George McConkie и Christopher Currie в университете Урбана-Шампейн штата Иллинойс провели ряд исследований, которые вызвали значительный интерес к этой области.

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

В исследовании McConkie & Currie обнаружили, что в некоторых случаях пятая часть всех изменений может пройти незамеченными. Это видео представляет собой прекрасный пример того, насколько много изменений может быть пропущено, если вы не акцентируете на них внимание.

Инструменты


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

Я выбрал PhantomCSS как инструмент для визуального тестирования регрессий. Для этого было несколько причин.

Во-первых, он имеет относительно большое и активное сообщество на GitHub. Когда дело доходит до open source, я всегда проверяю, что инструмент или библиотека активно развивается. Работа с замороженными open source проектами может довольно быстро стать обузой.

Во-вторых, PhantomCSS имеет удобный Grunt плагин, что позволяет ему легко внедриться в мой существующий процесс разработки.

Ядро PhantomCSS — это комбинация трех ключевых компонентов:

  • PhantomJS или SlimerJS — примитивный браузер. PhantomJS — это примитивная версия WebKit, в то время как Slimer — это движок Gecko, используемый в Firefox.
  • CasperJS — Casper — это JavaScript утилита для навигации и тестирования. Он позволяет нам определить набор действий, которые происходят внутри нашего примитивного браузера.
  • ResembleJS — это JavaScript/HTML5 библиотека для сравнений изображений. Она будет искать разницу между результатами наших тестов и базовыми результатами, и предупредит о различиях между ними.


И, как я уже упоминал, мы будем использовать Grunt для запуска наших тестов.

Реализация


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

Настройка Grunt


Во-первых, для запуска тестов нам потребуется Grunt, поэтому убедитесь, что он установлен. Для этого, в командной строке введите
 $ cd /path/to/your-site

После чего откройте Gruntfile проекта, загрузите задачу PhantomCSS, и добавьте ее в grunt.initConfig():

grunt.loadNpmTasks('@micahgodbolt/grunt-phantomcss');

grunt.initConfig({
  phantomcss: {
    desktop: {
      options: {
        screenshots: 'baselines/desktop',
        results: 'results/desktop',
        viewportSize: [1280, 800]
      },
      src: [
        'tests/phantomcss/start.js',
        'tests/phantomcss/*-test.js'
      ]
    }
  }
});


Тестирование на различных устройствах


Мне нравится использовать Sass MQ для работы с различными устройствами. Этот подход имеет дополнительное преимущество: он возвращает список всех моих устройств, которые я могу легко использовать для своих тестов.

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

С grunt-phantomcss мы можем определить набор тестов для запуска на различных устройствах и, в качестве дополнительного бонуса, настроить сохранение их в разных папках.

Для соблюдения семантики и порядка, я стараюсь называть подзадачи в соответствии с устройствами в Sass MQ.

Пример:

grunt.initConfig( {
  pkg: grunt.file.readJSON('package.json'),
  phantomcss: {
    desktop: {
      options: {
        screenshots: 'baselines/desktop',
        results: 'results/desktop',
        viewportSize: [1024, 768]
      },
      src: [
        'tests/phantomcss/start.js',
        'tests/phantomcss/*-test.js'
      ]
    },
    mobile: {
      options: {
        screenshots: 'baselines/mobile',
        results: 'results/mobile',
        viewportSize: [320, 480]
      },
      src: [
        'tests/phantomcss/start.js',
        'test/phantomcss/*-test.js'
      ]
    }
  }
});


Мы провели одинаковый набор тестов, который работает на разных устройствах и сохраняет результаты в соответствующие директории.

Настройка тестов


В определении Grunt можно увидеть, что мы запускаем процесс из файла tests/phantomcss/start.js. Этот файл запускает Casper (который затем запускает сценарии тестов и наш браузер), и выглядит он так:

phantom.casperTest = true;
casper.start();


Теперь вернемся к нашему Grunt. Вы наверно уже заметили, что мы запускаем все файлы в директории `tests/phantomcss/`, которые оканчиваются на `-test.js`. Grunt проходит циклом по каждому из этих файлов в алфавитном порядке.

Вы можете сами решить, как организовать тестовые файлы. Лично я создаю тестовый файл для каждого компонента в своем приложении.

Пишем первый тест


После того как вы реализовали ваш файл `start.js`, настало время написать свой первый тест. Мы назовем этот файл `header-test.js`.

casper.thenOpen('http://mysite.dev/')

.then(function() {
  phantomcss.screenshot('.site-header', 'site-header');
});


В начале файла мы говорим Casper'у открыть корневой URL, затем делаем скриншот всего элемента .site-header. Второй параметр — название скриншота. Я предпочитаю называть скриншоты так же как и компоненту, которую они захватывают. Так гораздо легче понимать и делиться результатами с товарищами по команде.

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

Взаимодействия сценариев


Casper позволяет автоматизировать взаимодействие, которое происходит в процессе работы PhantomCSS браузера. Например, проверку состояния кнопки при наведении мыши мы могли бы написать следующим образом:

casper.then(function() {
  this.mouse.move('.button');
  phantomcss.screenshot('.button');
});


Вы также можете тестировать состояния login/logout. В файле `start.js` мы можем написать функцию, которая заполнит форму логина в WordPress, как только мы запустим экземпляр Casper.

casper.start('http://default.wordpress.dev/wp-admin/', function() {
  this.fill('form#loginform', {
    'log': 'admin',
    'pwd': 'password'
  }, true);

  this.click('#wp-submit');

  console.log('Logging in...');
});


Вы можете заметить, что мы делаем это в casper.start() вместо того, чтобы делать это внутри каждого отдельного теста. Эта настройка сессии внутри casper.start() в файле `start.js` делает сессию доступной из других файлов с вашими тестами, так как casper.start() всегда запускается первым.

Для дополнительной информации я рекомендую взглянуть на документацию по Casper.

Запуск тестов


Теперь, когда мы реализовали набор тестов, пора их запускать. В командной строке запустите `$ grunt phantomcss`.

PhantomCSS автоматически сделает скриншоты при первом запуске, и установит их как базовые для сравнения с будущими результатами.



Если тест падает, PhantomCSS сохранит три различных скриншота в настроенную папку, и назовет файлы как, `.diff.png` и` .fail.png`.

Например, мы изменили размер шрифта текста на одной странице, но случайно уменьшил размер шрифта и на другой. PhantomCSS сделает скриншоты для сравнения:



Проблемы


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

Динамический контент


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

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

Чтобы решить эту проблему, есть два подхода, которые мне нравятся.

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

Так как эти тесты не будут развернуты на рабочем сервере, вам не придется беспокоиться об XSS уязвимости. Таким образом, до создания скриншота, я использую `.html()` в моих тестах для замены динамического контакта на статический, который берется из объекта JSON, который я включил в мой репозиторий.

Второй подход заключается в использовании инструмента под названием Hologram или mdcss, который позволит вам использовать комментарии в CSS для создания автоматически генерируемых стилей. Этот подход вносит большие изменения в рабочий процесс, что требует больше времени и средств, но имеет дополнительное преимущество — создание превосходной документации для интерфейсных веб-компонентов.

Распределение тестов внутри команды


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

Чтобы решить эту проблему, мы зарегистрировали нашу `$ grunt test` задачу, принимающую параметр `--url`, который затем сохраняется в файл локально с помощью grunt.log.

// All a variable to be passed, eg. --url=http://test.dev
var localURL = grunt.option( 'url' );

/**
 * Register a custom task to save the local URL, which is then read by the PhantomCSS test file.
 * This file is saved so that "grunt test" can then be run in the future without passing your local URL each time.
 *
 * Note: Make sure test/visual/.local_url is added to your .gitignore
 *
 * Props to Zack Rothauser for this approach.
 */
grunt.registerTask('test', 'Runs PhantomCSS and stores the --url parameter', function() {
  if (localURL) {
    grunt.log.writeln( 'Local URL: ' + localURL );
    grunt.file.write( 'test/visual/.local_url', localURL );
  }

  grunt.task.run(['phantomcss']);
});


Затем, в начале тестового файла прописываем:

var fs = require('fs'), siteURL;

try {
  siteURL = fs.read( 'test/visual/.local_url' );
} catch(err) {
  siteURL = (typeof siteURL === 'undefined') ? 'http://local.wordpress.dev' : siteURL;
}

casper.thenOpen(siteURL + '/path/to/template');


При запуске, ваши тесты будут смотреть на файл `.local_url`, но если файл не существует, то он будет использовать значение по умолчанию `http://local.wordpress.dev`.

В заключение


Существует множество преимуществ, которые визуально регрессионное тестирование может принести в ваши проекты. Спринты и непрерывная интеграция все чаще используются разработчиками.

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

Альтернативы


PhantomCSS — это не единственный инструмент. Я его выбрал просто потому что посчитал для целей нашей команды он подойдет наилучшим образом. Если вас заинтересовало визуальное регрессионное тестирование, но PhantomCSS вам не понравился, я советую взглянуть на следующие инструменты:

Комментарии (1)


  1. tavriaforever
    01.12.2015 15:28

    Есть хороший готовый аналог, который активно поддерживается и развивается – gemini
    ru.bem.info/tools/testing/gemini
    github.com/gemini-testing/gemini