Всім привіт, мене звати Ігор, я PHP-розробник у компанії Binariks. У цій статті я розповім вам про можливості тестування, які надає фреймворк Laravel в поєднанні з PHPUnit, тому запарюйте чайок та готуйтесь до лонгріду 🙂
Наявність валідних тестів з хорошим покриттям — одне з правил якісного коду. З їх допомогою можна швидко виявити проблеми в функціоналі, відповідно й прискорити вихід функціонала в прод. Вони спрощують життя QA-команді, зменшуючи кількість однотипного мануального тестування, тим самим зменшуючи вплив людського фактора.
В залежності від ступеня ізоляції тести розділяють на наступні типи:
- Unit Tests: максимально ізольовані тести. Використовуються для тестування окремого методу чи функції. Будь-які зовнішні залежності ізолюються на рівні методу чи функції.
- Components/Service Tests: тести, які перевіряють роботу та взаємодію окремих компонентів, чи сервісів. Ізолюються в межах тестованого функціоналу.
- API/HTTP Tests: тести, що перевіряють те, як працюють окремі АРІ та роботу всіх компонентів, що викликаються при виконанні запиту.
- Gui Tests: перевіряють роботу, фронтенд- і бекенд-частини в комплексі.
Як слід проводити тести
Перед тим, як почати розповідь про можливості фреймворку, думаю, варто нагадати правила хороших тестів:
- Тести мають бути швидкими.
- Варто уникати надлишкової абстракції в тестах. Код повинен бути читабельним і зрозумілим без надлишкового копання.
- Назви тестів повинні бути читабельними.
- Test-Driven Development (TDD) — це методологія, коли тести пишуться перед імплементуванням певного функціоналу. Переваги цього підходу в тому, що ви відразу будете писати майбутній код таким чином, щоб його можна було легко тестувати. Також ця методологія зменшує кількість регресивних тестів (тести, що покривають функціонал після його імплементації).
- Підготуй, проведи, перевір.
Хороший тест має три стадії:
- підготовка — генерування вхідних даних та станів системи;
- проведення тесту — проведення дій, потрібних для тесту;
- перевірка — проведення дій для перевірки вихідних даних та станів системи.
- Тест повинен бути відтворюваним і повертати однаковий результат незалежно від кількості викликів.
- Тести не повинні залежати один від одного. Якщо в одного тесту буде залежність від іншого, він може повернути помилку в разі окремого виклику.
- Тест має перевіряти тільки один конкретний тестовий сценарій.
- Вхідні дані тестів повинні бути реалістичними (використовувати фейкери, для генерування тестових даних — хороша практика).
- Якщо для тестування потрібно використовувати базу даних — створіть окрему, на якій ви будете проводити тести.
- Уникайте логічних виразів в тестах. На зображенні нижче покажу приклади того, як НЕ ВАРТО РОБИТИ:
- Всі зовнішні залежності в тесті повинні бути ізольованими.
- Завжди перевіряйте дані відповідними методами. Перевірка даних повинна відбуватися не тільки за значенням, але й за типом.
- Для максимальної користі з тестів інтегруйте ваші тести в CI/CD — це допоможе вам уникнути людського фактора, який завжди присутній в розробці.
- Покриття тестами має бути максимально можливим. Пишіть тести для різних сценаріїв. Ідеально, коли всі можливі варіанти роботи функціонала покриті тестами. Хорошим показником вважається, коли хоча б 70-80% функціоналу покрито тестами.
- Крім всього вище переліченого, тести можуть слугувати прикладами того, як працює функціонал, що тестується. Тому добре розглядати тести як частину специфікації чи документації.
Особливості тестування в Laravel за допомогою PHPUnit
Підтримка тестування за допомогою PHPUnit включена «з коробки», а файл phpunit.xml уже налаштовано для вашої програми. Також у фреймворк додано багато допоміжних методів, які є зручними й спрощують тестування.
За замовчуванням каталог tests в Laravel містить дві папки Feature і Unit.
Unit
Юніт-тести призначені для тестування маленької ізольованої частини вашого коду. Окремого методу класу чи функції.
Тести в директорії Unit не ініціюють ваш Laravel-додаток, тому з юніт-тестами ви не зможете отримати доступ до сервісів Laravel чи бази даних.
Наведу приклад написання юніт-тестів з практичним застосуванням вищеперелічених правил.
Завдання: імплементувати метод getSubscription в класі SubscriptionService, який буде пропонувати користувачеві підписки в залежності від типу його акаунту:
- якщо користувач має преміум-акаунт — пропонувати йому преміум-підписки;
- якщо у користувача звичайний акаунт — пропонувати йому базові підписки.
Відповідно до методології TDD почнемо з написання тестів і описуємо очікувану поведінку метода:
Створюємо сам функціонал:
Викликаємо тести:
Юніт-тести корисні для перевірки роботи окремих важливих частин коду. Вони швидкі і пишуться відносно просто, дають високу стабільність коду, що покритий тестами.
Проте їх ізольованість має і недоліки, а саме — вони не можуть гарантувати коректну взаємодію всіх окремо протестованих частин коду, при реальних сценаріях, коли код не є ізольованим.
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.
Напишемо тест:
Додамо відповідний scope в модель User:
Переконаємося, що тест працює коректно:
Додамо тест для другого випадку:
Перевіримо обидва випадки:
Тестування АРІ. Приклад:
- написати тест, який буде перевіряти наступний функціонал;
- створити 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 Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
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 «з коробки» має багато методів, що будуть корисні при тестуванні. Коротко пройдуся по них і додам корисні посилання (на документацію 🙂 ):
1) HTTP-тести. Назва каже сама за себе — їх мета провести тестування конкретних ендпойнтів сервера. Часто при тестуванні доводиться стикатися з наступними методами фреймворку:
withoutMiddleware()— допомагає відключити всі чи заданіMiddleware;withHeaders(),withSession(),withCookies()— імітують наявність в запиті певних хедерів, сесій, кукі;actingAs()— імітує поведінку сервера при певному авторизованому користувачеві;get(),post(),put(),patch(),delete(),json()— імітує відповідні методи виклику севера;assertStatus(),assertJson(),assertJsonStructure()— надають можливість перевірки статус відповіді з сервера та його структури;view(),blade()— можливість перевірки згенерованих фреймворком сторінок.
2) Методи для імітації роботи функціонала:
- у фреймворку передбачена можливість імітування черг, завантажень файлів, нотифікацій, передачі в контейнер імітації роботи певного об’єкта, роботи з часом.
- контроль за міграціями, можливість скасовувати зміни в базі даних після закінчення тесту, методи для тестування наявності (чи відсутності) певних даних в базі даних.
4) Методи для тестування консолі:
- фреймворк надає можливості з тестування вхідних та вихідних даних артісанівської консолі;
- з встановленням пекеджу Dusk з’являється можливість імітування роботи браузерів і написання GUI-тестів.
В цій статті я намагався коротко описати загальні хороші практики, які варто використовувати при написанні тестів, та показати приклади різних типів тестів, та їх імплементації в середовищі Laravel, зробити короткий огляд можливостей тестування в середовищі фреймворка.
Сподіваюся, ця стаття буде вам корисною. Всім успіхів!
Читайте також: Як написати односторінковий додаток на Laravel та Vue.js за 45 хвилин
Цей матеріал – не редакційний, це – особиста думка його автора. Редакція може не поділяти цю думку.






















Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: