Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vanchelo/350bcabd60d2bb3f28d842bbc91aae34 to your computer and use it in GitHub Desktop.
Save vanchelo/350bcabd60d2bb3f28d842bbc91aae34 to your computer and use it in GitHub Desktop.
Автоматизированное тестирование

Автоматизированное тестирование

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

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

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

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

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

Что можно тестировать

Тестировать программу можно на разных уровнях: код (юнит-тесты и интеграционное тестирование), API (если оно есть) и GUI. Разные виды тестов лучше подходят в разных ситуациях.

Тестирование кода

Код тестируется на разных уровнях:

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

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

Моки — это классы-заглушки, которые используются чтобы проверить, что определенная функция была вызвана (по моему, они не очень часто нужны).

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

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

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

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

Ради ускорения выполнения тестов, обычно используют базу данных, храняющую данные в памяти, а не на диске (MySQL и sqlite умеют это).

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

Тестировать можно не любой код. Если например в твоем коде жестко прописаны параметры соединения с базой данных или пути к папкам без возможности их поменять, ты вряд ли сможешь использовать для тестов временную БД. То же самое, если классы в твоем коде сильно связаны и ты не используешь dependency injection, если используются глобальные переменные или везде статические методы. В общем, пиши качественно.

Тестирование API

API (wiki) — это набор функций, которые можно вызывать, чтобы получить какие-то данные. Ну, например, у яндекс-карт есть АПИ геокодера. Отправив к нему запрос с географическим адресом, ты можешь получить координаты точки (и наоборот), а у Центробанка есть API, которое возвращает официальный курс валют в заданный день.

Если у твоего приложения есть API, то можно тестировать его, посылая заранее подготовленные запросы и сравнивая пришедший ответ с ожидаемым.

Тестирование GUI

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

GUI тесты еще называют End-to-End (E2E) или приемочные (aceptance) тесты.

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

зайти на страницу http://example.com/register
ввести в поле «Email» значение tester@example.com
ввести в поле «Пароль» значение 123456
ввести в поле «Повторите пароль» значение 123456
нажать кнопку «Зарегистрироваться»
дождаться загрузки страницы с таймаутом 5 секунд
убедиться, что на странице выводится текст «Вы зарегистрированы на сайте»

Для тестирования веб-приложения (сайта) необходимо имитировать работу браузера. Для этого есть разные подходы. Есть простые инструменты, которые лишь умеют отправлять HTTP-запросы к серверу и анализировать полученный HTML-код, и более продвинутые, которые либо используют настоящий браузерный движок (вроде PhantomJS) в «headless» режиме (то есть без вывода окошка со страницей на экран), а самые продвинутые предоставляют драйверы, с помощью которых можно контролировать реальный браузер (Selenium).

Простые HTML-браузеры хороши тем, что работают гораздо быстрее, но они не интерпретируют CSS- и JS-код и не могут проверить например видимость кнопки или работу скриптов. PhantomJS это умеет. Selenium позволяет получить наиболее полноценную имитацию действий пользователя, в том числе например тестирование в определенной версии браузера или использование флеш-плагина, но сложен в настройке: требуется где-то запускать эти браузеры, надо поднимать сеть виртуальных машин с нужными операционными системами и тесты на нем медленнее работают.

PhantomJS и Selenium умеют делать скриншот страницы, который можно будет посмотреть при неудачном выполнении теста.

Тестируем без фанатизма

При тестировании не стоит впадать в крайности. Например, нельзя говорить, что «100% кода должно быть покрыто юнит-тестами». Тесты должны прежде всего повышать качество кода, и требуют времени на их написание, отладку, поддержку. Если эти затраты больше, чем приносимая от них выгода, возможно они не требуются.

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

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

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

Также, есть подход, когда сначала пишутся тесты (которыми задаются требования к коду), а только потом сам код. Это называется TDD. Есть также его последователь BDD, где сценарии пишутся на странном языке Gherkin и напоминают обычный текст.

Что важно помнить

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

Тесты должны выполняться в контролируемом окружении.

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

Надо тестировать и позитивные, и негативные сценарии. К примеру, при тестировании формы регистрации надо проверить не только как она работает при вводе правильных данных, но и как она работает с неправильными данными (должна выдавать сообщение об ошибке).

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

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

Еще способы тестирования и повышения надежности кода

Использование assertions и тайп-хинтов. assert() (мануал) — это функция, которая выдает ошибку если условие в скобках не выполняется. Например, если ты сделал функцию, и она принимает только числа от 0 до 5, логично в начале поставить assert для проверки этого:

