{{TOC}} {{DOCVER 5.3=c06d6a2352ed8c767633aab9c20f2bf7d880c967 28.01.2017 5:00:51, 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15, 5.1=cdc24ba7426c5b11eb4d050706bd78c3ea4913cc 19.06.2016 20:08:01, 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11}} == Введение == Laravel построен с учётом тестирования. Фактически, поддержка ((http://phpunit.de PHPUnit)) доступна по умолчанию, а файл %%(t)phpunit.xml%% уже настроен для вашего приложения. Также фреймворк содержит удобные методы для полноценного тестирования ваших приложений. Папка %%(t)tests%% содержит файл с примером теста %%(t)ExampleTest.php%%. После установки нового приложения Laravel просто выполните команду %%(sh)phpunit%% для запуска ваших тестов. === Среда === При выполнении тестов Laravel автоматически задаст настройки среды %%(t)testing%%. Также при тестировании Laravel автоматически настроит сессии и кэш на драйвер %%(t)array%%, а значит данные сессий и кэша не сохранятся при тестировании. При необходимости вы можете определить другие значения настроек для тестовой среды. Переменные среды %%(t)testing%% можно настроить в файле %%(t)phpunit.xml%%, но перед запуском тестов не забудьте очистить кэш настроек с помощью Artisan-команды %%(sh)config:clear%%! === Создание и выполнение тестов === Для создания теста используйте Artisan-команду %%(sh)make:test%%: %%(sh) php artisan make:test UserTest %% Эта команда поместит новый класс %%(t)UserTest%% в папку %%(t)tests%%. Далее вы можете объявлять методы тестов как вы обычно объявляете их для PHPUnit. Для запуска тестов просто выполните команду %%(sh)phpunit%% в терминале: %% assertTrue(true); } } %% .(alert) Если вы определили собственный метод %%setUp()%%, не забудьте вызвать %%parent::setUp()%%. .(tl_note) Начиная с версии Laravel 5.3 последующие разделы данной статьи вынесены в отдельные статьи ((//docs/5.3/application-testing Тестирование приложения)), ((//docs/5.3/database-testing Тестирование БД)) и ((//docs/5.3/mocking Заглушки)). - //прим. пер.// == Тестирование приложения == Laravel предоставляет очень удобный API для создания HTTP-запросов к вашему приложению, проверки вывода, и даже заполнения форм. Например, посмотрим на файл %%(t)ExampleTest.php%% в папке %%(t)tests%%: %% visit('/') ->see('Laravel 5') ->dontSee('Rails'); } } %% Метод %%visit()%% делает %%(t)GET%%-запрос в приложение. Метод %%see()%% объявляет, что мы должны увидеть данный текст в отклике приложения. Метод %%dontSee()%% объявляет, что данный текст не возвращается в отклике приложения. Это самый базовый тест приложения в Laravel. === Взаимодействие с приложением === Само собой, можно делать намного больше, чем просто проверять появление текста в данном отклике. Давайте рассмотрим несколько примеров нажатия ссылок и заполнения форм. **Нажатие ссылок** В этом тесте мы сделаем запрос в приложение, "нажмём" ссылку в возвращённом отклике, а затем проверим, что оказались на нужном URI. Например, предположим, что в нашем отклике есть ссылка с текстом "О нас": %%(html) О нас %% Теперь давайте напишем тест, нажимающий ссылку и проверяющий переход пользователя на правильную страницу: %% public function testBasicExample() { $this->visit('/') ->click('О нас') ->seePageIs('/about-us'); } %% **Работа с формами** Также Laravel предоставляет несколько методов для тестирования форм. Методы %%type()%%, %%select()%%, %%check()%%, %%attach()%% и %%press()%% позволяют вам взаимодействовать со всеми элементами ввода на ваших формах. Например, представим, что на странице регистрации в приложении есть такая форма: %%(html)
{{ csrf_field() }}
Name:
Accept Terms
%% Мы можем написать тест для заполнения этой формы и проверки результата: %% public function testNewUserRegistration() { $this->visit('/register') ->type('Taylor', 'name') ->check('terms') ->press('Register') ->seePageIs('/dashboard'); } %% Если ваша форма содержит другие элементы ввода, такие как радио-кнопки и выпадающие списки, вы так же легко можете заполнить такие типы полей. Вот список всех методов для работы с формами: %%(hvlraw)
Метод Описание
$this->type($text, $elementName)Ввести текст в данное поле
$this->select($value, $elementName)Выбрать радио-кнопку или выпадающее поле
$this->check($elementName)Поставить чекбокс
$this->uncheck($elementName)Снять чекбокс (для 5.2 и выше)
$this->attach($pathToFile, $elementName)Прикрепить файл к форме
$this->press($buttonTextOrElementName)Нажать кнопку с заданным текстом или именем
%% **Работа с вложениями** Если на вашей форме есть элементы ввода типа %%(t)file%%, вы можете прикрепить файлы к форме методом %%attach()%%: %% public function testPhotoCanBeUploaded() { $this->visit('/upload') ->type('File Name', 'name') ->attach($absolutePathToFile, 'photo') ->press('Upload') ->see('Upload Successful!'); } %% === Тестирование JSON API === Также Laravel предоставляет несколько вспомогательных функций для тестирования JSON API и их откликов. Например, методы %%get()%%, %%post()%%, %%put()%%, %%patch()%% и %%delete()%% используются для выполнения различных HTTP-запросов. Вы также легко можете передать данные и заголовки в эти методы. Для начала давайте напишем тест, выполняющий %%(t)POST%%-запрос к %%(t)/user%% и проверяющий, что данный массив возвращается в формате JSON: %% json('POST', '/user', ['name' => 'Sally']) ->seeJson([ 'created' => true, ]); } } %% Метод %%seeJson()%% конвертирует данный массив в JSON, а затем проверяет, что фрагмент JSON появляется **где-либо** внутри полного JSON-отклика, возвращаемого приложением. Поэтому, если в нём будут ещё и другие свойства, этот тест всё равно будет пройден успешно, так как данный фрагмент присутствует в отклике. **Проверка точного совпадения JSON** Если вы хотите проверить **точное** совпадение данного массива с возвращённым из приложения JSON, вам надо использовать метод %%seeJsonEquals()%%: %% json('POST', '/user', ['name' => 'Sally']) ->seeJsonEquals([ 'created' => true, ]); } } %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) **Проверка структуры JSON** Также можно проверить соответствие структуры JSON определённым требованиям. Для этого служит используйте метод %%seeJsonStructure()%% и передайте в него список (вложенных) ключей: ~%% get('/user/1') ->seeJsonStructure([ 'name', 'pet' => [ 'name', 'age' ] ]); } } ~%% В этом примере ожидается получение %%(t)name%% и вложенного объекта %%(t)pet%% с его собственными %%(t)name%% и %%(t)age%%. Метод %%seeJsonStructure()%% выполнится без ошибки, если в отклике будут дополнительные ключи. Например, проверка будет пройдена и в том случае, когда %%(t)pet%% будет иметь атрибут %%(t)weight%%. Вы можете использовать %%(t)*%%, чтобы задать требование, что возвращаемая структура JSON должна содержать список, в котором каждый элемент содержит по крайней мере те атрибуты, которые есть в наборе значений: ~%% get('/users') ->seeJsonStructure([ '*' => [ 'id', 'name', 'email' ] ]); } } ~%% Вы можете использовать вложение %%(t)*%%. Тогда мы потребуем, что каждый %%(t)user%% в JSON отклике должен содержать заданный набор атрибутов, и каждый %%(t)pet%% каждого %%(t)user%% также должен содержать заданный набор атрибутов: ~%% $this->get('/users') ->seeJsonStructure([ '*' => [ 'id', 'name', 'email', 'pets' => [ '*' => [ 'name', 'age' ] ] ] ]); ~%% %% === Сессии / Аутентификация === В Laravel есть несколько функций для работы с сессиями во время тестирования. Сначала вы можете задать данные сессии для данного массива при помощи метода %%withSession()%%. Это полезно для загрузки сессии с данными перед выполнением тестового запроса в приложение: %% withSession(['foo' => 'bar']) ->visit('/'); } } %% Конечно, чаще всего сессии используют для задания нужного состояния пользователя, например, аутентифицированный пользователь. Простой способ аутентифицировать данного пользователя в качестве текущего - метод %%actingAs()%%. Например, мы можем использовать ((/docs/v5/testing#фабрики-моделей фабрику модели)), чтобы сгенерировать и аутентифицировать пользователя: %% create(); $this->actingAs($user) ->withSession(['foo' => 'bar']) ->visit('/') ->see('Hello, '.$user->name); } } %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) Вторым аргументом метода %%actingAs()%% вы можете указать защитника для аутентификации данного пользователя: ~%% $this->actingAs($user, 'backend') ~%% %% === Отключение посредников === При тестировании приложения иногда удобно отключить ((/docs/v5/middleware посредников)) для некоторых тестов. Это позволит вам тестировать маршруты и контроллер изолированно от любых влияний посредников. Laravel содержит простой типаж %%(t)WithoutMiddleware%%, который можно использовать для автоматического отключения всех посредников для класса теста: %% withoutMiddleware(); $this->visit('/') ->see('Laravel 5'); } } %% === Свои HTTP-запросы === Если вы хотите сделать свой HTTP-запрос в приложение и получить полный объект %%(t)Illuminate\Http\Response%%, используйте метод %%call()%%: %% public function testApplication() { $response = $this->call('GET', '/'); $this->assertEquals(200, $response->status()); } %% Если вы делаете запросы %%(t)POST%%, %%(t)PUT%% или %%(t)PATCH %%, то можете передать массив входных данных вместе с запросом. Тогда эти данные будут доступны в ваших маршрутах и контроллере через ((/docs/v5/requests экземпляр запроса)): %% $response = $this->call('POST', '/user', ['name' => 'Taylor']); %% === Проверки PHPUnit (//assertions//) === Laravel предоставляет несколько //assert//-методов для тестов ((https://phpunit.de/ PHPUnit)): %%(hvlraw)
Метод Описание
->assertResponseOk();Проверка того, что клиентский отклик имеет статус ОК
->assertResponseStatus($code);Проверка того, что клиентский отклик имеет указанный статус.
->assertViewHas($key, $value = null);Проверка того, что представление в отклике содержит данный кусок привязанных данных.
->assertViewHasAll(array $bindings);Проверка того, что представление содержит данный список привязанных данных.
->assertViewMissing($key);Проверка того, что в представлении в отклике отсутствует данный кусок привязанных данных.
->assertRedirectedTo($uri, $with = []);Проверка того, что клиент был переадресован по данному URI
->assertRedirectedToRoute($name, $parameters = [], $with = []);Проверка того, что клиент был переадресован по данному маршруту
->assertRedirectedToAction($name, $parameters = [], $with = []);Проверка того, что клиент был переадресован к данному действию
->assertSessionHas($key, $value = null);Проверка того, что в сессии есть данное значение.
->assertSessionHasAll(array $bindings);Проверка того, что в сессии есть данный список значений.
->assertSessionHasErrors($bindings = [], $format = null);Проверка того, что в сессии есть привязанные ошибки.
->assertHasOldInput();Проверка того, что в сессии есть введённые ранее данные.
->assertSessionMissing($key);Проверка того, что в сессии нет указанного ключа (для версии 5.2 и выше).
%% %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) **Вызов ((docs/v5/controllers контроллера)) из теста** Вы также можете вызвать из теста любой контроллер: ~%% $response = $this->action('GET', 'HomeController@index'); $response = $this->action('GET', 'UserController@profile', ['user' => 1]); ~%% .(alert) Вам не надо указывать полное пространство имён контроллера при использовании метода %%action()%%. Укажите только ту часть, которая идёт за %%(t)App\Http\Controllers%%. Метод %%getContent()%% вернёт содержимое-строку ответа. Если ваш маршрут вернёт ((docs/v5/templates %%View%%)), вы можете получить его через свойство %%$original%%: ~%% $view = $response->original; $this->assertEquals('John', $view['name']); ~%% Для вызова HTTPS-маршрута можно использовать метод %%callSecure()%%: ~%% $response = $this->callSecure('GET', 'foo/bar'); ~%% %% == Работа с базами данных == Также Laravel содержит различные полезные инструменты для упрощения тестирования приложений, использующих БД. Во-первых, вы можете использовать функцию %%seeInDatabase ()%% для проверки наличия в БД данных, подходящих по набору критериев. Например, если мы хотим проверить, что в таблице %%(t)users%% есть запись со значением в поле %%(t)email%% равным %%(t)sally@example.com%%, то можем сделать следующее: %% public function testDatabase() { // Сделать вызов в приложение... $this->seeInDatabase('users', ['email' => 'sally@example.com']); } %% Само собой, метод %%seeInDatabase()%% и другие подобные методы служат для удобства. Но вы можете использовать любые встроенные в PHPUnit методы проверок для дополнения своих тестов. === Сброс базы данных после каждого теста === Часто бывает полезно сбрасывать БД после каждого теста, чтобы данные из предыдущего теста не попадали в последующие тесты. **Использование миграций** Один из вариантов - откатывать БД после каждого теста и мигрировать её перед следующим тестом. В Laravel есть простой типаж %%(t)DatabaseMigrations%%, который автоматически сделает это за вас. Просто используйте типаж в классе теста: %% visit('/') ->see('Laravel 5'); } } %% **Использование транзакций** Другой вариант - обернуть каждый тест в транзакцию БД. И снова, в Laravel есть удобный типаж %%(t)DatabaseTransactions%%, который автоматически сделает это за вас: %% visit('/') ->see('Laravel 5'); } } %% .(alert) Этот типаж обернёт в транзакцию только то соединения с БД, которое используется по умолчанию. === ((#фабрики-моделей)) Фабрики моделей === При тестировании часто необходимо вставить несколько записей в БД перед выполнением теста. Вместо указания значений каждого поля тестовых данных вручную, Laravel позволяет определить набор атрибутов для каждой из ваших ((/docs/v5/eloquent моделей Eloquent)) при помощи "фабрик". Для начала посмотрим на файл %%(t)database/factories/ModelFactory.php%% в вашем приложении. Изначально этот файл содержит одно определение фабрики: %% $factory->define(App\User::class, function (Faker\Generator $faker) { return [ 'name' => $faker->name, 'email' => $faker->email, 'password' => bcrypt(str_random(10)), 'remember_token' => str_random(10), ]; }); %% В замыкание, которое выступает в качестве определения фабрики, вы можете вернуть тестовые значения по умолчанию для всех атрибутов модели. Замыкание получит экземпляр PHP-библиотеки ((https://github.com/fzaninotto/Faker Faker)), которая позволяет вам удобно генерировать случайные данных различных типов для тестирования. Конечно, вы можете добавить свои собственные дополнительные фабрики в файл %%(t)ModelFactory.php%%. А для улучшения организации вы можете также создать файлы дополнительной фабрики. Например, вы можете создать файлы %%(t)UserFactory.php%% и %%(t)CommentFactory.php%% в папке %%(t)database/factories%%. **Множественные типы фабрик** Иногда вам необходимо иметь несколько фабрик для одного класса модели Eloquent. Например, если нужна фабрика для пользователей "Administrator" вдобавок к обычным пользователям. Эти фабрики можно определить методом %%defineAs()%%: %% $factory->defineAs(App\User::class, 'admin', function ($faker) { return [ 'name' => $faker->name, 'email' => $faker->email, 'password' => str_random(10), 'remember_token' => str_random(10), 'admin' => true, ]; }); %% Вместо дублирования всех атрибутов из вашей основной фабрики пользователя, вы можете использовать метод %%raw()%% для получения базовых атрибутов. Когда у вас есть атрибуты, просто дополните их любыми необходимыми дополнительными значениями: %% $factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) { $user = $factory->raw(App\User::class); return array_merge($user, ['admin' => true]); }); %% **Использование фабрик в тестах** Когда вы определили свои фабрики, вы можете использовать их в своих тестах или файлах для заполнения БД, чтобы генерировать экземпляры модели с помощью глобальной функции %%factory()%%. Давайте рассмотрим несколько примеров создания моделей. Сначала используем метод %%make()%%, который создаёт модели, но не сохраняет их в БД: %% public function testDatabase() { $user = factory(App\User::class)->make(); // Использование модели в тестах... } %% Если вы хотите переопределить некоторые из значений по умолчанию для своих моделей, вы можете передать массив значений в метод %%make()%%. Будут заменены только указанные значения, а остальные будут иметь значения, определённые в фабрике: %% $user = factory(App\User::class)->make([ 'name' => 'Abigail', ]); %% Также вы можете создать коллекцию моделей или создать модели заданного типа: %% // Создать три экземпляра App\User... $users = factory(App\User::class, 3)->make(); // Создать экземпляр App\User "admin"... $user = factory(App\User::class, 'admin')->make(); // Создать три экземпляра App\User "admin"... $users = factory(App\User::class, 'admin', 3)->make(); %% **Сохранение моделей фабрики** Метод %%create()%% не только создаёт экземпляры модели, но также сохраняет их в БД при помощи Eloquent-метода %%save()%%: %% public function testDatabase() { $user = factory(App\User::class)->create(); // Использование модели в тестах... } %% Вы можете переопределить атрибуты для модели, передав массив в метод %%create()%%: %% $user = factory(App\User::class)->create([ 'name' => 'Abigail', ]); %% **Добавление отношений в модели** Вы можете сохранить в БД даже несколько моделей. В данном примере мы даже прикрепим к созданным моделям отношение. При использовании метода %%create()%% для создания нескольких моделей возвращается ((/docs/v5/eloquent-collections экземпляр коллекции)), позволяя вам использовать любые удобные функции для работы с коллекцией, такие как %%each()%%: %% $users = factory(App\User::class, 3) ->create() ->each(function ($u) { $u->posts()->save(factory(App\Post::class)->make()); }); %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) **Отношения и атрибуты замыкания** Также вы можете прикрепить к моделям отношения с помощью атрибутов замыканий в определении вашей фабрики. Например, если вы хотите создать экземпляр %%(t)User%% при создании %%(t)Post%%, можно сделать так: ~%% $factory->define(App\Post::class, function ($faker) { return [ 'title' => $faker->title, 'content' => $faker->paragraph, 'user_id' => function () { return factory(App\User::class)->create()->id; } ]; }); ~%% Эти замыкания также получают подготовленный массив атрибутов фабрики, который содержит их: ~%% $factory->define(App\Post::class, function ($faker) { return [ 'title' => $faker->title, 'content' => $faker->paragraph, 'user_id' => function () { return factory(App\User::class)->create()->id; }, 'user_type' => function (array $post) { return App\User::find($post['user_id'])->type; } ]; }); ~%% %% == Заглушки == === Заглушки событий === Если вы используете систему событий Laravel, то при желании можете отключить или заглушить определённые события при тестировании. Например, при тестировании регистрации пользователя вам, вероятно, не нужно вызывать обработчики всех событий %%(t)UserRegistered%%, поскольку они могут посылать письма "добро пожаловать" и т.п. В Laravel есть удобный метод %%expectsEvents()%%, который проверяет возникновение ожидаемых событий, но предотвращает запуск всех обработчиков для этих событий: %% expectsEvents(App\Events\UserRegistered::class); // Тестирование регистрации пользователя... } } %% %%(DOCNEW 5.2=6b0b057ae6de3c88cb29188459e38383c622ec23 8.12.2016 23:00:15) Методом %%doesntExpectEvents()%% можно проверить, что заданные события **не** произошли: ~%% expectsEvents(App\Events\PodcastWasPurchased::class); $this->doesntExpectEvents(App\Events\PaymentWasDeclined::class); // Тестирование покупки подкаста... } } ~%% %% Если вы хотите предотвратить запуск всех обработчиков событий, используйте метод %%withoutEvents()%%: %% withoutEvents(); // Тестирование кода регистрации пользователя... } } %% === Заглушки задач === Иногда вам может понадобиться просто проверить, что определённые задачи запускаются вашими контроллерами при выполнении запросов в ваше приложение. Это позволяет вам тестировать ваши маршруты / контроллеры изолированно - отдельно от логики вашей задачи. А саму задачу вы можете протестировать в отдельном тест-классе. В Laravel есть удобный метод %%expectsJobs()%%, который проверяет, что ожидаемые задачи вызываются, но при этом сами задачи выполняться не будут: %% expectsJobs(App\Jobs\PurchasePodcast::class); // Тестирование кода покупки подкаста... } } %% .(alert) Этот метод обнаруживает только те задачи, которые запускаются методами типажа %%(t)DispatchesJobs%% или вспомогательной функцией %%dispatch()%%. Он не обнаружит задачу, которая отправлена напрямую в %%Queue::push%%. === Заглушки фасадов === При тестировании вам может потребоваться отловить вызов к одному из ((docs/v5/facades фасадов)) Laravel. Например, рассмотрим такое действие контроллера: %% once() ->with('key') ->andReturn('value'); $this->visit('/users')->see('value'); } } %% .(alert) Не делайте этого для фасада %%(t)Request%%. Вместо этого, передайте желаемый ввод вспомогательному HTTP-методу, такому как %%call()%% или %%post()%%, во время выполнения вашего теста. %%(DOCNEW 5.0=5d10040a981deee82c0fde0e8e5d2ffc49eaaecb 8.02.2016 18:09:11) Рассмотрим такое действие ((docs/v5/controllers контроллера)): ~%% public function getIndex() { Event::fire('foo', ['name' => 'Dayle']); return 'All done!'; } ~%% Вы можете отловить вызов класса %%(t)Event%% с помощью метода %%shouldReceive()%% этого фасада, который вернёт экземпляр объекта-заглушки ((https://github.com/padraic/mockery Mockery)). **Заглушка фасада** ~%% public function testGetIndex() { Event::shouldReceive('fire')->once()->with('foo', ['name' => 'Dayle']); $this->call('GET', '/'); } ~%% .(alert) Не делайте этого для объекта %%(t)Request%%. Вместо этого, передайте желаемый ввод методу %%call()%% во время выполнения вашего теста. == Вспомогательные методы == Класс %%(t)TestCase%% содержит несколько вспомогательных методов для упрощения тестирования вашего приложения. **Установка и очистка сессий из теста** ~%% $this->session(['foo' => 'bar']); $this->flushSession(); ~%% **Установка текущего авторизованного пользователя** Вы можете установить текущего авторизованного пользователя с помощью метода %%be()%%: ~%% $user = new User(['name' => 'John']); $this->be($user); ~%% **Заполнение БД тестовыми данными** Вы можете заполнить вашу БД начальными данными из теста методом %%seed()%%: ~%% $this->seed(); $this->seed('DatabaseSeeder'); ~%% Больше информации на тему начальных данных доступно в ((docs/v5/migrations#начальные разделе о миграциях)). ==Обновление приложения== Как вы уже возможно знаете, вы можете получить доступ к ((/docs/v5/container сервис-контейнеру)) вашего приложения с помощью %%$this->app%% из любого тестового метода. Этот экземпляр сервис-контейнера обновляется для каждого тестового класса. Если вы хотите вручную обновить приложение для определённого метода, вы можете использовать метод %%refreshApplication()%% из этого тестового метода. Это приведет к сбросу дополнительных привязок, таких как заглушки, которые были помещены в сервис-контейнер после запуска теста. %%