Страницы

Поиск по вопросам

Показаны сообщения с ярлыком phpunit. Показать все сообщения
Показаны сообщения с ярлыком phpunit. Показать все сообщения

четверг, 2 января 2020 г.

Можно мокать тестируемый обьект?

#php #юнит_тесты #phpunit #любой_язык


Есть:


Класс который нужно протестировать

  class Message extends Data
  {
     protected $text = '';

     public function check()
     {
        return empty($this->text);
     }

     public function setText($text)
     {
        if ($this->check() === true) {
           $this->text = $text;
        } else {
           $this->text .= $text;
        }
     }
  }

Разработчики Вася и Петя, которые спорят как правильно написать
тест для setText, нужно мокать метод check или нет?


Вася: за mock check

При юнит тестировании нужно мокать все что возможно кроме спрятанных данных (private,
protected), что бы протестировать все сценарии которые не зависят друг от друга. Итого
будет 3 сценария, где мок check вернет либо true, false или иное, для метода setText.
И отдельно протестировать метод check. Также незабыть компонентный тест, который протестирует
обьект (Message) в общем, то есть интерфейс этого объекта, не мокая ничего что касаеться
Message и Data.

Петя: против mock check

Нельзя мокать методы тестируемого класса (Message) и его родителей (Data), можно
только внешние классы. При изменении метода check реально сломается setText но юнит
об этом не сообщит. Сами юниты будут показывать как работает класс, ненужно делать
компонентные тесты только на один объект, но сами тесты будут дублировать в себе проверку
одних и тех же внутренних зависимостей (метод check, будет 2 раза протестирован)

Вопрос:

Кто из них прав, или как правильно тестировать?

Примеченные:

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


Ответы

Ответ 1



За идею мокать тестируемый класс я бы сразу расстреливал. Тестируемый класс — это единое целое, и должно тестироваться как единое целое. Пролезая своими грязными ручками в метод check, вы предполагете конкретную реализацию метода setText. Это не юнит-тест, это профанация: вы тестируете не внешний интерфейс класса, а проверяете, что операции в нём написаны точно так же, как в тесте. Где в интерфейсе класса говорится, что setText должен вызывать check, а не кэшировать состояние к какой-то переменной? Теперь вы не можете изменить реализацию при сохранении интерфейса и поведения, не сломав юнит-тесты. Так зачем же юнит-тесты, которые ломаются от изменения внутренней реализации? Они только раздражать будут. Вот если какие-то методы тестируемого класса будет сложно тестировать (они будут лезть на внешние ресурсы, например), то тогда уже можно подумать о моках. Но в этом случае, скорее всего, логику залезания на внешние ресурсы надо выделить в отдельный класс и мокать уже его. Я вообще сторонник идей Мартина Фаулера: мокать нужно только то, что слишком сложно не мокать: базы данных, рассылку почты и т. п. Это позицию он называет "классическим TDD". Каждый раз, когда вы втыкаете мок в тестируемый код, вы подменяете реальное поведение, вы предполагете реализацию, вы имитируете поведение и делаете ошибки. Ваш код тестирует не реальную систему, а имитацию. Да, тесты с минимумом моков чаще ломаются охапкой, а не поодиночке. Но зато они ломаются! И если они ломаются, то сразу видно, что где-то есть реальная ошибка. Если же тесты тестируют реализацию, то они ломаются поодиночке, но общие для нескольких классов ошибки не ловятся, а проваленные тесты часто просто указывают на изменение реализации. "Мокеры" любят плодить миниатюрные юнит-тесты на каждый геттер и сеттер, но забывают, что классы существуют не в безвоздушном пространстве, а взаимодействуют. В результате у каждого класса покрытие 99%, а вместе почему-то всё ломается. Нельзя забывать про интеграционные тесты. И юнит-тесты, которые "слегка интеграционные" — это отличный путь. Не бойтесь вызвать метод из десятка разных мест: чем больше реальных вызовов, тем больше уверенности в надежности системы. Что касается конкретно вашего кода, то у вас метод выглядит как сеттер, но по сути им не является. Это провал теста до написания каких-либо тестов.

Ответ 2



