• Час читання ~10 хв
  • 27.03.2023

Поширеною проблемою в тестуванні є не тільки ТЕ, ЯК щось перевірити, але й ТЕ, ЩО ви можете перевірити. Ось чому я склав список усіх речей, які мені подобається тестувати у своїх програмах.

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

Шукаєте щось конкретне? Виберіть його:

Примітка: Всі приклади також можна знайти в спеціальному репозиторії.

Тестування статусу відповіді на сторінку Тестування

відповіді відповідь є одним з найпростіших тестів для написання, тим не менш, це надзвичайно корисно.

Це гарантує, що сторінка відповідає правильним кодом статусу HTTP, в першу чергу відповіддю 200.

it('gives back a successful response for home page', function () {
    $this->get('/')->assertOk();
});

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

Посилання: Дізнайтеся більше про тестування відповідей на сторінках в офіційних документах.

Тестування тексту

відповіді на сторінці Цей тест схожий на тест відповіді першої сторінки. Ми також тестуємо відповідь, але на цей раз нас цікавить зміст відповіді.

it('lists products', function () {
    
    $firstProduct = Product::factory()->create();
    $secondProduct = Product::factory()->create();

    
    $this->get('/')
        ->assertOk()
        ->assertSeeTextInOrder([
            $firstProduct->title,
            $secondProduct->title,
        ]);
});

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

Тут ви також можете бути більш конкретними, наприклад, коли ви хочете лише показати released products.

it('lists released products', function () {
    
    $releasedProduct = Product::factory()
        ->released()
        ->create();
        
    $draftProduct = Product::factory()
        ->create();

    
    $this->get('/')
        ->assertOk()
        ->assertSeeText($releasedProduct->title)
        ->assertDontSeeText($draftProduct->title);
});

Він також демонструє, як перевірити те, що не показано, що також може бути корисним. Цей тест не був би таким корисним, якби на домашній сторінці у нас був лише статичний текст.

Посилання: Дізнайтеся більше про тестування відповідей на сторінках в офіційних документах.

Тестове подання відповідей на сторінку Поруч із тестуванням стану відповіді та вмісту можна також перевірити подання

, яке повертається.

it('returns correct view', function() {
    
    $this->get('/')
        ->assertOk()
        ->assertViewIs('home');
});

Ви можете взяти це ще далі і перевірити дані, які передаються в перегляд.

it('returns correct view', function() {
    
    $this->get('/')
        ->assertOk()
        ->assertViewIs('home')
        ->assertViewHas('products');
});

Посилання: Дізнайтеся більше про тестування відповідей на сторінках в офіційних документах.

Тестування відповіді на сторінку JSON Часто ви хочете повернути дані JSON

зі свого API. Саме тут можна використовувати помічників JSON Ларавеля, як і assertJson метод.

it('returns all products as JSON', function () {
    
    $product = Product::factory()->create();
    $anotherProduct = Product::factory()->create();

    
    $this->post('api/products')
        ->assertOk()
        ->assertJson([
            [
                'title' => $product->title,
                'description' => $product->description,
            ],
            [
                'title' => $anotherProduct->title,
                'description' => $anotherProduct->description,
            ],
        ]);
});

Посилання: Дізнайтеся більше про тестування відповідей на сторінках в офіційних документах.

Тестування проти бази даних Оскільки ми зберігаємо дані в базі даних

, ми хочемо переконатися, що дані зберігаються правильно. Саме тут Ларавель може допомогти вам з деякими зручними помічниками з твердження.

it('stores a product', function () {
    
    $this->actingAs(User::factory()->create())
        ->post('product', [
        'title' => 'Product name',
        'description' => 'Product description',
    ])->assertSuccessful();

    
    $this->assertDatabaseCount(Product::class, 1);
    $this->assertDatabaseHas(Product::class, [
        'title' => 'Product name',
        'description' => 'Product description',
    ]);
});

Приклад гарантує, що продукт буде створений і збережений в базі даних для нашого поштового маршруту.

Посилання: Знайдіть усі твердження бази даних в офіційній документації.

