• Пт. Дек 1st, 2023

Тестирование Laravel веб-приложений с помощью PHPUnit

Автор:admin

Ноя 4, 2022

Всем привет, меня зовут Игорь, я PHP-разработчик в компании Binariks. В этой статье я расскажу вам о возможностях тестирования, которые предоставляет фреймворк Laravel в сочетании с PHPUnit, поэтому запаривайте чаек и готовьтесь к лонгриду.

Тестирование Laravel веб-приложений с помощью PHPUnit

Наличие валидных тестов с хорошим покрытием — одно из правил качественного кода. С их помощью можно быстро выявить проблемы в функционале, соответственно и ускорить выход функционала в прод. Они упрощают жизнь команды QA, уменьшая количество однотипного мануального тестирования, тем самым уменьшая влияние человеческого фактора на функционал.

В зависимости от степени изоляции тесты разделяют на следующие типы:

  • Unit Tests: максимально изолированные тесты. Используется для тестирования отдельного метода или функции. Любые внешние зависимости изолируются на уровне метода или функции.
  • Components/Service Tests: тесты, проверяющие работу и взаимодействие отдельных компонентов или сервисов. Изолируются в пределах тестируемого функционала.
  • API/HTTP Tests: тесты, проверяющие, как работают отдельные API и работа всех вызываемых при выполнении запроса компонентов.
  • Gui Tests: проверяют работу, фронт-энд и бэк-энд части в комплексе.

Как следует проводить тесты

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

  • Тесты должны быть быстрыми.
  • Следует избегать избыточной абстракции в тестах. Код, написанный в тесте, должен быть читаемым и понятным без избыточного копания в коде.
  • Названия тестов должны быть читабельными.
  • Test-Driven Development (TDD) — это методология, когда тесты пишутся перед имплементированием определенного функционала. Преимущества этого подхода в том, что вы будете сразу писать будущий код таким образом, чтобы его можно было легко тестировать. Эта методология также уменьшает количество регрессивных тестов (тесты, покрывающие функционал после его имплементации).
  • Подготовь, проведи, проверь.

Хороший тест имеет три стадии:

  1. Подготовка — генерирование входных данных и состояний системы.
  2. Проведение теста — проведение действий, необходимых для теста.
  3. Проверка — проведение действий для проверки исходных данных и состояний системы.

Тестирование Laravel веб-приложений с помощью PHPUnit

  • Тест должен быть воспроизводимым и возвращать одинаковый результат вне зависимости от количества вызовов теста.
  • Тесты не должны зависеть друг от друга. Если у него будет зависимость от другого теста, он может вернуть ошибку в случае отдельного вызова.
  • Тест должен проверять только один конкретный тестовый сценарий.
  • Входные данные тестов должны быть реалистичными (использовать фейкеры, для генерирования тестовых данных хорошая практика).
  • Если для тестирования требуется использовать базу данных, создайте отдельную базу данных, на которой вы будете проводить тесты.
  • Избегайте логических выражений в тестах. На рисунке ниже покажу примеры того, как НЕ СТОИТ ДЕЛАТЬ.

Тестирование Laravel веб-приложений с помощью PHPUnit

  • Все внешние зависимости в тесте должны быть изолированы.
  • Всегда проверяйте данные соответствующими методами. Проверка данных должна производиться не только по значению, но и по типу.
  • Для максимальной пользы тестов интегрируйте ваши тесты в CI/CD — это поможет вам избежать человеческого фактора, который всегда присутствует в разработке.
  • Покрытие тестами должно быть максимально возможным. Пишите тесты для разных сценариев. Идеально, когда все возможные варианты работы функционала покрыты тестами. Хорошим показателем считается когда хотя бы 70-80% функционала покрыто тестами.
  • Помимо всего выше перечисленного, тесты могут служить примерами того, как работает тестируемый функционал. Поэтому хорошо рассматривать тесты как часть спецификации или документации.

Особенности тестирования в Laravel с помощью PHPUnit

Поддержка тестирования с помощью PHPUnit включена из коробки, а файл phpunit.xml уже настроен для вашей программы. Также во фреймворк добавлено много вспомогательных методов, которые удобны и упрощают тестирование.

По умолчанию каталог tests в Laravel содержит две папки Feature и Unit.

Unit

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

Приведу пример написания юнит-тестов с практическим применением вышеперечисленных правил тестов.

Задание: имплементировать метод getSubscription в классе SubscriptionService, который будет предлагать пользователю подписки в зависимости от типа его аккаунта.

  • Если у пользователя есть премиум-аккаунт — предлагать ему премиум-подписки.
  • Если у пользователя обычный аккаунт — предлагать ему базовые подписки.

Согласно методологии TDD начнем с написания тестов и описываем ожидаемое поведение метода.

Тестирование Laravel веб-приложений с помощью PHPUnit

Создаем сам функционал:

Тестирование Laravel веб-приложений с помощью PHPUnit

Вызываем тесты:

Тестирование Laravel веб-приложений с помощью PHPUnit

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

Однако их изолированность имеет и недостатки, а именно они не могут гарантировать корректное взаимодействие всех отдельно протестированных частей кода, при реальных сценариях, когда код не изолирован.

Feature

В отличие от директории Unit, тесты в каталоге Feature предназначены для тестирования взаимодействия разных компонентов программы. Они инициируют ваше Laravel приложение. Соответственно, с помощью этих тестов можно проверять большую часть вашего кода, начиная от отдельных методов, работающих в инфраструктуре Laravel, то, как несколько объектов взаимодействуют друг с другом или даже полный запрос HTTP, включая ответ с сервера.

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