В данном конкретном случае я бы изменил интерфейс. Метод setText выглядит как обычный сеттер, но в действительности сеттером не является. Очень похоже, что его стоит разбить на методы clearText и appendText, и вынести логику, спрятанную за check, выше. Спор Васи с Петей косвенно свидетельствует о проблемах в проекте. Позиция Пети сильнее (нельзя мокать методы тестируемого класса), поскольку она соответствует правилам декомпозиции, описанным у книге дядюшки Боба Мартина «Чистый код». Если кратко: на одном слое декомпозиции должны находиться методы одного логического уровня, такой код гораздо проще читать. В Вашем примере всё выглядит так, что check должен находиться где-то выше, где его и надо тестировать. И вот там можно мокать класс Message, который находится на уровень ниже. И здесь, на уровне Message, мы можем протестировать методы clearText и appendText, возможно, мокая какие-то классы ниже. P.S. Замечу, что в реальных проектах не всегда возможно правильно позиционировать сущности по уровням, так что Вася с Петей рискуют до хрипоты обсуждать и этот вопрос. P.P.S. Заметил, что речь идёт о моках. Я по возможности следую такому правилу: сначала протестируй непосредственно, если не выходит, и нельзя перепроектировать, задействуй стаб, если и так не выходит, задействуй мок. Таким образом простые валидирующие или трансформирующие методы, которые работают с объектами стандартной библиотеки (строки, даты, регулярные выражения) перетекают в классы-утилиты, где и тестируются.

Ответ 3



У вашего класса не полный API. Добавьте метод getText и тестируйте поведение комплексно. Как вариант можно использовать следующие тесты: На чистом объекте, убедиться, что check возвращает false. На чистом объекте, убедиться, что getText возвращает ''. После setText метод check возвращает true. После setText метод getText возвращает правильную строку. После повторного setText метод getText возвращает правильную строку. Небольшое отступление: Некоторые методы очень трудно тестировать в изоляции друг от друга и это нормально. Типичный пример -- контейнер: class Container { private $data; public function get(key) {/* ... */} public function set(key, value) {/* ... */} } Методы get и set просто нельзя протестировать отдельно друг от друга. В таких случаях имеет смысл тестировать поведение объекта (либо группы методов). Кроме того, правильнее подходить к методам как к маленьким "черным ящикам". Тесты ни в коем случае не должны зависеть от реализации метода. В вашем примере, стоит вам заменить вызов check на что-то еще и ваши тесты сломаются, хотя поведение метода setText останется прежним. Тестирование не должно быть самоцелью. Поэтому, использовать какие-то хитрые приемы (моки тестируемого объекта, наследование, с целью раскрытия структуры и прочее) в большинстве случаев неправильно. Тесты -- это всего лишь инструмент, позволяющий гарантировать, что код работает.

воскресенье, 15 декабря 2019 г.

Запустить PHPUnit тесты из docker контейнера через PhpStorm

#php #phpstorm #composer #docker #phpunit


Что есть:


Docker-контейнер с PHP и набором юнит-тестов. Можно запустить контейнер, и внутри
через консоль запускать любые тесты - phpunit /test/test_1.php
хостовая машина с установленным PhpStorm 9
папка с проектом, где лежат в том числе юнит-тесты. Эта папка залинкована в докер-контейнер.


Проблема:

PhpStorm удобно менеджерит тесты, и позволяет запускать локальные или удаленные тесты
(посредством SSH). Но с докером работать не умеет, не получается обьяснить IDE как
запускать тесты, лежащие в докере.

Что нужно:

Как поднастроить докер или PhpStorm, чтобы через GUI можно было запускать тесты.

Дополнения:


по ssh работает сейчас, но хотелось бы обойтись без него.
пробовал создать bash-скрипт, который проксирует все запросы в контейнер. Вот такой
скрипт docker run --rm php:cli php $@. Таким образом начинает работать команда php
-v запущенная с хостовой машины. Но тесты используют аргументы файлы. Усложнил баш-скрипт:

#!/bin/sh

args=''
for arg
do
   if [ -f $arg ]; then
        arg=/mnt$( realpath $arg )
   fi
   args="$args $arg"
done
env > /tmp/docker-env
sed -i s/idekey=.*/idekey=PHPSTORM/ /tmp/docker-env
docker run -e "PHP_IDE_CONFIG=serverName=phpunit-docker" \
   --net=host --env-file /tmp/docker-env --rm \
   -v /:/mnt -v /var/www:/var/www app php $args

Это решает несколько проблем


можно создать php.sh с этим кодом, и положить в любое место, например в /usr/local/bin,
и обращаться с ним вроде это настоящий php
phpstorm вызывая тесты создает /tmp/ide-phpunit.php который принимает env переменные
которые настраиваются в самой ide, поэтому я использую /tmp/docker-env
настройка --net=host решает все проблемы с сетью, например nslookup раньше выпадал
с ошибкой

Это не решает проблемы


явного указания idekey=PHPSTORM и PHP_IDE_CONFIG

