Тестування Laravel веб застосунків з допомогою PHPUnit

  • 1 ноября, 12:58
  • 3970
  • 0

Всім привіт, мене звати Ігор, я PHP-розробник у компанії Binariks. У цій статті я розповім вам про можливості тестування, які надає фреймворк Laravel в поєднанні з PHPUnit, тому запарюйте чайок та готуйтесь до лонгріду.

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

В залежності від ступеня ізоляції тести розділяють на наступні типи:

  1. Unit Tests: максимально ізольовані тести. Використовуються для тестування окремого методу чи функції. Будь-які зовнішні залежності ізолюються на рівні методу чи функції.

  2. Components/Service Tests: тести, які перевіряють роботу та взаємодію окремих компонентів, чи сервісів. Ізолюються в межах тестованого функціоналу.

  3. API/HTTP Tests: тести, що перевіряють те, як працюють окремі АРІ та роботу всіх компонентів, що викликаються при виконанні запиту.

  4. Gui Tests: перевіряють роботу, фронт-енд і бек-енд частини в комплексі.

Як слід проводити тести

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

  1. Тести повинні бути швидкими.

  2. Варто уникати надлишкової абстракції в тестах. Код, написаний в тесті, повинен бути читабельним і зрозумілим без надлишкового копання в коді.

  3. Назви тестів повинні бути читабельними.

  4. Test-Driven Development (TDD) — це методологія, коли тести пишуться перед імплементуванням певного функціоналу. Переваги цього підходу в тому, що ви зразу будете писати майбутній код таким чином, щоб його можна було легко тестувати. Також ця методологія зменшує кількість регресивних тестів (тести, що покривають функціонал після його імплементації).

  5. Підготуй, проведи, перевір.

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

Підготовка — генерування вхідних даних та станів системи.

Проведення тесту — проведення дій потрібних для тесту.

Перевірка — проведення дій, для перевірки вихідних даних та станів системи.

  1. Тест повинен бути відтворюваним і повертати однаковий результат незалежно від кількості викликів тесту.

  2. Тести не повинні залежати один від іншого. Якщо в нього буде залежність від іншого тесту, він може повернути помилку, в разі окремого виклику.

  3. Тест повинен перевіряти тільки один конкретний тестовий сценарій.

  4. Вхідні дані тестів повинні бути реалістичними (використовувати фейкери, для генерування тестових даних — хороша практика).

  5. Якщо для тестування потрібно використовувати базу даних — створіть окрему базу даних, на якій ви будете проводити тести.

  6. Уникайте логічних виразів в тестах. На зображенні нижче покажу приклади того, як НЕ ВАРТО РОБИТИ.

  1. Всі зовнішні залежності в тесті повинні бути ізольованими.

  2. Завжди перевіряйте дані відповідними методами. Перевірка даних повинна відбуватися не тільки за значенням, але й за типом.

  3. Для максимальної користі з тестів інтегруйте ваші тести в CI/CD — це допоможе вам уникнути людського фактора, який завжди присутній в розробці.

  4. Покриття тестами повинно бути максимально можливим. Пишіть тести для різних сценаріїв. Ідеально, коли всі можливі варіанти роботи функціонала покриті тестами. Хорошим показником вважається коли хоча б 70-80% функціоналу покрито тестами.

  5. Крім всього вище переліченого, тести можуть служити прикладами того, як працює функціонал, що тестується. Тому добре розглядати тести, як частину специфікації чи документації.

Особливості тестування в Laravel з допомогою PHPUnit

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

За замовчуванням каталог tests в Laravel містить дві папки Feature і Unit.

Unit

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

Наведу приклад написання юніт-тестів з практичним застосуванням вищеперелічених правил тестів.

Завдання: імплементувати метод getSubscription в класі SubscriptionService, який буде пропонувати користувачеві підписки в залежності від типу його акаунту.

  1. Якщо користувач має преміум-акаунт — пропонувати йому преміум-підписки.

  2. Якщо в користувача звичайний акаунт — пропонувати йому базові підписки.