Перевірка валідації

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

it('requires the title', function () {
    
    $this->actingAs(User::factory()->create())
        ->post('product', [
            'description' => 'Product description',
        ])->assertInvalid(['title' => 'required']);
});

it('requires the description', function () {
    
    $this->actingAs(User::factory()->create())
        ->post('product', [
            'title' => 'Product name',
        ])->assertInvalid(['description' => 'required']);
});

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

it('requires title and description tested with a dataset', function($data, $error) {
    
    $this->actingAs(User::factory()->create())
        ->post('product', $data)->assertInvalid($error);
})->with([
    'title required' => [['description' => 'text'], ['title' => 'required']],
    'description required' => [['title' => 'Title'], ['description' => 'required']],
]);

Посилання: Дізнайтеся більше про підтвердження тверджень в офіційних документах.

Тестування моделей / відносин

По-перше, мені подобається тестувати кожен зв'язок моделі. Якщо бути точним, ми дійсно not хочемо перевірити функціональність відносин; це те, що вже робить Ларавель. Ми хочемо переконатися, що відносини визначені.

it('has products', function () {
    
    $user = User::factory()
        ->has(Product::factory())
        ->create();

    
    $products = $user->products;

    
    expect($products)
        ->toBeInstanceOf(Collection::class)
        ->first()->toBeInstanceOf(Product::class);
});

Розробники мають різні думки щодо того, з якою логікою слід поводитися в моделі, і це нормально. Але якщо ви додасте його, обов'язково також протестуйте його.

it('only returns released courses for query scope', function () {
    
    Course::factory()->released()->create();
    Course::factory()->create();

    
    expect(Course::released()->get())
        ->toHaveCount(1)
        ->first()->id->toEqual(1);
});

Іншим прикладом може бути модельний аксесуар типу:

protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }

А тест виглядав би так:

it('capitalizes the first character of the first name', function () {
    
    $user = User::factory()->create(['first_name' => 'christoph'])

    
    expect($user->first_name)
        ->toBe('Christoph');
});

Тестування відправки пошти

Laravel надає багато помічників з тестування, особливо при використанні фасадів.

class PublishPodcastController extends Controller
{
    public function __invoke(Podcast $podcast)
    {
        

        Mail::to($podcast->author)->send(new PodcastPublishedMail());
    }
}

В кінці цього контролера ми надсилаємо електронний лист. У нашому тесті ми можемо натиснути на контролер через кінцеву точку і переконатися, що цей електронний лист would have been надіслано.

it('sends email to podcast author', function() {
    
    Mail::fake();
    $podcast = Podcast::factory()->create();

    
    $this->post(route('publish-podcast', $podcast));

   
    Mail::assertSent(PodcastPublishedMail::class);
});

Завжди запускайте Mail::fake() метод на початку тестів під час тестування електронних листів. Це гарантує, що користувачу не надсилається фактичний електронний лист.

Більшість допоміжних методів, таких як assertSent також приймають зворотний дзвінок як другий аргумент. У нашому випадку він отримує поштовий об'єкт. Він містить усі дані електронної пошти, як-от електронний лист, на який його потрібно надіслати.

Це дозволяє вам робити ще більше тверджень, наприклад, про "адресу" електронного листа.

Mail::assertSent(PodcastPublishedMail::class, function(PodcastPublishedMail $mail) use ($podcast) {
    return $mail->hasTo($podcast->author->email);
});

Посилання: Дізнайтеся більше про тестування поштових коштів в офіційних документах.

Тестування вмісту

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

it('contains the product title', function () {
    
    $product = Product::factory()->make();

   
    $mail = new PaymentSuccessfulMail($product);

    
    expect($mail)
        ->assertHasSubject('Your payment was successful')
        ->assertSeeInHtml($product->title);
});

Посилання: Дізнайтеся про всі інші твердження для тестування вмісту пошти в офіційних документах.

Тестування Jobs & Queues

Мені подобається тестувати вакансії та черги окремо, починаючи ззовні. Це означає, що я перевіряю, що роботу штовхають у чергу.