Но всё равно PHPUnit тесты не запускаются, ругается composer


  PHP Fatal error:  Cannot redeclare    composerRequire7a368ac394ae1d2e857becf2a235ebaa()
(previously declared in    [APP_ROOT]/vendor/composer/autoload_real.php:56) in    [APP_ROOT]/vendor/composer/autoload_real.php
on line 59


Я подозреваю что это происходит потому что /tmp/ide-phpunit.php вызывает composer/autoload
для нахождения phpunit, и при запуске тесты тоже запускают этот же

    


Ответы

Ответ 1



В PhpStorm можно настроить запуск тестов через SSH. Ниже инструкция, как запустить SSH-сервер в debian/ubuntu контейнере (docker image php:cli - это debian). Зайдите в контейнер. Допустим, он называется phpapp docker exec -it phpapp bash Устанавливаем SSH-сервер apt-get update; apt-get install openssh-server Запускаем SSH-сервер. Почему-то нужно указывать полный путь. /usr/sbin/sshd Если будет ругаться, возможно, ему нужно создать какой-то каталог перед запуском mkdir /var/run/sshd Добавляем пользователя adduser test Выходим из контейнера - Ctrl+D Пробрасываем 22-й порт контейнера на какой-то из локальных портов, например, 127.0.0.1:10022 (вместо 10022 можно выбрать любое число 1025-65535). Если запускаете на Windows через VirtualBox/Vagrant/docker-machine, то пробрасывайте на 0.0.0.0:10022 и в VirtualBox настройте проброску локального порта 10022 на порт 10022 в вашем VirtualBox. Логинимся в контейнер - ssh test@127.0.0.1:10022, где test - это пользователь, которого вы создали. Устанавливать SSH в Docker считается плохим тоном, но пока в PhpStorm простая и красивая интеграция с PHPUnit (не просто консоль, а список тестов и coverage) работает только через SSH. На production так не делайте.

Ответ 2



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

вторник, 2 октября 2018 г.

Как запустить тест на Yii2?

Разбираюсь с новым Yii2, подтянул вот этот репозиторий: https://github.com/yiisoft/yii2
Далее подтянул файлы с помощью composer в приложении advanced
Начал смотреть в сторону тестов, ранее никогда с ними не работал, вот и возникли трудности с пониманием этого дела.
Так у нас есть папки с тестами в следующих директориях: backend, common, console, frontend
Я новичок, поэтому сразу хочу начать с самого простого, поэтому будем пока работать с папкой \frontend\tests
Я уже установил у себя phpunit и хочу запустить первый тест для теста =).
Содержимое данной папки такое: https://github.com/yiisoft/yii2/tree/master/apps/advanced/frontend/tests
Честно я почти ничего не понял, что у нас тут и зачем, скорее всего может разные фраэмворки тестов используются, но увидел знакомое название unit и полез туда.
НУ и дальше мои полномочия все. Тоесть подскажите пожалуйста как написать свой первый тест, что там как вообще происходит ?


Ответ

