товары

Узнайте, как выполнять тесты в Laravel на простых примерах, используя PHPUnit и PEST.

Когда речь идет об автоматизированных тестах или модульных тестах, на любом языке программирования существует два противоположных мнения:

  • потерянное время
  • Вы не можете обойтись без этого

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

Сначала давайте поговорим о том, «почему», а затем рассмотрим несколько примеров того, как.

Зачем нам нужно автоматическое тестирование

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

Это главное преимущество: автоматическое повторное тестирование всех функций. Это может показаться дополнительной работой, но если вы не прикажете «роботу» сделать это, мы должны сделать это вручную, верно? 

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

Автоматизированные тесты могут дать нам несколько преимуществ:

  • Экономьте время ручного тестирования;
  • Они позволяют сэкономить время как на реализуемой новой функции, так и на консолидированных функциях, избегая регресса;
  • Умножьте это преимущество на все новые функции и все уже реализованные функции;
  • Предыдущие три пункта применимы к каждой новой версии;
  • ...

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

Наши первые автоматизированные тесты

Чтобы выполнить первое автоматическое тестирование в Laravel, вам не нужно писать какой-либо код. Да, вы правильно прочитали. Все уже настроено и подготовлено в предварительной установке.defiконец Laravel, включая самый первый базовый пример.

Вы можете попробовать установить проект Laravel и сразу запустить первые тесты:

laravel new project
cd project
php artisan test

Это должен быть результат в вашей консоли:

Если мы посмотрим на предыдущуюdefiночь Ларавеля /tests, у нас есть два файла:

тесты/Функция/ExampleTest.php:

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

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

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

тесты/Unit/ExampleTest.php:

class ExampleTest extends TestCase
{
    public function test_that_true_is_true()
    {
        $this->assertTrue(true);
    }
}

Кажется немного бессмысленным проверять, правда ли это? 

Конкретно о юнит-тестах мы поговорим чуть позже. А пока вам нужно понять, что вообще происходит в каждом тесте.

  • Каждый тестовый файл в папке /tests — это класс PHP, расширяющий TestCase PHPUnit
  • Внутри каждого класса вы можете создать несколько методов, обычно один метод для тестируемой ситуации.
  • В каждом методе есть три действия: подготовка ситуации, затем действие, а затем проверка (подтверждение), соответствует ли результат ожидаемому.

Структурно это все, что вам нужно знать, все остальное зависит от того, что именно вы хотите протестировать.

Чтобы создать пустой тестовый класс, просто запустите эту команду:

php artisan make:test HomepageTest

Файл создан tests/Feature/HomepageTest.php:

class HomepageTest extends TestCase
{
    // Replace this method with your own ones
    public function test_example()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Теперь давайте посмотрим, что произойдет, если тестовый код в Laravel завершится неудачно.

Давайте теперь посмотрим, что произойдет, если тестовые утверждения не вернут ожидаемый результат.

Давайте изменим примеры тестов на это:

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/non-existing-url');
 
        $response->assertStatus(200);
    }
}
 
 
class ExampleTest extends TestCase
{
    public function test_that_true_is_false()
    {
        $this->assertTrue(false);
    }
}

И теперь, если мы запустим команду php artisan test снова:

 FAIL  Tests\Unit\ExampleTest
⨯ that true is true
 
 FAIL  Tests\Feature\ExampleTest
⨯ the application returns a successful response
 
---
 
• Tests\Unit\ExampleTest > that true is true
Failed asserting that false is true.
 
at tests/Unit/ExampleTest.php:16
   12▕      * @return void
   13▕      */
   14▕     public function test_that_true_is_true()
   15▕     {
➜  16▕         $this->assertTrue(false);
   17▕     }
   18▕ }
   19▕
 
• Tests\Feature\ExampleTest > the application returns a successful response
Expected response status code [200] but received 404.
Failed asserting that 200 is identical to 404.
 
