Поширеною проблемою в тестуванні є не тільки ТЕ, ЯК щось перевірити, але й ТЕ, ЩО ви можете перевірити. Ось чому я склав список усіх речей, які мені подобається тестувати у своїх програмах.
Всі тестові приклади зосереджені на концепціях тестування і можуть бути застосовані до всіх фреймворків тестування. Мої приклади написані за допомогою PEST.
Шукаєте щось конкретне? Виберіть його:
- Стан відповіді на сторінку тестування
- Тестування тексту відповіді на сторінці
- Тестування перегляду відповідей на сторінку
- Тестування відповіді на сторінку JSON
- Тестування проти бази даних
- Валідація тестування
- Тестування моделей / зв'язків
- Тестування пошти
- Тестування вмісту пошти
- Тестування вакансій та черг
- Повідомлення про тестування
- Дії з тестування
- Винятки з тестування
- Тестові блоки (модульні тести)
- Підробка HTTP-дзвінків
- Тестування HTTP-дзвінків
- Глузливі залежності
Примітка: Всі приклади також можна знайти в спеціальному репозиторії.
Тестування статусу відповіді на сторінку Тестування
відповіді відповідь є одним з найпростіших тестів для написання, тим не менш, це надзвичайно корисно.
Це гарантує, що сторінка відповідає правильним кодом статусу 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. Я завжди радий вчитися новому.