it('dispatches an import products job', function () {
    
    Queue::fake();

    
    $this->post('import');

    
    Queue::assertPushed(ImportProductsJob::class);
});

Це гарантує, що моя робота буде виштовхнута в чергу на певний спусковий гачок, наприклад, потрапляння в кінцеву точку. Знову ж таки, піклується про Queue::fake() те, щоб не штовхати роботу. Ми не хочемо виконувати роботу в цей момент.

Але нам все одно доведеться протестувати роботу, чи не так? Звичайно. У ньому міститься вирішальна логіка цієї особливості:

it('imports products', function() {
   
    
    (new ImportProductsJob)->handle();

    
    $this->assertDatabaseCount(Product::class, 50);
    
    
})

Цей новий тест концентрується на роботі та на тому, що вона повинна робити. Ми запускаємо роботу безпосередньо, викликаючи handle на неї, яку має кожна робота.

Тестування сповіщень

Сповіщення чудово підходять для інформування ваших користувачів про важливі події. Обов'язково перевірте їх також:

it('sends notification about new product', function () {
    
    Notification::fake();
    $user = User::factory()->create();
    $product = Product::factory()->create();

    
    $this->artisan(InformAboutNewProductNotification::class, [
        'productId' => $product->id,
        'userId' => $user->id,
    ]);

    
    Notification::assertSentTo(
        [$user], NewProductNotification::class
    );
});

У наведеному вище прикладі ми тестуємо повідомлення, надіслане користувачеві при створенні нового продукту. Ми використовуємо метод, artisan щоб запустити сповіщення. Це відмінний спосіб перевірки сповіщень, спровокованих командою.

Знову ж таки, існує fake метод фасаду сповіщення, який гарантує, що фактичне сповіщення не надсилається.

Посилання: Дізнайтеся більше про тестування сповіщень в офіційних документах.

Тестування дій Дії

- це всього лише прості класи, які мають одну конкретну роботу. Це чудовий спосіб організувати ваш код і відокремити вашу логіку від контролерів, щоб підтримувати їх у чистоті. Але як їх перевірити?

Почнемо знову з боку. По-перше, ми хочемо перевірити, що наша дія називається при попаданні в конкретну кінцеву точку.

it('calls add-product-to-user action', function () {
    
    $this->mock(AddProductToUserAction::class)
        ->shouldReceive('handle')
        ->atLeast()->once();

    
    $product = Product::factory()->create();
    $user = User::factory()->create();

    
    $this->post("purchase/$user->id/$product->i");
});

Ми можемо зробити це, знущаючись над нашим класом дій і очікуючи, що метод ручки буде називатися. Але, знову ж таки, нас тут не цікавить, що робить наша дія; ми хочемо переконатися, що він викликається, коли ми натискаємо на контролер покупки.

Щоб це спрацювало, ми повинні переконатися, що контейнер вирішує наші дії.

class PurchaseController extends Controller
{
    public function __invoke(User $user, Product $product): void
    {
        app(AddProductToUserAction::class->handle($user, $product);

        
    }
}

Тоді ми також можемо перевірити саму дію. Як і робота, ми називаємо handle метод запуску дії.

it('adds product to user', function () {
    
    $product = Product::factory()->create();
    $user = User::factory()->create();

    
    (new AddProductToUserAction())->handle($user, $product);

    
    expect($user->products)
        ->toHaveCount(1)
        ->first()->id->toEqual($product->id);
});

Винятки

для тестування Іноді це добре, коли виняток кидається, тому що ми навмисно хочемо зупинити виконання нашого коду. Ми також можемо це перевірити.

it('stops if at least one account not found', function () {
    
    $this->artisan(MergeAccountsCommand::class, [
        'userId' => 1,
        'userToBeMergedId' => 2,
    ]);
})->throws(ModelNotFoundException::class);

Ми можемо прив'язати throws метод до нашого тесту в PEST. Це дозволить переконатися, що виняток кинуто.

Тестові блоки (Unit Tests)

Модульні тести відмінно підходять для тестування невеликих фрагментів коду, як один метод. Ніякі інші залежності не задіяні. Це робить їх дуже швидкими і легкими для написання.

Наш приклад стосується об'єкта даних. Він містить метод, який створює новий екземпляр з корисного навантаження вебхука.

class UserData
{
    public function __construct(
        public string $email,
        public string $name,
        public string $country,
    )
    {}