Пришло время обновить ответ. Оригинальную версию можно будет увидеть внизу. Codeception PHPUnit сам по себе никуда не делся, однако ни одно приложение не в состоянии выжить только за счет unit-тестов. Со временем появился Codeception, который представляет из себя большую надстройку над PHPUnit, Selenium и еще кучи пакетов, в которые я сам не заглядывал. Codeception предоставляет три вида тестирования: Acceptance - "приемочное" тестирование, проверка работы пользователя с приложением на основе сценариев (BDD). Functional - функциональное тестирование, функциональная проверка работы приложения. Unit - модульное тестирование, низкоуровневое тестирование конкретных функций. Unit testing С модульным тестированием проще всего - это проверка конкретных функций: создаем массив данных из того, что должно поступить на вход и что должно получиться на выходе, потом прогоняем по этим данным функцию и смотрим, все ли получилось как хотели. Модульное тестирование не ограничивается только этим, но оно рассчитано именно на проверку отдельных функций, методов и модулей, точную сверку выходных данных с требуемыми, проверку состояний тех же модулей. Functional testing Функциональное тестирование появилось тогда, когда стало ясно, что кроме проверки функций необходимо проверять именно работу приложения - что оно не выдает 404 для существующих данных, выдает 403 для закрытых секций, выдает 400 на неправильно введенных данных. Функциональное тестирование постепенно развивалось - коды ответа кодами ответа, а разработчиков интересует именно получение пользователем той или иной информации на странице. Здесь и появилась такая штука, как selenium - сервер, который позволяет запускать браузер и взаимодействовать с ним, и webspider - эмуляция браузера, которая позволяет так же "ходить по страницам". Acceptance testing Codeception же несколько переформатировал понятие функционального тестирования и ввел acceptance testing. В codeception функциональное тестирование - это тестирование страниц через веб-спайдера, acceptance testing - тестирование страниц через браузер. Формально эти тесты могут совпадать с точностью до названия класса, но acceptance testing способен проверять реальную видимость html-элементов на странице и отслеживать работу javascript. Yii первой версии существовал в отрыве от общих стандартов в пользу удобства использования (хотя, честно говоря, не знаю, были ли тогда стандарты). Yii 2 пришел и к неймспейсам, и к обильному использованию чужих пакетов, в том числе codeception. Codeception ставится так же, как и все остальные зависимости Yii - через composer Как использовать codeception? Codeception, как и большинство современных пакетов, предлагает работать с ним через командную строку. Основной исполняемый файл - app_dir/vendor/codeception/codeception/codecept, для удобства я буду писать просто codecept. Проще всего установить codeception глобально и настроить команду-алиас на соответсвующий файл. Сначала необходимо проинциализировать приложение: codecept bootstrap Это создаст папку tests и создаст внутри всю необходимую инфраструктуру. В примерах Yii это уже будет сделано. После этого необходимо настроить codeception с помощью YAML-файлов в tests, но это лучше смотреть сразу в документации Codeception предоставляет возможность писать тесты как на основе PHPUnit_Framework_TestCase (CTestCase из Yii 1.х был оберткой вокруг него), так и на основе своей обертки \Codeception\TestCase\Test. Создать тест проще всего опять же из консоли (предположим, что тест создается для модуля HttpRequest) codecept generate:test unit HttpRequest // Test was created in %app.root%/tests/unit/HttpRequestTest.php Codeception сам добавляет суффикс Test (который позволяет отделять классы-тесты) и расширение .php. Так как скорее всего тестов будет много, можно сразу указать папку, где должен валяться тест. Предположим, полное название HttpRequest на самом деле \Core\Http\HttpRequest и валяется он в папке %app.root%/Core/Http, тогда будет разумно поместить тест в папку %app.root%/tests/Core/Http codecept generate:test unit Core/Http/HttpRequest // Test was created in %app.root%/tests/unit/Core/Http/HttpRequestTest.php Прекрасно, а что мне теперь писать в этом файле? Внутри файл находится класс теста. Этот класс представляет из себя набор методов-тестов, которые проверяют поведение кода, и дополнительных методов инфраструктуры. Каждый метод-тест начинается с префикса test (наприме, testValidate; хорошим тоном будет подставлять название тестируемого метода после префикса, если в тесте проверяется только один метод). PHPUnit посканирует класс и обнаружит все методы, начинающиеся с этого префикса, а затем будет запускать один за другим. Каждый метод теста проверяет поведение кода, используя assert-методы PHPUnit, каждый такой метод проверяет полученные данные. Например, $this->assertTrue($httpRequest->isPostRequest); проверит, соответствует ли true значение $httpRequest->isPostRequest, и в случае, если проверка провалится, оборвет выполнение теста (т.е. только одного метода, если не используется @depends). По поводу "инфраструктурных" методов: любое тестирование сталкивается с тем, что для тестирования требуется настроить окружение, загрузить какие-то данные, создать фальшивые объекты, создать массивы однотипных данных для проверки. Самыми главными здесь будут _before() и _after() (setUp() / tearDown() в PHPUnit) - они будут выполняться до и после выполнения каждого теста в классе, в них обычно выполняет подготовку фальшивого окружения/объектов. Пример класса-теста: class HttpRequestTest extends \Codeception\TestCase\Test { protected $codeGuy; public static function setUpBeforeClass() { $_SERVER['REQUEST_METHOD'] = 'POST'; // в консоли, вероятнее всего, этого ключа изначально вообще не будет } public static function tearDownAfterClass() { unlink('dummy.html'); // стираемый созданный тестом файл } public function testIsPostRequest() { $request = new HttpRequest; // создаем тестируемый объект. В зависимости от характера теста, создание, возможно, будет разумнее вынести в _before() $this->assertTrue($request->isPostRequest); // выполняем непосредственно проверку поведения, сравнивая isPostRequest с true } } А как запустить написанное? Как всегда - из командной строки. Следующая команда запустит все тесты: codecept run Т.к. часто придется обращаться к отдельному виду тестирования или вообще тесту (если отвалился единственный модуль, то толку-то все гонять?), то лучше запускать тесты по этому виду или вообще конкретный тест: codecept run unit // все юнит-тесты codecept run tests/unit/Core/Http/HttpRequestTest.php // конкретный тест, требуется относительный путь с суффиксом и расширением. После этого codeception выведет количество тестов, проверок (один тест может включать в себя несколько проверок) и ошибок. Понятное дело, что любое количество ошибок, отличное от нуля, должно восприниматься как build: failed, и соответствующий код должен немедленно исправляться. Эй, ты ничего не рассказал про остальные два вида тестирования! Потому что сам в них толком пока не поварился (а еще мне лень). В принципе, базовый getting started и любая IDE с подсветкой подскажут что к чему - они ОЧЕНЬ интуитивны. (предыдущая версия текста про PHPUnit) Модульные тесты (unit tests) в PHP в любом фреймворке всегда упираются в один и тот же пакет: phpunit. С чем бы вы не столкнулись, дело придется иметь именно с ним, возможно, в небольшой обертке. Сама суть модульного тестирования заключается примерно в следующем: при разработке какого-то приложения после появления (или изменения) запланированного функционала этого приложения параллельно с самим приложением пишется тестовое сопровождение. Тестовое сопровождение - это, как правило, набор классов, которые по наименованию и расположению повторяют тестируемое приложение, например: Приложение: models/ProductModel.php components/CrmService.php Тесты: tests/models/ProductModelTest.php tests/components/CrmServiceTest.php (контроллеры тут пока специально не упомянуты) Каждый класс - это набор тестов для соответствующего компонента, реально этот набор представлен методами внутри класса теста для этого компонента. PHPUnit автоматом запускает все методы класса, которые начинаются с test (и некоторые другие, но об этом в документации), поэтому типичный класс теста будет представлять собой примерно такую картину: class ProductModelTest extends TestCase { // в данном случае TestCase - та самая обертка, которая будет наследоваться от PHPUnit_Framework_TestCase public function testGet() // негласная конвенция - называть метод по имени тестируемого метода {...} // проверка всевозможных комбинаций входных и выходных значений для метода ProductModel::get() public function testGetAll() {...} public function testValidation() {...} } Теперь непосредственно к yii2. Само приложение advanced представляет из себя просто скелет (точнее, я нашел в common один тест, но он не смог найти codeception), поэтому нам сначала надо будет написать какое-нибудь подобие модели, например, такое. В этой псевдомодели нужно оттестировать все методы, чтобы а) не забыть о недописанных в будущем б) быть уверенным, что в любых стрессовых ситуациях система поведет себя именно так, как надо и в) быть оповещенным ровно в тот момент, как что-то отвалится из-за переписывания низкоуровневых компонент. Соответственно, каждый метод (благо их здесь всего ничего) нужно нашпиговать всеми возможными приходящими значениями и убедиться в корректности отработки (точнее, шпиговать его каждый раз, как что-то поменялось). Я написал для этой модели небольшой и дурацкий тест (ссылка), который занимается ровно этим. Теперь можно проверить работоспособность всей этой конструкции, если модель закинуть в common/models/ProductModel.php, а тест в common/tests/unit/models/ProductModelTest.php. После этого в директории common/tests надо будет выполнить следующую комманду: phpunit --bootstrap _bootstrap.php unit/models/ProductModelTest.php Это запустит соответствующий тест (по умолчанию phpunit будет пытаться загрузить все файлы с названием *Test.php из текущей папки и подпапок; подробнее, как всегда, в доках) и даст примерно следующий вывод: PHPUnit 3.6.10 by Sebastian Bergmann. ....... Time: 0 seconds, Memory: 3.00Mb OK (7 tests, 18 assertions) Который говорит о том, что 7 тестов прошли успешно. Когда я писал модель, я в проверке на id вместо < 1 указал < 0, и в результате все невалидные айдишники прошли, о чем меня и уведомила тестовая проверка: PHPUnit 3.6.10 by Sebastian Bergmann. F...... Time: 0 seconds, Memory: 3.00Mb There was 1 failure: 1) common\tests\unit\models\ProductModelTest::testGet Exception was expected for string input /srv/sandbox/yii2/apps/advanced/common/tests/unit/models/ProductModelTest.php:14 FAILURES! Tests: 7, Assertions: 6, Failures: 1. Такой вот сумбурный ответ. Что касается остальных папок - это файлы для поддержки codeception (в папках с подчерком в начале названия), с которым я сам пока не разобрался (это тестирование более высокого уровня, включающее в себя phpunit), и файлы для различных видов тестирования. На самом деле PHPUnit годится только для библиотек, а для приложений является полумерой, пусть и довольно хорошо защищающей - в приложении должны работать не только модели, но и контроллеры, а для этого недостаточно сымитировать ситуацию - нужно поднять nginx/apache (а лучше оба) и убедиться, что на конкретные запросы приходят верные ответы. Это называется функциональным тестированием, до чего я сам пока не дошел, и осуществляется с помощью загадочной для меня (пока что) программы selenium. Папка functional, соответственно, отвечает за функциональное тестирование. acceptance, насколько понял - это папка для codeception-тестов, который идет еще дальше, и вместо простой проверки запросов/ответов эмулирует клики и отображение для пользователя. Все вместе это называется Continuous Integration (CI) и обеспечивает невозможность выложить в продакшен с некислой посещаемостью и функционалом какой-нибудь серьезный ляп. Я по выходным все пытаюсь развернуть себе полноценный сервер с поддержкой всего этого веселья, но реально в проектах мне пока хватало PHPUnit - остальное, конечно, полезно, но проекты не того масштаба ) В любом случае для ознакомления с тестированием придется сначала поработать только с PHPUnit, поэтому об остальном можно пока не задумываться.