function doSomething($a) 
{
    assert($a >= 0 && $a <= 5);
    ...

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

Type hint — это конструкция, которая указывает что аргумент функции должен быть массивом или объектом определенного класса. При попытке передать в нее что-то другое произойдет фатальная ошибка. Используй тайп хинты везде, где можно. Пример:

function doSomething(SomeClass $a, array $b) {

Тестирование через вызов контроллера — это что-то вроде GUI тестирования, только без отправки HTTP-запросов (и имитации работы браузера), используя вызов контроллеров напрямую. Обычно это используется с приложениями на фреймворках вроде Yii2 или Symfony2.

Статический анализ кода. Специальная программа проверяет исходники, не запуская код, с целью найти опечатки и неправильные куски кода. Такая программа хорошо ищет ошибки ситаксиса: $x = ($x + 1; (пропущена скобка), опечатки вроде if ($x = 1) (используется = вместо == в if), опечатки в именах переменных, функций и классов, обращение к несуществующей переменной.

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

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

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

Вот ссылка про нагрузочное тестирование в Яндексе: http://habrahabr.ru/company/yandex/blog/202020/

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

Тестирование на основе скриншотов — статья от Яндекса http://habrahabr.ru/company/yandex/blog/200968/

Инструменты

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

PhpUnit

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

Сам phpUnit распространяется в виде одного файла phpunit.phar, и ты запускаешь его командой вроде

php phpunit.phar ....

Чтобы увидеть список всех доступных опций, можно набрать

php phpunit.phar --help

(если ты не работал с командной строкой и плохо понимаешь, о чем речь, прочти мой гайд по командной строке).

Тесты для phpunit хранятся в файлах (их может быть много, и при желании их можно раскладывать по папкам), каждый файл содержит 1 класс, унаследованный от встроенного в phpUnit класса PHPUnit_Framework_TestCase. А этот класс может содержать 1 или больше методов с конкретными тестовыми примерами. Давай напишем тест, проверяющий работу php-функции count (она возвращает число элементов в массиве, и тестом мы проверим что она делает это правильно).

class FunctionCountTest extends \PHPUnit_Framework_TestCase
{
    public function testFunctionCount()
    {
        // Протестируем работу стандартной функции count
        $array3 = array(1, 2, 3);
        $array0 = array();
        $array1 = array(1);

        // count($array3) должно вернуть 3
        $this->assertEquals(3, count($array3));
        $this->assertEquals(0, count($array0));
        $this->assertEquals(1, count($array1));
    }
}

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

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

Запустить тест можно командой

php phpunit.phar FunctionCountTest.php

PhpUnit выполнит его и выведет результаты. Если у тебя много тестов, можно указать только имя папки и phpUnit сам найдет все файлы в ней, имена которых заканчиваются на Test.php и выполнит.

Если ты тестируешь существующий код, то часто что-то требуется сделать до выполнения тестов, например, подключить автозагрузчик или задать значение какой-то переменной. Для этого можо создать файл, например, bootstrap.php. Если ты используешь композер, то подключение автозагрузки делается одной строчкой:

require_once '../vendor/autoload.php';

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

php phpunit.phar --bootstrap bootstrap.php FunctionCountTest.php

Обычно для тестов создают папку с названием tests в корне проекта.

Настройки для phpunit также можно задать не опциями, а в XML-файле phpunit.xml (что такое XML? wiki). Формат файла описан в мануале. Это удобнее,так как их в этом случае не надо печатать в командной строке. Вот как будет выглядеть файл для примера выше:

<phpunit bootstrap="bootstrap.php">
    <testsuites>
        <testsuite name="count">
            <file>FunctionCountTest.php</file>
        </testsuite>
  </testsuites>  
</phpunit>

Соответственно, для запуска тестов достаточно набрать

php phpunit.phar

Подвох: phpunit при выполнении тестов перехватывает и скрывает все, что выводится с помощью echo и аналогичных функций. Если тебе надо что-то вывести для отладки, используй конструкцию вроде var_dump($x); die();.

Ссылки про phpUnit:

phpunit интегрируется в IDE:

  • Eclipse PDT через плагин MakeGood или PTI (англ.)
  • Netbeans (я подозреваю, что можно обойтись без установки PEAR, достаточно указать путь к phpunit.phar)
  • PhpStorm, мануал (англ.) — достаточно нажать в настройках кнопку скачивания phpunit.phar

В этом случае ты можешь запускать и просматривать результаты тестов прямо в IDE. Но учиться лучше с использованием командной строки.

Codeception

Cайт codeception. Этот фреймворк заточен на написание API и GUI тестов (хотя он включает в себя phpunit и может выполнять его тесты, но удобнее их хранить отдельно). Он может работать как с примитивным html-браузером на основе Symfony BrowserKit (не интерпретирующим CSS и JS), так и с PhantomJS и Selenium. Также, он может использоваться для «функционального» тестирования, то есть вызова контроллеров напрямую (не через запрос на веб-сервер). Для этого у него есть плагины к разным популярным фреймворкам. Причем синтаксис скриптов для всех этих случаев примерно одинаков.

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

Codeception, как и phpUnit, распространяется в виде одного файла codecept.phar. После скачивания можно запустить команду

php codecept.phar bootstrap

Чтобы он создал нужную структуру папок для тестов.

Вот как может выглядеть скрипт проверки формы регистрации. Код на нем похож на текст на английском языке и видимо вдохновлен behat (который вдохновлен рубиевским cucumber).

// Создаем объект-тестировщик
$I = new AcceptanceTester($scenario);

// Задаем название теста (оно отображается в отчете)
$I->wantTo('Test that registration works');

// Переходим на страницу
$I->amOnPage('/register');

// Заполняем форму
$I->fillField('email', 'tester@example.com');
$I->fillField('password', '123456');
$I->fillField('password-confirm', '123456');

// Отправляем нажатием кнопки
$I->click('Register');

// Проверяем результат
$I->see('You have registered');

// Проверяем что запись появилась в БД
$I->seeInDatabase('users', array('email' => 'tester@example.com'));

Команда, чтобы запустить этот скрипт, есть на сайте.

Как я уже писал выше, этот скрипт может фактически работать с использованием разных уровней эмуляции браузера (первый способ самый быстрый и простой, последний самый точный и мощный, но медленный):

  • вызывать напрямую контроллер, если твое приложение использует поддерживаемый фреймворк (поддерживаются ZF1-2, Yii1-2, Symfony2, Silex. Slim не поддерживается, никто не желает написать плагин и выложить в опенсорс?)
  • используя PhpBrowser, то есть скачивание HTML-страниц через HTTP
  • контролируя PhantomJS через WebDriver
  • контролируя реальный браузер через Selenium

В codeception есть много удобных вспомогательных функций для заполнения форм, отправки файлов, поиска элементов на странице с помощью XPath и CSS селекторов, работы с куками, заголовками, аякс-запросами, всплывающими окнами, ифреймами, снятия скриншотов, навигации. Эти функции разбиты на модули (например, модуль для тестирования АПИ, модуль для работы с базой данных), и ты можешь писать свои или расширять существующие модули через наследование.

Ссылки:

Skipfish

Skipfish (англ.) — инструмент от Google, который может использоваться для поиска ошибок на сайте и заодно для нагрузочного тестирования. Он обходит все страницы, начиная со стартовой и перемещаясь по ссылкам, и позволяет обнаруживать битые ссылки (в том числе на картинки, CSS и JS файлы). Также, он умеет отправлять запросы со случайно сгенерированными данными и пытается искать явные XSS/SQL уязвимости. Он работает очень быстро (если конечно сайт может отвечать быстро).

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

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

PhantomJS

PhantomJS — это браузерный движок (используется Webkit — тот же, что используется в Safari, Opera, Яндекс-браузере и старых версиях Хрома), которым можно управлять с помощью скриптов на яваскрипте. Это headless браузер, то есть он не выводит никаких окон (и вообще не требует наличия видеокарты и дисплея), а работает как приложение командной строки. Он кроссплатформенный и его можно запускать, например, автоматически на линукс сервере. Он умеет переходить по страницам, загружать CSS/JS (при желании и картинки), делать скриншоты, выполнять произвольный JS код в контексте страницы.

Также, для PhantomJS есть плагин ghostdriver (WebDriver), который позволяет подсоединиться к программе извне и управлять ей. Он использует протокол Selenium, и с его помощью PhantomJS можно управлять из codeception.

Тесты по идее можно писать на яваскрипте, скармливая их напрямую PhantomJS, но удобнее использовать какую-нибудь библиотеку работающую поверх него, например, Casper.js (это если ты готов писать тесты на явскрипте, если на PHP, то стоит использовать codeception + PhantomJS через WebDriver).

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

Статьи по использованию PhantomJS с codeception наверно нетрудно нагуглить.

Selenium

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

Selenium сервер написан на Яве, потому она понадобится чтобы его запустить. Поддерживаются браузеры Firefox, IE (6-11), Safari на OS X, Opera 12 (старая Опера), Chrome.

Selenium дает наиболее полноценное тестирование, так как ты можешь запускать код в конкретной версии браузера (например, IE) под конкретной ОС. Но его настройка сложнее чем других инструментов, и он требует больше ресурсов. Когда выполняется тест, браузер должен быть запущен и ты не можешь пользоваться компьютером, так как один лишний клик может сорвать выполнение теста. По этой причине для тестов обычно поднимают сеть вирутальных машин, в которых тесты и выполняются.

Так как настроить окружение для запуска тестов сложно, есть коммерческие сервисы (например saucelabs) которые за плату выполняют selenium-тесты на нужных браузерах и возвращают результат. Они предоставляют API с помощью которого тесты можно запускать автоматически и умеют отслеживать изменения в репозитории, тестируя код при каждом новом коммите.

Тесты в браузере содержат подвохи: например, когда ты программно нажимаешь кнопку, браузеру нужно время, чтобы выполнить привязанный к ней яваскрипт, обработать изменения в DOM, перерисовать экран. Если твой скрипт не будет дожидаться этого, а попробует сразу после нажатия кнопки проверить изменения на экране, он может их не увидеть. В некоторых статьях ты можешь увидеть совет вроде «делать паузу N мс после каждого шага», но это плохие советы. Во-первых, нет гарантий, что действие выполнится за эти N мс, во-вторых, это сильно тормозит тесты. Лучше, как советует Яндекс, в таких случаях периодически проверять появление определенного элемента на странице.

Также, не так просто проверить, что элемент видим. Ведь в CSS много свойств (opacity, visisbility, display), которыми можно его скрыть, плюс он может быть помещен за пределами экрана.

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

Также, Selenium содержит плагин к фаерфоксу (Selenium IDE), который позволяет записывать действия пользователя и генерировать из них тест (то есть повторять эти действия позже), но он, как я понимаю, довольно слабый и генерирует тяжелочитаемый код на своем странном языке. Гораздо лучше управлять Selenium из codeception и писать тесты на PHP.

Статьи:

Mocha

Mocha, гитхаб (читается «мока», кто бы поверил) — фреймворк для тестирования яваскрипт-кода, например приложений. Он использует подход BDD.

Zombie.js

Это фреймворк для написания тестов, имитирующий браузер (поддерживается CSS/JS) за счет JSDOM, то есть реализации DOM под Node.JS. Внутри он также использует вышеупомянутый Mocha. Его можно использовать для тестирования верстки и яваскрипта. Ссылка: http://zombie.labnotes.org/ http://zombie.js.org/

Вот пример теста, тестирующий позитивный сценарий успешной регистрации на некоем воображаемом сайте (код я не проверял, так что могут быть небольшие ошибки). Этот код запускается под Node.JS:

const Browser = require('zombie');

// В тесте мы будем отправлять запросы на адрес http://example.com/signup
// однако эти запросы будут обслуживаться установленным локально 
// сервером на порту 3000 с адресом localhost:3000
Browser.localhost('example.com', 3000);

// describe описывает сценарий теста, 
// причем блоки describe могут вкладываться друг в друга
describe('Пользователь заходит на страницу регистрации', function() {

  // создаем объект, имитирующий браузер
  const browser = new Browser();
  
  // Действие, выполняемое до теста - переход на нужную страницу
  before(function(done) {
    browser.visit('/signup', done);
  });

  describe('пользователь заполняет и отправляет форму', function() {

    before(function(done) {
      browser
        .fill('email',    'tester@example.com')
        .fill('password', '123456')
        .fill('password-confirm', '123456')
        .pressButton('Зарегистрироваться', done);
    });

    // it описывает конкретное требование и код для его проверки
    it('регистрация должна быть успешной', function() {
      browser.assert.success();
    });

    it('пользователь должен увидеть приветственное сообщение', function() {
      // Уведомление должно вывестись в элементе с классом notification 
      // (используется CSS-синтаксис селекторов)
      browser.assert.text('.notification', 'Вы успешно зарегистрированы');
    });
  });
});

Jasmine

Jasmine (англ.) - это тоже фреймворк для тестирования JS-кода с уклоном в BDD (Behaviour-Driven Development). Если ты забыл, то идея BDD в том, что мы до разработки описываем требования (спецификацию) к коду в виде тестов. Он позволяет писать простые, легко читаемые тесты. Запускать тесты можно как через браузер (если ты хочешь смотреть релуьтаты глазами), так и из командной строки с помощью Node.JS (если проверку надо автоматизировать). Если твои тесты используют взаимодействие с DOM (или какие-то другие компоненты браузера) то для запуска под Node.JS придется подключить упомянутый выше JSDOM. Вот пример простого теста на Jasmine, которым мы протестируем функцию вычисления квадратного корня Math.sqrt():

describe("Math.sqtr() это функция", function() {
    it("вычисляет квадратный корень", function() {
        expect(Math.sqrt(9)).toBe(3);
    });
    
    it("возвращает нуль, если попытаться найти корень из нуля", function () {
        expect(Math.sqrt(0)).toBe(0);
    });
    
    it("возвращает NaN при попытке вычислить корень из отрицательного числа", function () {
        expect(Math.sqrt(-9)).toBe(NaN);
    });
});

Если не обращать внимание на некоторую нечитаемость текста из-за смеси русского и английского, то в общем код теста действительно напоминает список требований к функции. Мы описываем конкретные тестовые случаи с помощью конструкций expect(...).toBe(...) где указываем пример выполняемого кода и ожидаемый результат.

Вот результат выполнения этих тестов в браузере, где Jasmine выводит список пройденных проверок:

Результат запуска теста Jasmine в браузере

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

Behat, phpspec

Библиотеки для написания тестов в стиле BDD: http://docs.behat.org/en/v2.5/ (англ), http://www.phpspec.net/

Travis CI

Чтобы не запускать тесты каждый раз руками, используют CI Server вроде Teamcity, Jenkins или Hudson (статья от Яндекса: http://habrahabr.ru/company/yandex/blog/237017/ ). Но эти системы надо устанавливать и настраивать, в то время как Travis это сервис, который сам будет подсоединяться к твоему гитхаб репозиторию, брать оттуда изменения и прогонять тесты. Для open source проектов на github Travis CI бесплатен.

Ссылки

На хабре хорошие статьи про тестирование в блогах Яндекса и Баду:

Вот примеры разных open source проектов c тестами (кстати, многие из них используют travis CI):

Задачи

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

Ты форкаешь тот, который тебе больше нравится (или меньше не нравится), и мы пишем под него юнит-тесты на phpUnit и интерфейсные тесты на codeception.

Если тебе не нравится файлообменник, можно потестировать что-то еще (если есть идеи небольшого приложения на PHP, требующего тестов — напишите).

Прежде чем писать тесты, надо составить план тестирования (что мы тестируем и как). Мы сначала определяем, какие возможности предоставляет приложение, а потом для каждой пишем несколько тест кейсов. Не забывай, что надо тестировать как позитивные, так и негативные сценарии. Вот примерный план, 1 пункт = 1 тест.

  • Загрузка файла
    • Загрузить файл и убедиться что он загрузился
    • Отправить пустую форму без файла
    • Загрузить слишком большой файл
    • Загрузить файл c utf-8 символами в названии и проверить что все отображается корректно
  • Страница файла
    • Загрузить файл и убедиться что он доступен для скачивания. Скачиваемый файл совпадает с загруженным
    • Убедиться что выводится правильная информация о файле
  • Медиаданные
    • Загрузить картинку и убедиться что превьюшка выводится и она совпадает с картинкой
    • Загрузить картинку неподдерживаемого типа вроде tif/bmp
    • Загрузить аудиофайл и убедиться что доступен плеер
    • Загрузить видеофайл и увидеть плеер
  • Комментарии
    • Отправить комментарий
    • Отправить пустой комментарий
    • Отправить комментарий с HTML кодом
    • Отправить комментарий-ответ
  • Последние файлы
    • Загрузить 3 файла и проверить что они отображаются в правильном порядке с правильными ссылками

Также, стоит написать тест на codeception который обходит сайт и проверяет отстутвие битых ссылок.

В качестве базы для тестов стоит использовать in-memory mysql базу.

Если у тебя есть какие-то дополнения к плану, можно их внести.

Что тестировать юнит-тестами? Ими можно тестировать например функцию валидации (комментария к примеру), а также функции вроде форматирования размера файла (которые выводят его в виде «12Мб»).

Также, надо настроить интеграцию с Travis CI, чтобы тесты выполнялись на нем.

Связаться с автором

codedokode@gmail.com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment