• Время чтения ~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 Laravel, такие как 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,
            ],
        ]);
});

Ссылка: Узнайте больше о тестировании ответов на странице в официальных документах.

Поскольку мы храним данные в базе данных, мы хотим убедиться, что данные хранятся правильно. Именно здесь Laravel может помочь вам с некоторыми удобными помощниками по утверждению.

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',
    ]);
});

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

Ссылка: Найти все утверждения базы данных по официальной документации.

Тестирование Validation

Validation является важной частью многих приложений. Вы хотите убедиться, что могут быть отправлены только действительные данные. По умолчанию 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 хотим проверить функциональность отношений; это то, что Laravel уже делает. Мы хотим убедиться, что отношения определены.

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);
});

Ссылка: Узнайте обо всех других утверждениях для тестирования содержимого почты в официальных документах.

Тестирование заданий и очередей

Мне нравится тестировать задания и очереди отдельно, начиная снаружи. Это означает, что я проверяю, что задание отправляется в очередь.

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");
});

Мы можем сделать это, издеваясь над нашим классом действия и ожидая, что будет вызван метод handle. Но, опять же, нас здесь не интересует, что делают наши действия; мы хотим убедиться, что он вызывается, когда мы нажимаем на наш контроллер покупки.

Чтобы это работало, мы должны убедиться, что контейнер разрешает наши действия.

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. Это гарантирует, что исключение будет создано.

Модульные тесты (модульные тесты)

Модульные тесты отлично подходят для тестирования небольших фрагментов кода, таких как один метод. Никакие другие зависимости не задействованы. Это делает их очень быстрыми и легкими в написании.

Наш пример касается объекта данных. Он содержит метод, который создает новый экземпляр из полезных данных веб-перехватчика.

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