at tests/Feature/ExampleTest.php:19
   15▕     public function test_the_application_returns_a_successful_response()
   16▕     {
   17▕         $response = $this->get('/non-existing-url');
   18▕
➜  19▕         $response->assertStatus(200);
   20▕     }
   21▕ }
   22▕
 
 
Tests:  2 failed
Time:   0.11s

Есть два неудачных теста, помеченных как FAIL, с пояснениями ниже и стрелками, указывающими на точную строку тестов, которые не прошли. Ошибки обозначаются таким образом.

Пример: Тестирование кода регистрационной формы в Laravel

Предположим, у нас есть форма, и нам нужно протестировать различные случаи: мы проверяем, не удается ли она с недопустимыми данными, мы проверяем, успешно ли она работает с правильным вводом и т. д.

Официальный стартовый комплект от Ларавел Бриз включает я тестирование функциональности внутри него. Давайте посмотрим на несколько примеров оттуда:

tests/Feature/RegistrationTest.php

use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class RegistrationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_registration_screen_can_be_rendered()
    {
        $response = $this->get('/register');
 
        $response->assertStatus(200);
    }
 
    public function test_new_users_can_register()
    {
        $response = $this->post('/register', [
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
}

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

Ознакомимся еще с двумя способами проверки результата, еще с двумя утверждениями: $this->assertAuthenticated()$response->assertRedirect(). Вы можете проверить все утверждения, доступные в официальной документации PHPUnit e Ответ Laravel . Обратите внимание, что по этому поводу встречаются некоторые общие утверждения. $this, в то время как другие проверяют конкретные $responseиз маршрутного вызова.

Еще одна важная вещь – это use RefreshDatabase;оператор со штрихом, вставленным над классом. Это необходимо, когда тестовые действия могут повлиять на базу данных, как в этом примере, при ведении журнала добавляется новая запись в базу данных. usersтаблица базы данных. Для этого вам следует создать отдельную тестовую базу данных, которая будет обновляться с помощью php artisan migrate:freshкаждый раз при запуске тестов.

У вас есть два варианта: физически создать отдельную базу данных или использовать базу данных SQLite в памяти. Оба настраиваются в файле phpunit.xmlпредоставляется по умолчаниюdefiНита с Laravel. В частности, вам нужна эта часть:

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_DRIVER" value="array"/>
    <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
    <!-- <env name="DB_DATABASE" value=":memory:"/> -->
    <env name="MAIL_MAILER" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="TELESCOPE_ENABLED" value="false"/>
</php>

См. DB_CONNECTIONDB_DATABASEкакие из них комментируются? Если на вашем сервере установлен SQLite, самое простое действие — просто раскомментировать эти строки, и ваши тесты будут выполняться для этой базы данных в памяти.

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

В дополнение к этому коду:

$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);

Мы также можем использовать утверждения теста базы данных и сделайте что-то вроде этого:

$this->assertDatabaseCount('users', 1);
 
// Or...
$this->assertDatabaseHas('users', [
    'email' => 'test@example.com',
]);

Пример страницы входа

Давайте теперь посмотрим еще один пример страницы входа в Laravel Breeze.

tests/Feature/AuthenticationTest.php:

class AuthenticationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_login_screen_can_be_rendered()
    {
        $response = $this->get('/login');
 
        $response->assertStatus(200);
    }
 
    public function test_users_can_authenticate_using_the_login_screen()
    {
        $user = User::factory()->create();
 
        $response = $this->post('/login', [
            'email' => $user->email,
            'password' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
 
    public function test_users_can_not_authenticate_with_invalid_password()
    {
        $user = User::factory()->create();
 
        $this->post('/login', [
            'email' => $user->email,
            'password' => 'wrong-password',
        ]);
 
        $this->assertGuest();
    }
}

Речь идет о форме входа. Логика аналогична регистрации, не так ли? Но три метода вместо двух, так что это пример тестирования как хороших, так и плохих сценариев. Итак, общая логика заключается в том, что вам следует тестировать оба случая: когда дела идут хорошо, и когда они терпят неудачу.

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

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

И снова Laravel генерирует заводскую предварительную настройку.defiнита с ложными данными для Userмодель, нестандартная.

database/factories/UserFactory.php:

class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

Видите ли, сколько всего готовит сам Laravel, так легко ли нам было бы начать тестирование?

Итак, если мы выполним php artisan testпосле установки Laravel Breeze мы должны увидеть что-то вроде этого:

 PASS  Tests\Unit\ExampleTest
✓ that true is true
 
 PASS  Tests\Feature\Auth\AuthenticationTest
✓ login screen can be rendered
✓ users can authenticate using the login screen
✓ users can not authenticate with invalid password
 
 PASS  Tests\Feature\Auth\EmailVerificationTest
✓ email verification screen can be rendered
✓ email can be verified
✓ email is not verified with invalid hash
 
 PASS  Tests\Feature\Auth\PasswordConfirmationTest
✓ confirm password screen can be rendered
✓ password can be confirmed
✓ password is not confirmed with invalid password
 
 PASS  Tests\Feature\Auth\PasswordResetTest
✓ reset password link screen can be rendered
✓ reset password link can be requested
✓ reset password screen can be rendered
✓ password can be reset with valid token
 
 PASS  Tests\Feature\Auth\RegistrationTest
✓ registration screen can be rendered
✓ new users can register
 
 PASS  Tests\Feature\ExampleTest
✓ the application returns a successful response
 
Tests:  17 passed
Time:   0.61s

Функциональные тесты по сравнению с модульными тестами и другими

Вы видели подпапки tests/Feature e tests/Unit ?. 

В чем разница между ними? 

В глобальном масштабе, за пределами экосистемы Laravel/PHP, существует несколько типов автоматического тестирования. Вы можете найти такие термины, как:

  • Модульные тесты
  • Тестирование функций
  • Интеграционные тесты
  • Функциональные тесты
  • Сквозное тестирование
  • Приемочные испытания
  • Дымовые испытания
  • и т.п.

Это звучит сложно, но реальные различия между этими типами тестов иногда размыты. Вот почему Laravel упростил все эти запутанные термины и сгруппировал их в два: модуль/функция.

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

Модульные тесты имеют два значения. В общем, вы можете обнаружить, что любой автоматизированный тест называется «модульным тестированием», а весь процесс можно назвать «модульным тестированием». Но в контексте функциональности и единицы этот процесс заключается в изолированном тестировании конкретной закрытой единицы кода. Например, у вас есть класс Laravel с методом, который что-то вычисляет, например, общую стоимость заказа с параметрами. Таким образом, модульный тест будет определять, возвращаются ли правильные результаты из этого метода (модуля кода) с разными параметрами.

Чтобы сгенерировать модульный тест, вам нужно добавить флаг:

php artisan make:test OrderPriceTest --unit

Сгенерированный код такой же, как и в предварительном модульном тесте.defiСистема Ларавел:

class OrderPriceTest extends TestCase
{
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

Как видите, его не существует RefreshDatabase, и это один из defiнаиболее распространенные определения модульного теста: он не затрагивает базу данных, работает как «черный ящик», изолированный от работающего приложения.

Пытаясь имитировать пример, который я упоминал ранее, давайте представим, что у нас есть класс обслуживания OrderPrice.

app/Services/OrderPriceService.php:

class OrderPriceService
{
    public function calculatePrice($productId, $quantity, $tax = 0.0)
    {
        // Some kind of calculation logic
    }
}

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

class OrderPriceTest extends TestCase
{
    public function test_single_product_no_taxes()
    {
        $product = Product::factory()->create(); // generate a fake product
        $price = (new OrderPriceService())->calculatePrice($product->id, 1);
        $this->assertEquals(1, $price);
    }
 
    public function test_single_product_with_taxes()
    {
        $price = (new OrderPriceService())->calculatePrice($product->id, 1, 20);
        $this->assertEquals(1.2, $price);
    }
 
    // More cases with more parameters
}

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

Далее, если у вас есть специальные расчеты или логика, вы можете definire как модуль с параметрами, вы можете создавать модульные тесты специально для этого.

Иногда написание тестов требует модификации самого кода и его рефакторинга, чтобы сделать его более «тестируемым»: разделения модулей на специальные классы или методы.

Когда/как проводить тесты?

Какова реальная польза от этого php artisan test, когда его следует запустить?

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

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

Если пойти еще дальше, можно автоматизировать много вещи. С помощью различных инструментов CI/CD вы можете указать тесты, которые будут запускаться всякий раз, когда кто-то отправляет изменения в определенную ветку Git или перед объединением кода в производственную ветку. Самый простой рабочий процесс — использовать действия Github, у меня есть отдельное видео что доказывает это.

Что вам следует протестировать?

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

Фактически, здесь я согласен с людьми, которые обвиняют автоматическое тестирование в том, что оно отнимает больше времени, чем приносит реальную пользу. Это может произойти, если вы пишете тесты для каждой детали. Впрочем, этого может потребовать ваш проект: главный вопрос — «какова цена потенциальной ошибки».

Другими словами, вам необходимо расставить приоритеты в тестировании, задав вопрос: «Что произойдет, если этот код выйдет из строя?» Если в вашей платежной системе есть ошибки, это напрямую повлияет на бизнес. Поэтому, если функциональность ваших ролей/разрешений нарушена, это огромная проблема безопасности.

Мне нравится, как Мэтт Стауффер сформулировал это на конференции: «Сначала вы должны протестировать те вещи, которые, если они потерпят неудачу, вас уволят с работы». Конечно, это преувеличение, но идея понятна: сначала попробуйте самое важное. А потом и другие функции, если у вас есть время.

PEST: новая альтернатива PHPUnit

Все приведенные выше примеры основаны на инструменте предварительного тестирования Laravel.defiночь: PHPUnit . Но с годами в экосистеме появились и другие инструменты, и одним из последних популярных является PEST . Создано официальным сотрудником Laravel Нуно Мадуро , направлен на упрощение синтаксиса, что позволяет еще быстрее писать код для тестов.

Под капотом работает su PHPUnit, как дополнительный слой, просто пытается минимизировать некоторые заранее повторяющиеся части.defiконец кода PHPUnit.

Давайте посмотрим на пример. Помните класс предварительного тестирования функцийdefiработаете в Laravel? Я напомню вам:

namespace Tests\Feature;
 
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Знаете ли вы, как будет выглядеть тот же тест с PEST?

test('the application returns a successful response')->get('/')->assertStatus(200);

Да, ОДНА строка кода и всё. Итак, цель PEST — устранить накладные расходы:

  • Создание классов и методов для всего;
  • Расширение тестового примера;
  • Размещая действия в отдельных строках: в PEST их можно объединить в цепочку.

Чтобы сгенерировать PEST-тест в Laravel, вам необходимо указать дополнительный флаг:

php artisan make:test HomepageTest --pest

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

BlogInnovazione.it

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

АРТИКОЛИ recenti

Будущее уже здесь: как судоходная отрасль меняет мировую экономику

Военно-морской сектор является настоящей глобальной экономической державой, которая достигла 150-миллиардного рынка...

1 мая 2024

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

В прошлый понедельник Financial Times объявила о сделке с OpenAI. FT лицензирует свою журналистику мирового уровня…

Апрель 30 2024

Онлайн-платежи: вот как потоковые сервисы заставляют вас платить вечно

Миллионы людей платят за стриминговые сервисы, выплачивая ежемесячную абонентскую плату. Распространено мнение, что вы…

Апрель 29 2024

Veeam предлагает наиболее полную поддержку программ-вымогателей: от защиты до реагирования и восстановления.

Coveware от Veeam продолжит предоставлять услуги по реагированию на инциденты, связанные с кибер-вымогательством. Coveware предложит возможности криминалистики и исправления…

Апрель 23 2024

Читайте «Инновации» на вашем языке

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

Следуйте за нами