понедельник, 1 октября 2018 г.

Как запустить тест на Yii2?

Разбираюсь с новым Yii2, подтянул вот этот репозиторий: https://github.com/yiisoft/yii2
Далее подтянул файлы с помощью composer в приложении advanced
Начал смотреть в сторону тестов, ранее никогда с ними не работал, вот и возникли трудности с пониманием этого дела.
Так у нас есть папки с тестами в следующих директориях: backend, common, console, frontend
Я новичок, поэтому сразу хочу начать с самого простого, поэтому будем пока работать с папкой \frontend\tests
Я уже установил у себя phpunit и хочу запустить первый тест для теста =).
Содержимое данной папки такое: https://github.com/yiisoft/yii2/tree/master/apps/advanced/frontend/tests
Честно я почти ничего не понял, что у нас тут и зачем, скорее всего может разные фраэмворки тестов используются, но увидел знакомое название unit и полез туда.
НУ и дальше мои полномочия все. Тоесть подскажите пожалуйста как написать свой первый тест, что там как вообще происходит ?


Ответ

Пришло время обновить ответ. Оригинальную версию можно будет увидеть внизу. Codeception PHPUnit сам по себе никуда не делся, однако ни одно приложение не в состоянии выжить только за счет unit-тестов. Со временем появился Codeception, который представляет из себя большую надстройку над PHPUnit, Selenium и еще кучи пакетов, в которые я сам не заглядывал. Codeception предоставляет три вида тестирования: Acceptance - "приемочное" тестирование, проверка работы пользователя с приложением на основе сценариев (BDD). Functional - функциональное тестирование, функциональная проверка работы приложения. Unit - модульное тестирование, низкоуровневое тестирование конкретных функций. Unit testing С модульным тестированием проще всего - это проверка конкретных функций: создаем массив данных из того, что должно поступить на вход и что должно получиться на выходе, потом прогоняем по этим данным функцию и смотрим, все ли получилось как хотели. Модульное тестирование не ограничивается только этим, но оно рассчитано именно на проверку отдельных функций, методов и модулей, точную сверку выходных данных с требуемыми, проверку состояний тех же модулей. Functional testing Функциональное тестирование появилось тогда, когда стало ясно, что кроме проверки функций необходимо проверять именно работу приложения - что оно не выдает 404 для существующих данных, выдает 403 для закрытых секций, выдает 400 на неправильно введенных данных. Функциональное тестирование постепенно развивалось - коды ответа кодами ответа, а разработчиков интересует именно получение пользователем той или иной информации на странице. Здесь и появилась такая штука, как selenium - сервер, который позволяет запускать браузер и взаимодействовать с ним, и webspider - эмуляция браузера, которая позволяет так же "ходить по страницам". Acceptance testing Codeception же несколько переформатировал понятие функционального тестирования и ввел acceptance testing. В codeception функциональное тестирование - это тестирование страниц через веб-спайдера, acceptance testing - тестирование страниц через браузер. Формально эти тесты могут совпадать с точностью до названия класса, но acceptance testing способен проверять реальную видимость html-элементов на странице и отслеживать работу javascript. Yii первой версии существовал в отрыве от общих стандартов в пользу удобства использования (хотя, честно говоря, не знаю, были ли тогда стандарты). Yii 2 пришел и к неймспейсам, и к обильному использованию чужих пакетов, в том числе codeception. Codeception ставится так же, как и все остальные зависимости Yii - через composer Как использовать codeception? Codeception, как и большинство современных пакетов, предлагает работать с ним через командную строку. Основной исполняемый файл - app_dir/vendor/codeception/codeception/codecept, для удобства я буду писать просто codecept. Проще всего установить codeception глобально и настроить команду-алиас на соответсвующий файл. Сначала необходимо проинциализировать приложение: codecept bootstrap Это создаст папку tests и создаст внутри всю необходимую инфраструктуру. В примерах Yii это уже будет сделано. После этого необходимо настроить codeception с помощью YAML-файлов в tests, но это лучше смотреть сразу в документации Codeception предоставляет возможность писать тесты как на основе PHPUnit_Framework_TestCase (CTestCase из Yii 1.х был оберткой вокруг него), так и на основе своей обертки \Codeception\TestCase\Test. Создать тест проще всего опять же из консоли (предположим, что тест создается для модуля HttpRequest) codecept generate:test unit HttpRequest // Test was created in %app.root%/tests/unit/HttpRequestTest.php Codeception сам добавляет суффикс Test (который позволяет отделять классы-тесты) и расширение .php. Так как скорее всего тестов будет много, можно сразу указать папку, где должен валяться тест. Предположим, полное название HttpRequest на самом деле \Core\Http\HttpRequest и валяется он в папке %app.root%/Core/Http, тогда будет разумно поместить тест в папку %app.root%/tests/Core/Http codecept generate:test unit Core/Http/HttpRequest // Test was created in %app.root%/tests/unit/Core/Http/HttpRequestTest.php Прекрасно, а что мне теперь писать в этом файле? Внутри файл находится класс теста. Этот класс представляет из себя набор методов-тестов, которые проверяют поведение кода, и дополнительных методов инфраструктуры. Каждый метод-тест начинается с префикса test (наприме, testValidate; хорошим тоном будет подставлять название тестируемого метода после префикса, если в тесте проверяется только один метод). PHPUnit посканирует класс и обнаружит все методы, начинающиеся с этого префикса, а затем будет запускать один за другим. Каждый метод теста проверяет поведение кода, используя assert-методы PHPUnit, каждый такой метод проверяет полученные данные. Например, $this->assertTrue($httpRequest->isPostRequest); проверит, соответствует ли true значение $httpRequest->isPostRequest, и в случае, если проверка провалится, оборвет выполнение теста (т.е. только одного метода, если не используется @depends). По поводу "инфраструктурных" методов: любое тестирование сталкивается с тем, что для тестирования требуется настроить окружение, загрузить какие-то данные, создать фальшивые объекты, создать массивы однотипных данных для проверки. Самыми главными здесь будут _before() и _after() (setUp() / tearDown() в PHPUnit) - они будут выполняться до и после выполнения каждого теста в классе, в них обычно выполняет подготовку фальшивого окружения/объектов. Пример класса-теста: class HttpRequestTest extends \Codeception\TestCase\Test { protected $codeGuy; public static function setUpBeforeClass() { $_SERVER['REQUEST_METHOD'] = 'POST'; // в консоли, вероятнее всего, этого ключа изначально вообще не будет } public static function tearDownAfterClass() { unlink('dummy.html'); // стираемый созданный тестом файл } public function testIsPostRequest() { $request = new HttpRequest; // создаем тестируемый объект. В зависимости от характера теста, создание, возможно, будет разумнее вынести в _before() $this->assertTrue($request->isPostRequest); // выполняем непосредственно проверку поведения, сравнивая isPostRequest с true } } А как запустить написанное? Как всегда - из командной строки. Следующая команда запустит все тесты: codecept run Т.к. часто придется обращаться к отдельному виду тестирования или вообще тесту (если отвалился единственный модуль, то толку-то все гонять?), то лучше запускать тесты по этому виду или вообще конкретный тест: codecept run unit // все юнит-тесты codecept run tests/unit/Core/Http/HttpRequestTest.php // конкретный тест, требуется относительный путь с суффиксом и расширением. После этого codeception выведет количество тестов, проверок (один тест может включать в себя несколько проверок) и ошибок. Понятное дело, что любое количество ошибок, отличное от нуля, должно восприниматься как build: failed, и соответствующий код должен немедленно исправляться. Эй, ты ничего не рассказал про остальные два вида тестирования! Потому что сам в них толком пока не поварился (а еще мне лень). В принципе, базовый getting started и любая IDE с подсветкой подскажут что к чему - они ОЧЕНЬ интуитивны. (предыдущая версия текста про PHPUnit) Модульные тесты (unit tests) в PHP в любом фреймворке всегда упираются в один и тот же пакет: phpunit. С чем бы вы не столкнулись, дело придется иметь именно с ним, возможно, в небольшой обертке. Сама суть модульного тестирования заключается примерно в следующем: при разработке какого-то приложения после появления (или изменения) запланированного функционала этого приложения параллельно с самим приложением пишется тестовое сопровождение. Тестовое сопровождение - это, как правило, набор классов, которые по наименованию и расположению повторяют тестируемое приложение, например: Приложение: models/ProductModel.php components/CrmService.php Тесты: tests/models/ProductModelTest.php tests/components/CrmServiceTest.php (контроллеры тут пока специально не упомянуты) Каждый класс - это набор тестов для соответствующего компонента, реально этот набор представлен методами внутри класса теста для этого компонента. PHPUnit автоматом запускает все методы класса, которые начинаются с test (и некоторые другие, но об этом в документации), поэтому типичный класс теста будет представлять собой примерно такую картину: class ProductModelTest extends TestCase { // в данном случае TestCase - та самая обертка, которая будет наследоваться от PHPUnit_Framework_TestCase public function testGet() // негласная конвенция - называть метод по имени тестируемого метода {...} // проверка всевозможных комбинаций входных и выходных значений для метода ProductModel::get() public function testGetAll() {...} public function testValidation() {...} } Теперь непосредственно к yii2. Само приложение advanced представляет из себя просто скелет (точнее, я нашел в common один тест, но он не смог найти codeception), поэтому нам сначала надо будет написать какое-нибудь подобие модели, например, такое. В этой псевдомодели нужно оттестировать все методы, чтобы а) не забыть о недописанных в будущем б) быть уверенным, что в любых стрессовых ситуациях система поведет себя именно так, как надо и в) быть оповещенным ровно в тот момент, как что-то отвалится из-за переписывания низкоуровневых компонент. Соответственно, каждый метод (благо их здесь всего ничего) нужно нашпиговать всеми возможными приходящими значениями и убедиться в корректности отработки (точнее, шпиговать его каждый раз, как что-то поменялось). Я написал для этой модели небольшой и дурацкий тест (ссылка), который занимается ровно этим. Теперь можно проверить работоспособность всей этой конструкции, если модель закинуть в common/models/ProductModel.php, а тест в common/tests/unit/models/ProductModelTest.php. После этого в директории common/tests надо будет выполнить следующую комманду: phpunit --bootstrap _bootstrap.php unit/models/ProductModelTest.php Это запустит соответствующий тест (по умолчанию phpunit будет пытаться загрузить все файлы с названием *Test.php из текущей папки и подпапок; подробнее, как всегда, в доках) и даст примерно следующий вывод: PHPUnit 3.6.10 by Sebastian Bergmann. ....... Time: 0 seconds, Memory: 3.00Mb OK (7 tests, 18 assertions) Который говорит о том, что 7 тестов прошли успешно. Когда я писал модель, я в проверке на id вместо < 1 указал < 0, и в результате все невалидные айдишники прошли, о чем меня и уведомила тестовая проверка: PHPUnit 3.6.10 by Sebastian Bergmann. F...... Time: 0 seconds, Memory: 3.00Mb There was 1 failure: 1) common\tests\unit\models\ProductModelTest::testGet Exception was expected for string input /srv/sandbox/yii2/apps/advanced/common/tests/unit/models/ProductModelTest.php:14 FAILURES! Tests: 7, Assertions: 6, Failures: 1. Такой вот сумбурный ответ. Что касается остальных папок - это файлы для поддержки codeception (в папках с подчерком в начале названия), с которым я сам пока не разобрался (это тестирование более высокого уровня, включающее в себя phpunit), и файлы для различных видов тестирования. На самом деле PHPUnit годится только для библиотек, а для приложений является полумерой, пусть и довольно хорошо защищающей - в приложении должны работать не только модели, но и контроллеры, а для этого недостаточно сымитировать ситуацию - нужно поднять nginx/apache (а лучше оба) и убедиться, что на конкретные запросы приходят верные ответы. Это называется функциональным тестированием, до чего я сам пока не дошел, и осуществляется с помощью загадочной для меня (пока что) программы selenium. Папка functional, соответственно, отвечает за функциональное тестирование. acceptance, насколько понял - это папка для codeception-тестов, который идет еще дальше, и вместо простой проверки запросов/ответов эмулирует клики и отображение для пользователя. Все вместе это называется Continuous Integration (CI) и обеспечивает невозможность выложить в продакшен с некислой посещаемостью и функционалом какой-нибудь серьезный ляп. Я по выходным все пытаюсь развернуть себе полноценный сервер с поддержкой всего этого веселья, но реально в проектах мне пока хватало PHPUnit - остальное, конечно, полезно, но проекты не того масштаба ) В любом случае для ознакомления с тестированием придется сначала поработать только с PHPUnit, поэтому об остальном можно пока не задумываться.