Легкий способ начать тестировать

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

    Итак, первый совет. Забудьте всё что вы знаете о юнит-тестах. Швырните табуреткой в человека, который сказал вам, что без них не обойтись. Попробуем разобраться, в каких случаях нужно их использовать, а в каких — нецелесообразно.

    Я абсолютно уверен, что PHP-программисты редко пишут тесты, потому что начинают не с того конца. Все знают, что тесты это хорошо и клево. Но открыв сайт того же PHPUnit, и прочитав красочный пример о тестировании калькулятора умеющего выполнять операцию a + b, они спрашивают себя: какое отношение этот калькулятор имеет к моему приложению? Ответ: никакого. Ровно как все похожие примеры, на сайтах других фреймворков тестирования. Будто бы все забыли, что PHP прежде всего для веба. Будто бы все пишут калькуляторы, а не сайты на основе MVC-парадигмы.

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

    И потому мы поговорим о функциональных тестах (или приемочных).

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

    Википедия

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

    Что у нас есть для функционального тестирования? Из всего известного мне, это Codeception и, например, PHPUnit + Mink или прямое использование Selenium. Я хотел включить в этот печерень и Behat, но его автор попросил не использовать Behat для функционального тестирования.

    Если бы тестировать с Selenium или PHPUnit + Mink было просто, вы бы уже наверняка их использовали. Потому остановимся на Codeception.

    Тест в нем описывается так:

    <?php
    $I = new WebGuy($scenario);
    $I->wantTo('see my site is working');
    $I->amOnPage('/');
    $I->see('Welcome, on my site!');
    ?>


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

    <?php
    $I->click('Feedback');
    $I->see('Submit your feedback');
    $I->fillField('Body','Your site is great!');
    $I->click('Submit');
    $I->see('Thanks for your feedback!');
    ?>


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

    Впрочем, на Хабре про Codeception я уже писал.

    А теперь попробуем определиться, с unit-тестами.

    Модульное тестирование, или юнит-тестирование (англ. unit testing) — процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.

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

    Википедия.

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

    Но если в рамках функциональных тестов вы проверили всё, что пользователь может сделать, то в юнит-тестах постарайтесь учесть, что он не должен сделать.

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

    Или ещё один пример: у вас на сайте акция — каждый 100ый зарегистрированый пользователь получает подарок. Как бы так проверить, что подарки выдаются, и не создавать при этом 100 лишних пользователей? Вот тут уже пишите юнит-тесты. Без них, скорее всего, никак.

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

    И ещё: если вы создаете сайт или приложение на основе своего фреймворка, CMS, или каких-то своих модулей к этим фреймворкам и CMS — обязательно пишите к ним модульные тесты. Тут без вариантов. Если же вы используете сторонние популярные решения, а не изобретаете велосипеды, то скорее всего их код уже протестирован автором и зацикливаться на их тестировании не стоит.

    Для юнит-тестирования фреймворков много:

    Как альтернатива традиционным юнит-тестам:

    Опять таки, выбирайте сами. PHPUnit, это конечно стандарт, но более современные фреймворки могут быть проще и эффективнее.

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

    Disclaimer: прошу прощения за ряд намеренных упрощений и неточностей в статье. С моей точки зрения, они необходимы, чтобы объяснить базовые принципы автоматического тестирования PHP-приложений. Чем больше людей будут тестировать свои приложения, тем лучше станет мир. Ведь главное — начать.
    Поделиться публикацией

    Похожие публикации

    Комментарии 54

      +18
      Ок. Ок. В начале поста вы пишете, что открывая сайт фреймворка PHPUnit я вижу тесты калькулятора, которые не имеют отношение к моему веб-приложению, построенному на основе паттерна MVC. Я ожидал в конце статьи увидеть живой пример, например, тестирования модуля аутентификации. А по факту я вижу голую абстракцию.
        +1
        Ок. Вопрос такой: а кто написал модуль аутентификации? Я бы вот его не писал, а взял из какого-то фреймворка. И убедился, что там он протестирован. То есть, статья о том, что прежде всего нужно тестировать приложения вцелом. И тестировать нужно конкретно код приложения.
          +4
          Название статьи вам о чем-нибудь говорит?

          Я так и не понял: статья о нужности тестирования или о том, все-таки, как же легко начать тестировать?
            –3
            Ну в нужности тестирования, я полагаю, никто не сомневается. Статья о том, что начинать нужно с функциональных тестов. И то какие лучше элементы тестировать юнит-тестами.

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

              Я junior, но в следствие проблем с предыдущим проектом, я решил покрыть следующий проект максимально тестами, насколько мне это позволят сроки и другие факторы. Заголовок поста меня заинтересовал, а войдя я даже ссылок на литературу не увидел.
                –1
                Максимальное покрытие это хорошо. Спорить не буду. Я лишь заостряю внимание, с чего стоит начать. При этом, позвольте такой вопрос: ваш нынешний проект, он использует какие-то сторонние фреймворки?
            0
            Вообщем, спасибо. Я уточнил это в статье.
              +2
              Присоединяюсь. Как незнакомый с тестированием, я понял что данная статья одна из тех, которые пишут о том, как легко начать тестировать тем, кто уже этим занимается пару лет :)
                +2
                Не-не-не, те кто этим занимаются много лет, заберут табуретку из начала поста и будут ею швырять в меня )
                  0
                  Объясните, пожалуйста, что вы хотите этим текстом тогда сказать? что не нужно тестировать конкретные методы, а тестировать результат — вывод в браузер?
                    0
                    Точно.

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

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

                        — приёмочные — проверяют конечный результат, как правило в браузере, то есть что непосредственно знает браузер (и как следствие показывает пользователю), получая html/js/css с сервера со всеми заголовками и т. п., проверяется не только приложение (php-код), но и сервер, и браузер (например JS-код) и прочая среда. Выполняются долго. Запускаются редко.

                        — функциональные — проверяют почти то же самое, но на более низком уровне, сервер и браузер уже не используются обычно, а проверяется вывод скрипта, то есть что html код соответствует ожидаемому, что в базу внесены нужные изменения и т. п. Зачастую используют эмуляторы браузеров. Выполняются быстрее. Запускаются чаще.

                        — интеграционные — опускаются ещё ниже, проверяется взаимодействие отдельных слоев приложения, например, что роутер, получив URL (или URI? вечно их путаю), вызовет контроллер с нужными параметрами и что контроллер, получив параметры передаст в шаблон нужные переменные (тесты роутера и тесты контроллера — обычно два разных теста). Выполняются относительно быстро, запускаются часто.

                        — модульные (юнит) — самый низкий уровень, тестирование отдельных методов, тупо что метод с параметрами возвращает нужное значение. Выполняются очень быстро, запускаются также часто, как нажимается кнопка save или run. В идеале автоматически при её нажатии :)

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

                        По хорошему надо тестировать всё, но, скажем, приёмочные тесты отнимают довольно много времени и если нет отдельных тестировщиков, то ими часто пренебрегают (особенно если сложного JS на клиенте нет). Функциональные дают чуть меньшую уверенность в том, что приложение работает как ожидалось, но отнимают относительно мало времени, по сути один тест является спецификацией одной ветки выполнения, то есть переписыванием ТЗ с человеческого языка на алгоритмический в терминах приложения и ЯП. Интеграционные при желании оттестировать всё что можно занимают много времени и часто являются детализацией функциональных, разбивая их на несколько частей, что несколько снижает энтузиазм их написания («это же уже работает!»). Модульные пишутся просто (если архитектура нормальная) и быстро. Если считать функциональные спецификацией приложения, то модульные это спецификация методов, с хорошей архитектурой метод многого делать не должен и спецификации примитивны.

                        Внутреннему заказчику я бы посоветовал (особенно при работе с существующим приложением без тестов) для начала заказать, чтобы исполнители начинали с функциональных тестов (на новую функциональность или на ту, которую нужно изменить), как минимум для основной ветки выполнения (обычный сценарии работы) и использовали модульные в нетривиальных случаях, когда функциональными добраться до неосновной ветки выполнения сложно и/или долго (например для ситуаций типа «нет места на диске» или «сервер БД недоступен»). При обнаружении бага прежде чем искать его (особенно если сходу не понятно где он конкретно), сначала написать тест, который с этим багом заваливается, а без него должен пройти. При небольшом рефакторинге (типа выделения метода-объекта) писать модульные и интеграционные (если не сложно, а сложно быть не должно) тесты.

                          +1
                          Спасибо за такой развернутый комментарий.

                          Кстати, ты спрашивал как в Codeception сделать новую test suite для интеграционных тестов. Теперь это делается командой

                          codecept generate:suite name guyname
                            0
                            Спасибо. Всё никак не приступлю к попытке внедрения codecept на существующий проект. Но скоро :)
                            0
                            Спасибо огромное — вот примерно такого не хватало как по мне в статье :) ибо в ней рассчитывается, что пользователь должен что-то знать, а проблема как раз в том, что в плане тестирования трудно к чему-то подступиться, ибо непонятно с чего начать.

                            И ещё в статье немного непонятно сделано с переходом от функционального тестирования к модульному — на этом переходе объяснение обрывается и дальше как-то водянисто — не так как в начале — с примерами.
                              0
                              И ещё вопрос, что делать с JS — ведь много функционала в современном приложении работает черех ajax — как его тестировать?
                                0
                                Через Selenium.
                                Можно как писать внтури Selenium IDE, так и через те же тесты в Codeception.
                                  0
                                  Именно выполнение JS-кода и его связь с сервером — через Selenium, как приёмочные тесты.

                                  Упростит задачу если предварительно серверную часть протестировать функциональнымии, то есть например, что на запрос типа GET /users/volch.json (или GET /api/users/volch — на любителя, мне первый вариант больше нравится) сервер вернёт именно json, причём корректный имеено для этого запроса. Это чтобы не ковырять JS, когда проблема на самом деле в сервере.
                                    0
                                    А мне второй. Но обоснованно. В первом через URI сообщается больше информации, чем это может потребоваться. В частности, он жестко задаем формат выходных данных — JSON. Если код-клиент этого URI потребует для работы XML, то придется в нем изменять URI, хотя по сути данные останутся прежними. Во-втором же при правильной реализации можно этим рулить через Content-Type работая с одним и тем же URI.

                                    RESTfull очень любопытный и, имхо, правильный подход в контексте веба.
                                      0
                                      Собственно я хотел показать разницу между /api/users/volch и /users/volch*

                                      Тогда вообще GET /users/volch :) Идеологически оно, конечно, правильно. Но мне как-то проще поменять uri в клиенте, чем формировать и менять при необходимости Content-Type. Плюс помню были проблемы с корректной обработкой в самом популярном на то время клиенте IE5.5
                                    0
                                    0
                                    *На PHPunit в принципе их писать можно, но ни разу не видел такого. В другом месте это должно было быть, про приёмочные, но эту «ветку» решил удалить и так много получилось, но эта фраза как-то выжила, причём не в том месте где должна была быть.
                          0
                          О том как легко начать тестировать. Об этом написано в заголовке.
                          Или приведённые два примера вы находите сложными?
                            –2
                            Данные примеры — это приемочные тесты или модульные? Почем дальше идет речь о том, что нужно использовать PHPUnit? Потом говорится, что их можно и не использовать.

                            Есть ещё какие-то функциональные тесты…
                              +3
                              Начиналось в стате всё с функциональных тестов. Если напишите их, получайте ачивку и переходите к юнит тестам.
                                0
                                Это вы с кем разговариваете? :)
                        +2
                        А вы из тех безнадёжных, которым нужны видеоуроки иначе дела не будет?
                        Голой абстракицией вы назвали пример поиска строки на странице и проверка раотоспособности формы? Действительно, это же базовые примеры PHPUnit-а…

                        Отличня статья, лично меня Codeception очень заинтересовал. Особенно после сравнения автора с PHPUnit gist.github.com/1498755
                          +2
                          Как-то не-русски получилось: «сравнения автора с PHPUnit».
                          Сравнения автором одинаковых тестов на PHPUnit и Codeception.
                        +2
                        Лично я из статьи так и не понял, как могу легко начать тестировать? Т.е. кроме того, что мне нужно скачать один из тестировочных фреймов и определить задачи для них. Но это ведь понятно и без пояснений. Лично для меня не хватает работающих примеров.
                          +1
                          Как я понял статья не для начинающих, а для тех, кто хочет перейти с юнит-тестов на этот Codeception. Для начинающих тут ничего полезного нет.
                            –1
                            Там один пример и он работающий. Начните с него.

                            Остальные ситуации объяснены на пальцах.
                            0
                            А вы можете описать что нужно сделать, чтобы эти тесты работали с:

                            $I->click('Войти');
                            $I->see('Личный кабинет');


                            если все файлы проекта в win-1251?

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

                              А вот кирилица в консоли это впринципе болезненная тема. Постараюсь изучить возможности.
                              Какая ОС, кстати?
                                +2
                                find . -name '*.php' -type f | while read i; do iconv -f WINDOWS-1251 -t UTF-8 "$i" >tmp; mv tmp "$i"; done
                                
                                  +1
                                  Блин, пока набирал ответ — уже написали что это windows.
                                    +1
                                    О да, знающие решение проблемы люди видать минусуют.
                                    0
                                    Кириллицу в консоли я победил с помощью ansicon. Но все равно, при выводе результатов возникают проблемы. Я завтра на работе все шаги повторю, чтобы вспомнить. Я могу вам потом прислать скрин вывода результатов? А то сам я победить не могу)
                                    ОС — win7 64
                                      0
                                      Оки. Будет интересно. Там у меня с цветами глюк был, на винде цвета по дефолту выводились. Я его вроде поправил, тепер цвета нужно включать через конфигурацию.
                                      +1
                                      Опишите простым и понятным языком, как взаимодействует пользователь с вашим приложением. Затем запишите все то же самое на алгоритмическом языке. Получите тест. Для большинства начинающих уже первый этап становится серьезным испытанием. :)
                                        +1
                                        Так и просится слово «бросить» в названии статьи:)
                                          0
                                          за 30 дней?
                                          0
                                          Отличная статья! Очень полезно для тех кто хочет начать тестировать, но не знает с чего начать или все время откладывает!

                                          Я, лично, теперь точно попробую!
                                            0
                                            Тестирование это конечно хорошо, но писать тестирование утомительно, если только сайты не делаются на основе какой то одной платформы.
                                            А одна платформа подразумевает что все основные моменты уже и так протестированы.
                                            ХЗ я не сторонник писания тестов, просто не вижу в этом смысл для себя.
                                              +1
                                              Про платформу я вроде говорил неоднократно в статье. Тестировать её не надо.
                                              Тестируйте только то, что сделали сами. Если вы уверены, что ссылка А всегда работает и форма Б всегда работает — ну тоже, не тестируйте.

                                              Только скажу, что с ростом проекта уверенности стает всё меньше и меньше )
                                                0
                                                Платформу я имел ввиду движок сайта, магазина или еще чаго. Если на нем поднят не первый проект то там по дефолту уже баги закрыты.
                                                  0
                                                  Ну если его вообще не менять из проекта в проект, только дизайн править, то да.
                                                    +1
                                                    Если вы пишите под протестированный движок (как протестирован — не важно), или просто доверяете его разработчикам, или платформу получили в виде чёрного ящика (а-ля dll), то тесты пишите только на свой код. Не надо проверять, например, что предоставляемая платформой обёртка для БД действительно пишет в БД — это уже не ваша зона ответственности как разработчика продукта на платформе: убедились, что вызов обёртки идёт с нужными параметрами — пошли дальше. Если есть баги в платформе, касающиеся вашей функциональности, то они выявятся на функциональных/приёмочных тестах и сможете пофиксить платформу или отправить багрепорт, а пока фиксят написать костыль для обхода. А наличие модульных и интеграционных тестов на свой код позволит однозначно отнести непроходящий функциональный тест на совесть разработчика платформы (или что доки к ней не читаете и, например, пытаетесь использовать PL/SQL в MySQL ;) )
                                                  0
                                                  но писать тестирование утомительно, если только сайты не делаются на основе какой то одной платформы.

                                                  А вот тут не согласен. Платформа у нас одна и едина. Называется Веб. И тестировать там ничего утомительного. Перед сдачей сайту заказчику вы что делаете? Просматриваете сайт на наличие ошибок, смотрите, чтобы всё работало. Возьмите и один раз запишите свои шаги и будут вам функциональные тесты.
                                                    0
                                                    Утомительно покрытие кода тестами для галочки, потому что «так нужно», заставили, да ещё тестирующий фреймворк не знаешь толком, а архитектура заставляет писать даже модульные тесты с десятком стабов/моков и проверкой БД. В общем покрывать тестами существующий код, написанный без учета необходимости тестирования.

                                                    А вот когда пишешь с нуля или на платформе, написанной с учетом практик тестирования, да ещё сначала пишешь тесты, а потом код, который их выполняет, то это становится не утомительным, а прикольным (в хорошем смысле слова). И архитектура сама собой получается такой что, например, какие-то изменения нужно вносить только в одно место, заведомо зная, что на работу остальных они не повлияют. А если повлияют, то узнаешь об этом моментально, а не когда заказчик посреди ночи начнёт названивать.
                                                    0
                                                    Спасибо, приятный стиль изложения.
                                                    Интересно было бы узнать ваше мнение, как бы вы решали проблему тестирования, если бы сложность логики на клиенте (в браузере, на ExtJS) приближалась бы к «толстому» клиенту, с многочисленными динамически подгружаемыми JS-формами и довольно большим локальным хранилищем данных (Gears->IndexedDB).
                                                    Применим ли в данном случае Codeception?
                                                      0
                                                      Для яваскрипт кода используйте Jasmine, он заменит вам юнит-тесты. Для функциональных тестов, наверняка, лучше сам по себе Selenium.

                                                      Codeception нужен для тестирования серверной части.

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

                                                    Самое читаемое