Написание Feature-тестов имеет несколько особенностей.

  • Поскольку тесты имеют доступ к базе данных, для тестирования следует создать отдельную базу данных, где будут генерироваться и тестироваться данные.
  • PHPunit.xml файл позволит задать или перезаписать все .env переменные (в том числе и соответствующие конфигурации). Кроме того, вы можете создать файл .env.testing в таком случае он будет использоваться вместо .env файла при тестировании.
  • Для обновления данных Laravel предлагает трейт Illuminate Foundation Testing RefreshDatabase, который осуществляет все миграции и инициирует транзакцию, которая вернет вашу базу данных в исходное состояние после завершения теста.
  • Для генерирования тестовых данных следует использовать Faker, Factories, Seeders.
  • При тестировании HTTP-запросов следует использовать функционал Named Routes — это простой и удобный способ генерирования сложных URL.
  • Используйте Laravel Mocks для логинов, загрузки файлов, Events и т.д.

Тестирование компонента. Пример: в модель User необходимо добавить scope, который будет фильтровать записи в базе данных по полю is_admin.

  • – Если в scope будет передаваться true – возвращать все записи, в которых поле is_admin true;
  • – Если в scope будет передаваться false – возвращать все записи, в которых поле is_admin false.

Напишем тест:

Тестирование Laravel веб-приложений с помощью PHPUnit

Добавим подходящий scope в модель User:

Тестирование Laravel веб-приложений с помощью PHPUnit

Убедимся, что тест работает корректно:

Тестирование Laravel веб-приложений с помощью PHPUnit

Добавим тест для второго случая:

Тестирование Laravel веб-приложений с помощью PHPUnit

Проверим оба случая:

Тестирование Laravel веб-приложений с помощью PHPUnit

Тестирование АРИ. Пример:

  • Написать тест, который будет проверять следующий функционал.
  • Создать endpoint POST: api/articles.
  • Этот endpoint должен храниться в базе данных (user_id (id залогованного пользователя), title, text).
  • Доступ к endpoint может иметь только пользователь с правами admin (users.is_admin===true).
  • При успешном выполнении ответ с сервера статус 200 и последующие поля.

{

“user_id”: (id пользователя),

«id»: (article id),

«title»: (поле title)

}

<?php

namespace TestsFeature;

use AppModelsUser;

use IlluminateFoundationTestingRefreshDatabase;

use IlluminateFoundationTestingWithFaker;

use TestsTestCase;

class CreateArticleTest extends TestCase

{

use RefreshDatabase;

use WithFaker;

public function setUp(): void

{

parent::setUp();

// исключаем из теста все мидлверы, не относящиеся к тесту

$this->withoutMiddleware();

}

public function testCreateArticleSuccess()

{

// Создаем пользователя-админа

$user = User::factory()->create([

‘is_admin’ => true,

]);

// С помощью фейкера генерируем данные запроса

$request = [

‘title’ => $this->faker()->text(6),

‘text’ => $this->faker()->text(50),

];

// С помощью метода actingAs() имитируем поведение запроса для админа

$response = $this->actingAs($user)

// выполняем сам запрос

->post(

// первый параметр – роут сгенерированный с помощью Laravel функционала Named routes

route(‘articles.create’),

// Второй параметр – request body

$request,

);

// Проверка результата:

// Проверяем, ответ ли с сервера 200

$response->assertStatus(200)

//Проверка структуры ответа

->assertJsonStructure([

‘id’,

‘user_id’,

‘title’,

])

// проверка достоверности полученных данных

->assertJson([

‘user_id’ => $user->id,

‘title’ => $request[‘title’],

])

// Конвертируем ответ в array, чтобы получить id статьи

->json();

// Проверяем, была ли создана запись в базе данных

$this->assertDatabaseHas(‘articles’, [

‘id’ => $response[‘id’],

‘user_id’ => $response[‘user_id’],

‘title’ => $response[‘title’],

‘text’ => $request[‘text’],

]);

}

}

Laravel «из коробки» имеет множество методов, которые будут полезны при тестировании. Кратко пройдусь по ним и добавлю полезные ссылки (на документацию :-)):

HTTP-тесты:

Название говорит само за себя — их цель провести тестирование конкретных эндпойнтов сервера. Часто при тестировании приходится сталкиваться со следующими методами фреймворка:

  • withoutMiddleware() — помогает отключить все заданные Middleware;
  • withHeaders(), withSession(), withCookies() — имитируют наличие в запросе определенных хедеров, сессий, куки;
  • actingAs() — имитирует поведение сервера при определенном авторизованном пользователе;
  • get(), post(), put(), patch(), delete(), json() — имитирует соответствующие методы вызова севера;
  • assertStatus(), assertJson(), assertJsonStructure() — предоставляют возможность проверки статус ответа с сервера и их структуры;
  • view(), blade() — возможность проверки сгенерированных фреймворком страниц.

Методы для имитации работы функционала:

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

Тестирование базы данных:

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

Методы для тестирования консоли:

Фреймворк предоставляет возможности тестирования входных и выходных данных артисановской консоли.

При установке пекеджа Dusk появляется возможность имитирования работы браузеров и написания GUI тестов.

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

Надеюсь, эта статья будет вам полезна. Всем успехов.

Источник: marketer.ua

Автор: admin