Відповідно до методології TDD почнемо з написання тестів і описуємо очікувану поведінку метода.

Створюємо сам функціонал:

Викликаємо тести:

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

Проте їх ізольованість має і недоліки, а саме вони не можуть гарантувати коректну взаємодію всіх окремо протестованих частин коду, при реальних сценаріях, коли код не є ізольованим.

Feature

На відміну від директорії Unit, тести в каталозі Feature призначені для тестування взаємодії різних компонентів програми. Вони ініціюють ваш Laravel додаток. Відповідно, з допомогою цих тестів можна перевіряти більшу частину вашого коду, починаючи від окремих методів, які працюють в інфраструктурі Laravel, те, як кілька об’єктів взаємодіють один з одним або навіть повний запит HTTP включно з відповіддю з сервера.

Згідно з рекомендаціями розробників фреймворку Laravel, більшість ваших тестів мають бути Feature-тестами. Тому, що ці типи тестів забезпечують більшу впевненість у тому, що ваша система функціонує належним чином.

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

  1. Оскільки тести мають доступ до бази даних, для тестування варто створити окрему базу даних, на якій будуть генеруватися і тестуватися дані.

  2. PHPunit.xml файл дозволить задати чи перезаписати всі .env змінні (в тому числі і конфігурації, які відповідають). Крім того, ви можете створити файл .env.testing в такому випадку він буде використовуватися замість .env файлу при тестуванні.

  3. Для оновлення даних Laravel пропонує трейт Illuminate\ Foundation\ Testing\ RefreshDatabase, який здійснює всі міграції та ініціює транзакцію, яка поверне вашу базу даних до вихідного стану після завершення тесту.

  4. Для генерування тестових даних варто використовувати Faker, Factories, Seeders.

  5. При тестуванні HTTP-запитів варто використовувати функціонал Named Routes — це простий і зручний спосіб генерування складних URL.

  6. Використовуйте Laravel Mocks для логінів, завантаження файлів, Events тощо.

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

— Якщо в scope буде передаватися true — повертати всі записи, в яких поле is_admin true;

— Якщо в scope буде передаватися false — повертати всі записи, в яких поле is_admin false.

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

Додамо відповідний scope в модель User:

Переконаємося, що тест працює коректно:

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

Перевіримо обидва випадки:

Тестування АРІ. Приклад:

  1. Написати тест, який буде перевіряти наступний функціонал.

  2. Створити endpoint POST: api/articles.

  3. Цей endpoint повинен зберігати в базу даних (user_id (id залогованого користувача), title , text).

  4. Доступ до endpoint може мати тільки користувач з правами admin (users.is_admin === true).

  5. При успішному виконанні відповідь з сервера статус 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 «з коробки» має багато методів, що будуть корисні при тестуванні. Коротко пройдуся по них і додам корисні посилання (на документацію :-)):

HTTP-тести:

Назва говорить сама за себе — їх мета провести тестування конкретних ендпойнтів сервера. Часто при тестуванні доводиться стикатися з наступними методами фреймворку:

withoutMiddleware() — допомагає відключити всі, чи задані Middleware;

withHeaders(), withSession(), withCookies() — імітують наявність в запиті певних хедерів, сесій, кукі;

actingAs() — імітує поведінку сервера при певному авторизованому користувачеві;

get(), post(), put(), patch(), delete(), json() — імітує відповідні методи виклику севера;

assertStatus(), assertJson(), assertJsonStructure() — надають можливість перевірки статус відповіді з сервера та його структури;

view(), blade() — можливість перевірки згенерованих фреймворком сторінок.

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

У фреймворку передбачена можливість імітування черг, завантажень файлів, нотифікацій, передачі в контейнер імітації роботи певного об’єкта, роботи з часом.

Тестування бази даних:

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

Методи для тестування консолі:

Фреймворк надає можливості з тестування вхідних та вихідних даних артісанівської консолі.

З встановленням пекеджу Dusk з’являється можливість імітування роботи браузерів і написання GUI тестів.

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

Сподіваюся, ця стаття буде вам корисною. Всім успіхів.


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все