2. Написание тестов на PHPUnit
Пример 2.1 показывает, как мы можем писать тесты, используя PHPUnit, которые выполняют операции с массивом PHP. В этом примере представлены основные соглашения и шаги для написания тестов с помощью PHPUnit:
Кроме того, вы можете использовать аннотацию @test в докблоке метода, чтобы пометить его как метод тестирования.
Внутри тестовых методов для проверки того, соответствует ли фактическое значение ожидаемому используются методы-утверждения, такие как assertSame() (см. Утверждения ).
Зависимости тестов
Адриан Кун (Adrian Kuhn) и другие:
Модульные тесты главным образом пишутся в качестве хорошей практики, помогающей разработчикам выявлять и исправлять баги, проводить рефакторинг кода и служить в качестве документации для тестируемого программного модуля (программы). Для достижения этих преимуществ модульные тесты в идеале должны охватывать все возможные пути исполнения программы. Один модульный тест обычно покрывает один конкретный путь в одной функции или методе. Однако тестовые методы необязательно должны быть инкапсулированными и независимыми. Часто существуют неявные зависимости между тестовыми методами, скрытые в сценарии реализации теста.
PHPUnit поддерживает объявление явных зависимостей между тестовыми методами. Эти зависимости не определяют порядок, в котором должны выполняться тестовые методы, но они позволяют возвращать экземпляр (данные) фикстуры теста, созданные поставщиком (producer) для передачи его зависимым потребителям (consumers).
Пример 2.2 показывает, как использовать аннотацию @depends для представления зависимостей между тестовыми методами.
Провайдеры данных
Метод провайдера данных должен быть объявлен как public и возвращать либо массив массивов, либо объект, реализующий интерфейс Iterator и возвращать массив при каждой итерации. Для каждого массива, являющегося частью коллекции, будет вызываться тестовый метод с элементами массива в качестве его аргументов.
При использовании большого количества наборов данных полезно указывать для каждого из них строковый ключ, вместо использования числового ключа по умолчанию. Вывод станет более подробным, так как он будет содержать имя набора данных, не прошедший тест.
Как тестировать приложения PHP с помощью PHPUnit
В этом руководстве мы вернемся к PHP и на этот раз рассмотрим, как создавать модульные тесты. Это поможет нам создавать более надежные и менее подверженные ошибкам приложения 💪.
Но прежде всего, и для тех, кто не имеет четкого представления о….
Что такое модульные тесты?
В основном это скрипты, которые мы создаем для тестирования определенных блоков кода. Тест получает данные из него и проверяет их с помощью функций, предоставляемых библиотекой, которую мы будем использовать. Таким образом, мы можем убедиться, что все хорошо, прежде чем отправить его в продакшн.
Создадим проект, чтобы посмотреть на все более наглядно.
Создаем структуру нашего проекта. Сначала мы создадим две папки в корневом каталоге, одну назовем app, а другую – tests. Далее создадим файл composer.json со следующим содержимым:
После этого мы запускаем команду composer dump для создания autoload:
Далее мы установим PHPUnit. Это будет библиотека, которую мы будем использовать для создания наших тестов.
После этого нам нужно создать конфигурационный файл для PHPUnit. Для этого перейдем в корневой каталог нашего проекта и создадим файл phpunit.xml, который будет содержать следующий код:
Фактически, мы передаем в атрибуте bootstrap файл загрузки класса, а в testsuite – каталог, в котором мы будем сохранять тесты, который в нашем случае будет находиться в папке tests.
Для этого примера мы создадим класс, который будем использовать в качестве подопытного кролика для запуска наших тестов. Для этого мы переходим в каталог приложения и внутри него создаем папку Classes. После создания этой папки мы обращаемся к ней и создаем файл Calc.php, который будет содержать следующий код:
Как вы можете видеть, это очень простой код. В нем просто есть функция, которая берет два числа и вычисляет сумму.
После того как это сделано, пришло время создать наш первый тест. Для этого перейдем в папку tests и в ней создадим файл CalcTest.php. Очень важно, чтобы имя файла имело формат CamelCase и заканчивалось Test.php. Это способ, который использует PHPUnit для определения того, когда это тест, а когда нет. Если он не имеет такого формата, тест выполнен не будет, поэтому не забывайте об этой детали.
После создания файла мы открываем его и добавляем следующий код:
После создания файла мы открываем его и добавляем следующий код:
Как вы видите, первое, что мы делаем, это добавляем библиотеку PHPUnit и наш класс Calc.
Следующим шагом будет создание класса, который должен называться так же, как и наш файл, и который будет расширять класс TestCase. Таким образом, мы сможем использовать функциональные возможности PHPUnit для создания наших тестов.
Внутри класса мы создадим наши тесты. В данном случае у нас есть только один под названием test_sum. Все методы, которые мы хотим запустить, должны начинаться с имени test_ и иметь формат названия snake_case, иначе PHPUnit проигнорирует их и они не будут запущены. Также каждый тест должен быть самодостаточным и не зависеть от других тестов, чтобы работать правильно.
Внутри test_sum мы создаем экземпляр класса Calc и выполняем метод sum, который возвращает результат. Для проверки теста мы будем использовать метод assertEquals, который является методом класса TestCase и в переводе означает что-то вроде “определить, равны ли они”. В этом методе в качестве первого параметра мы передаем результат, который ожидаем получить, а во втором – результат, который мы получили, чтобы подтвердить, что все в порядке.
Сейчас, когда мы создали наш тест, пришло время проверить его. Для этого переходим в наш корневой каталог и запускаем следующую команду:
Если все прошло успешно, мы должны получить сообщение о том, что все наши тесты успешно пройдены.
Кроме assertEquals, у нас есть еще много типов подтверждений, как вы можете видеть в этом списке. Например, если мы хотим подтвердить, что возвращаемый результат является целым числом, мы можем использовать AssertIsInt:
setUp() и tearDown()
Иногда бывает так, что в различных тестах мы повторяем код и в начале, и в конце, что означает, что у нас много повторяющегося кода. Для решения этой проблемы у нас есть методы setUp и tearDown. Метод setUp всегда будет запускаться перед выполнением каждого теста, а метод tearDown будет запускаться каждый раз, когда один из наших тестов завершится.
Чтобы увидеть их в действии, вернемся к классу Calc и добавим новый метод так, чтобы теперь он выглядел следующим образом:
Затем мы возвращаемся к нашему тесту и изменяем его, чтобы добавить новый тест, а также посмотреть, как применить метод setUp():
Как вы видите, мы добавили метод setUp, который будет создавать экземпляр класса каждый раз, когда выполняется тест. Таким образом, наши тесты будут выглядеть чище и понятнее.
Заключение
Идея тестов заключается в том, что мы всегда запускаем их перед загрузкой в продакшн. Таким образом нам будет легче защитить наш код от ошибок, поэтому я рекомендую вам использовать их при любой возможности.
Похожие записи
Функция загрузки файлов на сайт является одной из самых используемых в Интернете. Обычно информация о…
В этой статье о Python мы рассмотрим, как получить наименьшее общее кратное (НОК) двух чисел.…
Если вам нужно скопировать файл с помощью Python, но вы не знаете, как это сделать,…
8 незаменимых инструментов для тестирования PHP
Для получения качественного кода, мы должны выполнять тестирование во время его написания (если не использовать TDD). Однако, трудно сделать выбор среди такого широкого круга PHP инструментов тестирования.
Эта статья посвящена самым широко распространенным инструментам тестирования. В ней собрана обновленная информация по состоянию QA инструментов в 2017 году.
PHPUnit
Cucumber
Ниже приведен пример из документации, который хорошо отображает выразительность фреймворка.
Тема связана со специальностями:
Atoum
Atoum – еще один фреймворк для тестирования PHP. Это автономный пакет, который можно установить через GitHub, Composer или исполняемый файл PHAR.
Тесты Atoum очень читабельны, имеют выраженные имена методов и взаимосвязи.
Selenium
Мы можем использовать Selenium с PHPUnit, используя расширение.
Вот простой пример:
Dusk
Dusk из Laravel – еще один инструмент автоматизации браузера. Он может использоваться автономно (с chromedriver) или с Selenium. Он имеет простой в использовании API и охватывает все возможности тестирования, такие как ожидание элементов, загрузка файлов, управление мышью и т.д. Вот простой пример:
Kahlan
Kahlan – это полнофункциональная среда тестирования Unit & BDD, которая использует описательный синтаксис.
Из приведенного выше синтаксиса видно, что он похож на тесты Behat. Kahlan поддерживает Stub-инг и Mock-инг (stubbing and mocking out of the box) без зависимостей, покрытия кода, отчетности и т.д.
Видео курсы по схожей тематике:
Веб разработка на PHP Symfony
php_testability
Последний пакет, о котором мы упомянем здесь – PHP Testability. Это инструмент статического анализа, который рассказывает о проблемах с тестируемостью в вашей программе и генерирует подробный отчет.
Пакет в настоящее время не имеет помеченного релиза, на который можно полагаться, но вы можете безопасно использовать его в разработке. Вы можете установить его через Composer:
Затем запустить его следующим образом:
Сервисы непрерывной интеграции (CI)
Важная часть в предоставлении кода при работе с командами – это возможность автоматически проверять код до его объединения с официальным репозиторием проекта. Большинство доступных сервисов/инструментов CI предоставляют возможность тестировать код на разных платформах и конфигурациях, чтобы убедиться, что ваш код безопасен для слияния.
Существует множество сервисов, которые предлагают доступные цены, но вы также можете использовать инструменты с открытым исходным кодом:
• PHPCI: (с открытым исходным кодом)
• TravisCI: (бесплатно для проектов с открытым исходным кодом)
• SemaphoreCI: (бесплатно для проектов с открытым исходным кодом)
Бесплатные вебинары по схожей тематике:
Парсинг сайтов с использованием PHP
Создание web-сайта с географической базой данных фотографий
Создание базового функционала блога на Symfony 4
Заключение
Принять культуру тестирования кода сложно, но она понемногу развивается с практикой. Если вы заботитесь о своем коде, вы должны его протестировать! Перечисленные в этой статье инструменты и ресурсы помогут вам разобраться и начать работу.
Как писать легко тестируемый и поддерживаемый код на PHP
Разнообразные фреймворки предоставляют инструменты для быстрой разработки приложений, но зачастую они способствуют накоплению технического долга также быстро, как позволяют создавать функциональность.
Технический долг появляется, когда сопровождение не является главной целью разработчика. Будущие изменения и отладка кода становятся затруднительными в связи с недостаточным модульным тестированием и непроработанной структурой.
В этой статье вы узнаете, как структурировать ваш код так, чтобы достичь простоты тестируемости и сопровождения – и сэкономить ваше время.
Давайте начнем с несколько выдуманного, но типичного кода. Это может быть класс в любом заданном фреймворке:
Этот код будет работать, но требует некоторых улучшений:
1. Этот код не тестируемый.
2. Данный код не такой поддерживаемый, каким бы мог быть.
Например, если мы изменим источник данных, нам нужно будет изменять код базы данных при каждом использовании App::db в нашем приложении. Кроме того непонятно, как быть в случае, когда мы хотим использовать не просто информацию о текущем пользователе?
Предпринятый модульный тест
Здесь приведена попытка создания модульного теста для функционала, описанного выше:
Для того чтобы этот модульный тест работал, нам нужно сделать следующее:
Что ж, давайте перейдем к тому, как это можно улучшить.
Придерживаемся принципа «Не повторяйся»
Этим методом мы можем пользоваться во всем нашем приложении. Мы можем передать текущего пользователя в вызове функции, вместо того, чтобы внедрять данный функционал в нашу модель. Код становится более модульным и поддерживаемым, когда перестает зависеть от других функциональных единиц (например, глобальной переменной сессии).
Однако он все еще не такой легко тестируемый и поддерживаемый, каким бы мог быть. Мы до сих пор полагаемся на соединение с базой данных.
Внедрение зависимости
Давайте поправим ситуацию путем добавления некоторых зависимостей. Здесь показано, как может выглядеть наша модель, если мы поместим соединение с базой данных в класс:
Теперь зависимости для нашей модели User обеспечены. Наш класс больше не предполагает наличие определенного подключения к базе данных, и не полагается на какие-либо глобальные объекты.
На данный момент наш класс в целом тестируемый. Мы можем передать источник данных согласно нашему выбору (по большей части) и идентификатор пользователя и протестировать результаты вызова. Также мы можем переключать соединение к нашим отдельным базам данных (предполагая, что обе базы реализуют одинаковые методы извлечения данных). Круто!
Давайте посмотрим, как выглядит модульный тест сейчас:
Я добавил кое-что новенькое в этот модульный тест: фиктивную реализацию. Фиктивная реализация позволяет нам имитировать (фальсифицировать) PHP объекты. В данном случае мы осуществляем фиктивную реализацию подключения к базе данных. С нашей « заглушкой » мы можем пропустить тестирование подключения к базе данных и просто проверить нашу модель.
Хотите узнать больше о фиктивной реализации?
Таким образом, решаются несколько проблем:
Но мы все еще можем сделать наш код гораздо лучше. С этого места начинается самое интересное.
Интерфейсы
Для дальнейшего улучшения мы можем определить и реализовать интерфейсы. Рассмотрим следующий код:
Здесь происходит несколько вещей.
Что мы имеем в результате?
А еще мы упростили наш модульный тест!
Но мы все еще можем сделать код лучше!
Контейнеры
Рассмотрим использование нашего текущего кода:
Я переместил создание модели User в одно место в конфигурации приложения. В результате:
Заключительное слово
В нашем уроке мы выполнили следующее:
Я уверен, вы заметили, что мы добавили намного больше кода для достижения простоты обслуживания и тестируемости. Сильный аргумент против такой реализации – увеличение сложности. В самом деле, для этого требуется более глубокое знание кода, как для основного разработчика, так и для остальных участников проекта.
Однако, затраты на объяснение и понимание кода с лихвой окупаются снижением технического долга.
Код стал значительно более легким в обслуживании, представилась возможность производить изменения всего в одном месте, а не в нескольких.
Возможность быстро проводить модульное тестирование снизит количество ошибок в коде со значительным отрывом – особенно в долгосрочных или разрабатываемых в сообществе (с открытым исходным кодом) проектах.
Проделывая дополнительную работу сейчас, мы сэкономим время и освободимся от головной боли в будущем.
Источники
Затем вы можете установить ваши зависимости, основанные на Composer, со следующими требованиями:
При использовании PHP фреймворка Laravel 4 применение контейнеров и других идей, описанных здесь, носит исключительно важный характер.
PHPUnit. «Как мне протестировать мой чёртов контроллер», или тестирование для сомневающихся
Да, это очередной пост на тему тестирования. Казалось бы, что тут уже можно обсуждать? Все кому надо — пишут тесты, кому не надо — не пишут, все счастливы! Факт же в том, что большинство постов о юнит-тестировании имеют… как бы так никого не обидеть… идиотские примеры! Нет, ну правда! Сегодня я попытаюсь это исправить. Прошу под кат.
И так, быстрый гуглёж на тему тестов находит просто уйму статей, которые в своей основной массе делятся на две категории:
1) Счастье копирайтера. Сначала мы видим долгое вступление, потом историю юнит-тестирования на Древней Руси, потом десять лайфхаков с тестами, и в конце-концов пример. С тестированием кода вроде этого:
И я сейчас не шучу. Я правда видел статьи с «калькулятором» в роли учебного пособия. Да-да, я понимаю что для начала надо всё упростить, абстракции, туда-сюда… Но ведь на этом всё и заканчивается! А дальше дорисуйте сову, как говорится
2) Чрезмерно переусложнённые примеры. А давайте напишем тест, и запихнём его в Gitlab CI, а потом будем ещё автодеплоить если тест прошёл, а на тесты ещё PHP Infection намажем, да с Hudson всё соеденим. И так далее в таком стиле. Вроде и полезно, а вроде и совсем не то что ты ищешь. А ведь хочется просто чуток увеличить стабильность своего проекта. А все эти непрерывности — ну потом, не всё же сразу.
В итоге люди сомневаются, «а надо ли оно мне». Я же, в свою очередь, хочу попытаться рассказать о тестировании понятнее. И оговорюсь сразу — я разработчик, я не тестировщик. Я уверен, что я сам многого не знаю, а моим первым в жизни словом не было слово «мок». Я даже никогда не работал по TDD! Зато я точно знаю, что даже мой текущий уровень навыков позволил мне покрыть несколько проектов тестами, а эти самые тесты уже отловили свой десяток багов. А если мне это помогло — значит и кому-то ещё может помочь. Некоторые пойманные баги было бы сложно выловить вручную.
Для начала, краткий ликбез в формате вопрос-ответ:
Q: Я обязан использовать какой-то фреймворк? А что если у меня Yii? А если Kohana? А если %one_more_framework_name%?
А: Нет, PHPUnit это самостоятельный фреймворк для тестирования, вы можете его прикрутить хоть к легаси-коду на самопальном фреймворке.
Q: А я сейчас руками сайт по-быстрому прохожу, и нормально. Зачем оно мне?
А: «Прогон» нескольких десятков тестов длится несколько секунд. Автоматическое тестирование всегда быстрее мануального, а при качественных тестах ещё и надёжнее, так как покрывает все сценарии.
Q: У меня легаси-код с функциями по 2000 строк. Я могу это тестировать?
A: И да, и нет. В теории — да, любой код можно покрыть тестом. На практике, код должен писаться с заделом под будущее тестирование. Функция на 2000 строк будет иметь слишком много зависимостей, ветвлений, пограничных случаев. Может и получится её в итоге всю покрыть, но скорее всего это займёт у вас непозволительно много времени. Чем качественнее код — тем легче его тестировать. Чем лучше соблюдается принцип Single Responsibility — тем проще будут тесты. Для тестирования старых проектов чаще всего придётся сначала здорово отрефакторить их.
Q: У меня очень простые методы (функции), что там тестировать? Там всё надёжно, там нет места ошибке!
А: Следует понимать, вы не тестируете правильность реализации функции (если у вас не TDD), вы просто «фиксируете» её текущее состояние работы. В будущем, когда вам понадобится её изменять, вы сможете с помощью теста быстро определять не сломали ли вы её поведение. Пример: есть функция, которая валидирует email. Делает она это регуляркой.
Весь ваш код расчитывает на то, что если передать в эту функцию валидный имейл — она вернёт true. Массив валидных имейлов — тоже true. Массив хотя бы с одним невалидным имейлом — false. Ну и так далее, по коду суть понятна. Но настал день, и вы решили заменить монструозную регулярку внешним API. Но как гарантировать, что переписанная функция не поменяла принцип работы? Вдруг она плохо обработает массив? Или вернёт не boolean? А тесты смогут это всё держать под контролем. Хорошо написанный тест сразу укажет на поведение функции отличное от ожидаемого.
Q: Когда я начну видеть толк от тестов?
А: Во-первых, как только покроете значительную часть кода. Чем ближе покрытие к 100% — тем надёжнее тестирование. Во-вторых, как только придётся делать глобальные изменения, либо же изменения в сложной части кода. Тесты могут отловить такие проблемы, которые вручную могут быть легко упущены (пограничные случаи). Во-третьих, при написании самих тестов! Часто возникает ситуация, когда при написании теста выявляются недостатки кода, которые на первый взгляд незаметны.
Q: Ну вот, у меня сайт на laravel. Сайт это не функция, сайт это хренова гора кода. Как тут тестировать?
А: Именно об этом пойдёт речь дальше. Вкратце: отдельно тестируем методы контроллеров, отдельно middleware, отдельно сервисы, и т. д.
Одна из идей Unit-тестирования заключается в изоляции тестируемого участка кода. Чем меньше кода проверяется одним тестом — тем лучше. Посмотрим пример максимально приближённый к реальной жизни:
Эмуляция, подмена объектов называется моканьем (от англ. mock object, буквально: «объект-пародия»). Никто не мешает писать такие объекты вручную, но всё уже придумано до нас, поэтому на помощь приходит такая чудесная библиотека как Mockery. Давайте создадим моки для сервисов.
Ничего сложного, не так ли? Мы создали заглушки для необходимых параметров класса, создали экземпляр нужного класса, и «дёрнули» нужный метод, передавая заведомо неправильный запрос. Получили ответ. Но как теперь его проверить? Это и есть самая важная часть теста — так называемое утверждение, assertion. PHPUnit имеет десятки готовых assertions. Просто используем одну из них
Данный тест гарантирует следующее — если в метод логин прилетит аргумент-объект у которого не найдётся поля login или password — то метод вернёт строку «Auth error». Вот, в общем-то, и всё. Так просто — но так полезно, ведь теперь мы можем редактировать метод login без страха сломать что-то. Наш frontend может быть уверенным, что в случае чего — он получит именно такую ошибку. И если кто-то сломает это поведение (например, решит изменить текст ошибки) — то тест сразу же об этом просигнализирует! Допишем остальные проверки, чтобы покрыть как можно больше возможных сценариев.
А что же по-поводу зависимостей, спросите вы. Их то мы «заглушили», а вдруг они сломаются? А вот именно для этого и надо максимальное покрытие кода тестами. Мы не будем проверять работу этих сервисов в контексте логина — мы будем тестировать логин рассчитывая на правильную работу сервисов. А потом напишем такие же, изолированные тесты для этих сервисов. А потом тесты для их зависимостей. И так далее. В итоге каждый отдельный тест гарантирует только правильную работу маленького куска кода, при условии что все его зависимости работают правильно. А так как все зависимости тоже покрыты тестами — то их правильная работа тоже гарантируется. В итоге, любое изменение в систему ломающее логику работы даже малейшего участка кода — сразу же отобразится в том или ином тесте. Как конкретно запустить прогон тестов — рассказывать не буду, документация у PHPUnit вполне хорошая. А в Laravel, например, достаточно выполнить vendor/bin/phpunit с корня проекта, чтобы увидеть сообщение вроде этого