    public static function fromWebhookPayload(array $webhookCallData): UserData
    {
        return new self(
            $webhookCallData['client_email'],
            $webhookCallData['client_name'],
            $webhookCallData['client_country'],
        );
    }
}

У відповідному тесті ми лише перевіряємо, що цей метод повертає.

it('creates UserData object from paddle webhook call', function () {
    
    $payload = [
      'client_email' => '[email protected]',
      'client_name' => 'Christoph Rumpel',
      'client_country' => 'AT',
    ];

    
    $userData = UserData::fromWebhookPayload($payload);

    
    expect($userData)
        ->email->toBe('[email protected]')
        ->name->toBe('Christoph Rumpel')
        ->country->toBe('AT');
});

Підробка HTTP-дзвінків

Іноді вам потрібно здійснювати HTTP-дзвінки у вашому додатку. Це може бути отримання даних із зовнішнього API або надсилання даних до іншої служби. Ви часто хочете підробити ці дзвінки у своїх тестах, тому вам не доведеться покладатися на зовнішню службу.

it('import product', function () {
    
    Http::fake();

    
    
});

Метод fake на фасаді HTTP гарантує, що не буде здійснено реального дзвінка і що відповідь завжди є кодом статусу 200 .

Але ви також можете бути більш конкретними. Наприклад, ми тестуємо дію, яка отримує дані із зовнішнього API та зберігає їх у базі даних.

it('imports product', function() {
    
    Http::fake([
        'https://christoph-rumpel.com/import' => Http::response([
            'title' => 'My new product',
            'description' => 'This is a description',
        ]),
    ]);
    $user = User::factory()->create();

    
    (new ImportProductAction)->handle($user);

    
    $this->assertDatabaseHas(Product::class, [
        'title' => 'My new product',
        'description' => 'This is a description',
    ]);
});

Тестування HTTP-дзвінків Поруч із підробкою HTTP-дзвінків

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

it('make the right call', function () {
    
    Http::fake();
    $user = User::factory()->create();

    
    (new ImportProductAction)->handle($user);

    
    Http::assertSent(function ($request) {
        return $request->url() === 'https://christoph-rumpel.com/import'
            && $request['accessToken'] === '123456';
    });
});

Посилання: Дізнайтеся більше про підробку HTTP-дзвінків в офіційних документах.

Знущання над залежностями

При роботі з кодом з залежностями може бути корисно знущатися над ними. Це дозволить вам зосередитися на логіці вашого коду, а не на залежностях. Це також означає mocking can be useful for any kind of tests.

Ми вже робили це під час тестування наших класів дій, але це працює з будь-якою залежністю. У наступному прикладі ми маємо контролера з двома залежностями: постачальник платежів і поштовик.

class PaymentController extends Controller
{
    public function __invoke(PaymentProvider $paymentProvider, Mailer $mailer)
    {
        $paymentProvider->handle();

        $mailer->to(auth()->user())->send(new PaymentSuccessfulMail);
    }
}

У нашому тесті ми хочемо зосередитися на перевірці того, чи надсилається правильний електронний лист. Ось чому ми можемо знущатися над постачальником платежів і очікувати, handle що метод буде називатися. В результаті наш тест не провалиться, навіть незважаючи на те, що фактичного постачальника платежів ніколи не викликають.

it('sends payment successful mail', function () {
    
    Mail::fake();

    
    $this->mock(PaymentProvider::class)
        ->shouldReceive('handle')
        ->once();

    
    $this->post('payment');

    
    Mail::assertSent(PaymentSuccessfulMail::class);
});

Висновок

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

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

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297