«Это, конечно, классно, но что из этого я не поймаю руками?» — спросите вы. А давайте для этого представим следующий код
Мы видим упрощённую модель работы с внешним API. Функция использует какой-то класс для работы c API, и в случае ошибки — возвращает null. Если же при использовании этой функции мы получаем null — следует «поднять панику» (отправить сообщение в слак, или имейл разработчику, или кинуть ошибку в кибану. Да куча вариантов). Вроде всё просто, не так ли? Но представим что через некоторое время другой разработчик решил «поправить» эту функцию. Он решил что возвращать null — это прошлый век, и следует кидать исключение.
И он даже переписал все участки кода, где вызывалась эта функция! Все, кроме одного. Его он упустил. Отвлёкся, устал, просто ошибся — да мало ли. Факт лишь в том, что один участок кода всё ещё ожидает старого поведения функции. А PHP это у нас не Java — мы не получим ошибку компиляции на основании того, что throwable функция не завёрнута в try-catch. В итоге в одном из 100 сценариев использования сайта, в случае падения API — мы не получим сообщение от системы. Более того, при ручном тестировании мы скорее всего не отловим этот вариант события. API у нас внешнее, от нас не зависит, работает хорошо — и скорее всего мы не попадём «руками» на случай отказа API, и неверной обработки исключения. Зато будь у нас тесты — они отлично отловят данный кейс, потому что класс ExternalApi в ряде тестов у нас «заглушен», и эмулирует как нормальное поведение, так и падение. И следующий тест у нас упадёт
Этой информации, на самом деле, достаточно. Если у вас не легаси лапша, уже спустя минут 20-30 вы сможете написать свой первый тест. А спустя несколько недель — узнать что-то новое, крутое, вернуться в комментарии под этот пост, и написать какой автор говнокодер, и не знает о %framework_name%, и тесты хреновые пишет, а надо делать %this_way%. И я буду очень рад в таком случае. Это будет значит что моя цель достигнута: кто-то ещё открыл для себя тестирование, и немножечко повысил общий уровень профессионализма в нашей сфере!





























