Членове

Научете как да правите тестове в Laravel с прости примери, като използвате PHPUnit и PEST

Когато става въпрос за автоматизирани тестове или модулни тестове, във всеки език за програмиране има две противоположни мнения:

  • Загуба на време
  • Не можете без него

Така че с тази статия ще се опитаме да убедим първото, особено като демонстрираме колко лесно е да започнете с автоматизирано тестване в Laravel.

Първо нека поговорим за „защо“, а след това нека видим някои примери за това как.

Защо имаме нужда от автоматизирано тестване

Автоматизираните тестове изпълняват части от кода и докладват всички грешки. Това е най-простият начин да ги опишем. Представете си да пуснете нова функция в приложение и след това личен асистент робот ще отиде и ще тества ръчно новата функция, като същевременно ще тества дали новият код не нарушава някоя от старите функции.

Това е основното предимство: повторно тестване на всички функции автоматично. Това може да изглежда като допълнителна работа, но ако не кажете на „робота“ да го направи, алтернативно трябва да го направим ръчно, нали? 

Или могат да бъдат пуснати нови функции, без да се тества дали работят, с надеждата, че потребителите ще докладват грешки.

Автоматизираните тестове могат да ни дадат няколко предимства:

  • Спестете време за ръчно тестване;
  • Те ви позволяват да спестите време както за внедрената нова функция, така и за консолидираните функции, като избягвате регресията;
  • Умножете тази полза с всички нови функции и всички вече внедрени функции;
  • Предишните три точки се отнасят за всяка нова версия;
  • ...

Опитайте се да си представите вашето приложение след година или две, с нови разработчици в екипа, които не знаят кода, написан през предишни години, или дори как да го тестват. 

Първите ни автоматизирани тестове

За изпълнение на първото автоматизирано тестване в Laravel, не е необходимо да пишете никакъв код. Да, правилно прочетохте. Всичко вече е конфигурирано и подготвено в предварителната инсталацияdefiкрая на Laravel, включително първия основен пример.

Можете да опитате да инсталирате проект на Laravel и да стартирате първите тестове веднага:

laravel new project
cd project
php artisan test

Това трябва да е резултатът във вашата конзола:

Ако разгледаме преdefiкрая на Laravel /tests, имаме два файла:

tests/Feature/ExampleTest.php :

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Не е необходимо да знаете синтаксис, за да разберете какво се случва тук: заредете началната страница и проверете дали кодът на състоянието HTTP è "200 OK".

Известен също като име на метод test_the_application_returns_a_successful_response() става четим текст, когато видите резултатите от теста, просто като замените символа за подчертаване с интервал.

tests/Unit/ExampleTest.php :

class ExampleTest extends TestCase
{
    public function test_that_true_is_true()
    {
        $this->assertTrue(true);
    }
}

Изглежда малко безсмислено да проверявате дали това е вярно? 

Малко по-късно ще говорим специално за тестовете на модулите. Засега трябва да разберете какво обикновено се случва във всеки тест.

  • Всеки тестов файл в папката /tests е PHP клас, който разширява TestCase на PHPUnit
  • В рамките на всеки клас можете да създадете множество методи, обикновено един метод за ситуация за тестване
  • Във всеки метод има три действия: подготовка на ситуацията, след това действие и след това проверка (потвърждение) дали резултатът е според очакванията

Структурно, това е всичко, което трябва да знаете, всичко останало зависи от точните неща, които искате да тествате.

За да генерирате празен тестов клас, просто изпълнете тази команда:

php artisan make:test HomepageTest

Файлът се генерира tests/Feature/HomepageTest.php:

class HomepageTest extends TestCase
{
    // Replace this method with your own ones
    public function test_example()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Сега нека да видим какво се случва, ако тестовият код се провали в Laravel

Нека сега да видим какво се случва, ако тестовите твърдения не върнат очаквания резултат.

Нека променим примерните тестове на това:

class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/non-existing-url');
 
        $response->assertStatus(200);
    }
}
 
 
class ExampleTest extends TestCase
{
    public function test_that_true_is_false()
    {
        $this->assertTrue(false);
    }
}

И сега, ако изпълним командата php artisan test отново:

 FAIL  Tests\Unit\ExampleTest
⨯ that true is true
 
 FAIL  Tests\Feature\ExampleTest
⨯ the application returns a successful response
 
---
 
• Tests\Unit\ExampleTest > that true is true
Failed asserting that false is true.
 
at tests/Unit/ExampleTest.php:16
   12▕      * @return void
   13▕      */
   14▕     public function test_that_true_is_true()
   15▕     {
➜  16▕         $this->assertTrue(false);
   17▕     }
   18▕ }
   19▕
 
• Tests\Feature\ExampleTest > the application returns a successful response
Expected response status code [200] but received 404.
Failed asserting that 200 is identical to 404.
 
at tests/Feature/ExampleTest.php:19
   15▕     public function test_the_application_returns_a_successful_response()
   16▕     {
   17▕         $response = $this->get('/non-existing-url');
   18▕
➜  19▕         $response->assertStatus(200);
   20▕     }
   21▕ }
   22▕
 
 
Tests:  2 failed
Time:   0.11s

Има два неуспешни теста, маркирани като НЕУСПЕШЕН, с обяснения по-долу и стрелки, сочещи към точния ред от неуспешни тестове. Грешките се обозначават по този начин.

Пример: Тестване на код на формуляр за регистрация в Laravel

Да предположим, че имаме формуляр и трябва да тестваме различни случаи: проверяваме дали е неуспешен с невалидни данни, проверяваме дали е успешен с правилния вход и т.н.

Официалният стартов комплект от Laravel Breeze включва i тестване на функционалността в него. Нека да разгледаме някои примери от там:

tests/Feature/RegistrationTest.php

use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class RegistrationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_registration_screen_can_be_rendered()
    {
        $response = $this->get('/register');
 
        $response->assertStatus(200);
    }
 
    public function test_new_users_can_register()
    {
        $response = $this->post('/register', [
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
}

Тук имаме два теста в един клас, тъй като и двата са свързани с формуляра за регистрация: един проверява дали формулярът е зареден правилно, а друг проверява дали изпращането работи добре.

Нека се запознаем с още два метода за проверка на резултата, още две твърдения: $this->assertAuthenticated()$response->assertRedirect(). Можете да проверите всички налични твърдения в официалната документация на PHPUnit e Отговор на Laravel . Обърнете внимание, че се срещат някои общи твърдения по темата $this, докато други проверяват конкретните $responseот обаждането на маршрута.

Друго важно нещо е use RefreshDatabase;израз, с щрих, вмъкнат над класа. Необходимо е, когато тестовите действия могат да повлияят на базата данни, както в този пример, регистрирането добавя нов запис в usersтаблица на база данни. За целта трябва да създадете отделна тестова база данни, която ще се актуализира с php artisan migrate:freshвсеки път, когато се изпълняват тестовете.

Имате две възможности: физически да създадете отделна база данни или да използвате SQLite база данни в паметта. И двете са конфигурирани във файла phpunit.xmlпредоставени по подразбиранеdefiнита с Laravel. По-конкретно, имате нужда от тази част:

<php>
    <env name="APP_ENV" value="testing"/>
    <env name="BCRYPT_ROUNDS" value="4"/>
    <env name="CACHE_DRIVER" value="array"/>
    <!-- <env name="DB_CONNECTION" value="sqlite"/> -->
    <!-- <env name="DB_DATABASE" value=":memory:"/> -->
    <env name="MAIL_MAILER" value="array"/>
    <env name="QUEUE_CONNECTION" value="sync"/>
    <env name="SESSION_DRIVER" value="array"/>
    <env name="TELESCOPE_ENABLED" value="false"/>
</php>

Вижте DB_CONNECTIONDB_DATABASEкои се коментират? Ако имате SQLite на вашия сървър, най-простото действие е просто да премахнете коментарите от тези редове и вашите тестове ще се изпълняват срещу тази база данни в паметта.

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

В допълнение към този код:

$this->assertAuthenticated();
$response->assertRedirect(RouteServiceProvider::HOME);

Можем също да използваме твърденията за тестване на базата данни и направете нещо подобно:

$this->assertDatabaseCount('users', 1);
 
// Or...
$this->assertDatabaseHas('users', [
    'email' => 'test@example.com',
]);

Пример за страницата за вход

Нека сега видим друг пример за страница за вход с Laravel Breeze

tests/Feature/AuthenticationTest.php:

class AuthenticationTest extends TestCase
{
    use RefreshDatabase;
 
    public function test_login_screen_can_be_rendered()
    {
        $response = $this->get('/login');
 
        $response->assertStatus(200);
    }
 
    public function test_users_can_authenticate_using_the_login_screen()
    {
        $user = User::factory()->create();
 
        $response = $this->post('/login', [
            'email' => $user->email,
            'password' => 'password',
        ]);
 
        $this->assertAuthenticated();
        $response->assertRedirect(RouteServiceProvider::HOME);
    }
 
    public function test_users_can_not_authenticate_with_invalid_password()
    {
        $user = User::factory()->create();
 
        $this->post('/login', [
            'email' => $user->email,
            'password' => 'wrong-password',
        ]);
 
        $this->assertGuest();
    }
}

Става въпрос за формата за вход. Логиката е подобна на регистрацията, нали? Но три метода вместо два, така че това е пример за тестване на добри и лоши сценарии. Така че общата логика е, че трябва да тествате и двата случая: когато нещата вървят добре и когато се провалят.

Иновационен бюлетин
Не пропускайте най-важните новини за иновациите. Регистрирайте се, за да ги получавате по имейл.

Освен това това, което виждате в този тест, е употребата на Фабрики за бази данни : Laravel създава фалшив потребител ( отново във вашата актуализирана тестова база данни ) и след това се опитва да влезе с правилни или неправилни идентификационни данни.

Още веднъж, Laravel генерира фабричните predefiнита с неверни данни за Userмодел, извън кутията.

database/factories/UserFactory.php:

class UserFactory extends Factory
{
    public function definition()
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ];
    }
}

Виждате ли, колко много неща са подготвени от самия Laravel, така че лесно ли ще ни бъде да започнем да тестваме?

Така че, ако изпълним php artisan testслед като инсталираме Laravel Breeze, трябва да видим нещо подобно:

 PASS  Tests\Unit\ExampleTest
✓ that true is true
 
 PASS  Tests\Feature\Auth\AuthenticationTest
✓ login screen can be rendered
✓ users can authenticate using the login screen
✓ users can not authenticate with invalid password
 
 PASS  Tests\Feature\Auth\EmailVerificationTest
✓ email verification screen can be rendered
✓ email can be verified
✓ email is not verified with invalid hash
 
 PASS  Tests\Feature\Auth\PasswordConfirmationTest
✓ confirm password screen can be rendered
✓ password can be confirmed
✓ password is not confirmed with invalid password
 
 PASS  Tests\Feature\Auth\PasswordResetTest
✓ reset password link screen can be rendered
✓ reset password link can be requested
✓ reset password screen can be rendered
✓ password can be reset with valid token
 
 PASS  Tests\Feature\Auth\RegistrationTest
✓ registration screen can be rendered
✓ new users can register
 
 PASS  Tests\Feature\ExampleTest
✓ the application returns a successful response
 
Tests:  17 passed
Time:   0.61s

Функционални тестове в сравнение с модулни тестове и други

Видяхте подпапките tests/Feature e tests/Unit ?. 

Каква е разликата между тях? 

В световен мащаб, извън екосистемата Laravel/PHP, има няколко вида автоматизирано тестване. Можете да намерите термини като:

  • Единични тестове
  • Тестване на функции
  • Интеграционни тестове
  • Функционални тестове
  • Тестване от край до край
  • Тестове за приемане
  • Тестове за дим
  • и т.н.

Звучи сложно и действителните разлики между тези видове тестове понякога са замъглени. Ето защо Laravel опрости всички тези объркващи термини и ги групира в две: единица/функция.

Просто казано, тестовете на функции се опитват да изпълнят действителната функционалност на вашите приложения: вземете URL адреса, извикайте API, имитирайте точното поведение като попълване на формуляра. Тестовете на функциите обикновено извършват същите или подобни операции, както всеки потребител на проект би направил ръчно в реалния живот.

Единичните тестове имат две значения. Като цяло може да откриете, че всеки автоматизиран тест се нарича „единично тестване“ и целият процес може да се нарече „единично тестване“. Но в контекста на функционалност срещу единица, този процес е за тестване на конкретна непублична единица код, в изолация. Например, имате клас Laravel с метод, който изчислява нещо, като общата цена на поръчката с параметри. Следователно единичният тест ще посочи дали са върнати правилни резултати от този метод (кодова единица) с различни параметри.

За да генерирате единичен тест, трябва да добавите флаг:

php artisan make:test OrderPriceTest --unit

Генерираният код е същият като преди модулния тестdefiLaravel система:

class OrderPriceTest extends TestCase
{
    public function test_example()
    {
        $this->assertTrue(true);
    }
}

Както виждате, не съществува RefreshDatabase, а това е едно от defiнай-често срещаните дефиниции на модулен тест: не докосва базата данни, работи като „черна кутия“, изолирана от работещото приложение.

Опитвайки се да имитираме примера, който споменах по-рано, нека си представим, че имаме клас на обслужване OrderPrice.

app/Services/OrderPriceService.php:

class OrderPriceService
{
    public function calculatePrice($productId, $quantity, $tax = 0.0)
    {
        // Some kind of calculation logic
    }
}

Тогава единичният тест може да изглежда така:

class OrderPriceTest extends TestCase
{
    public function test_single_product_no_taxes()
    {
        $product = Product::factory()->create(); // generate a fake product
        $price = (new OrderPriceService())->calculatePrice($product->id, 1);
        $this->assertEquals(1, $price);
    }
 
    public function test_single_product_with_taxes()
    {
        $price = (new OrderPriceService())->calculatePrice($product->id, 1, 20);
        $this->assertEquals(1.2, $price);
    }
 
    // More cases with more parameters
}

Според моя личен опит с проекти на Laravel, по-голямата част от тестовете са Feature tests, а не Unit tests. Първо, трябва да тествате дали вашето приложение работи по начина, по който реалните хора биха го използвали.

След това, ако имате специални изчисления или логика, можете definire като единица, с параметри, можете да създавате единици тестове специално за това.

Понякога писането на тестове изисква модифициране на самия код и преработването му, за да стане по-тестваем: разделяне на единиците в специални класове или методи.

Кога/как да се правят тестове?

Каква е действителната полза от това php artisan test, кога трябва да го стартирате?

Има различни подходи, в зависимост от вашия бизнес работен процес, но като цяло трябва да се уверите, че всички тестове са „зелени“ (т.е. без грешки), преди да изпратите най-новите промени в кода в хранилището.

След това работите локално върху задачата си и когато смятате, че сте готови, изпълнете някои тестове, за да сте сигурни, че не сте счупили нещо. Не забравяйте, че вашият код може да причини грешки не само във вашата логика, но и неволно да наруши някое друго поведение в нечий друг код, написан отдавна.

Ако направим крачка напред, е възможно да се автоматизира много нещата. С различни инструменти за CI/CD можете да зададете тестове, които да се изпълняват всеки път, когато някой накара промени в конкретен клон на Git или преди сливане на код в производствения клон. Най-простият работен процес би бил да използвам Github Actions, имам отделно видео което го доказва.

Какво трябва да тествате?

Има различни мнения относно това колко голямо трябва да бъде така нареченото „тестово покритие“: опитайте всяка възможна операция и случай на всяка страница или ограничете работата до най-важните части.

Всъщност тук съм съгласен с хората, които обвиняват автоматизираното тестване, че отнема повече време, отколкото осигурява действителна полза. Това може да се случи, ако пишете тестове за всеки един детайл. Това каза, че може да се изисква от вашия проект: основният въпрос е „каква е цената на потенциалната грешка“.

С други думи, трябва да дадете приоритет на усилията си за тестване, като зададете въпроса „Какво ще се случи, ако този код се провали?“ Ако вашата платежна система има грешки, това ще се отрази пряко на бизнеса. Така че, ако функционалността на вашите роли/разрешения е повредена, това е огромен проблем със сигурността.

Харесва ми как Мат Стауфър го каза на конференция: „Първо трябва да тествате тези неща, които, ако се провалят, ще ви уволнят от работата ви.“ Разбира се, това е преувеличено, но разбирате идеята: опитайте първо важните неща. И след това други функции, ако имате време.

PEST: нова алтернатива на PHPUnit

Всички горни примери са базирани на инструмента за предварително тестване на Laraveldefiвечер: PHPUnit . Но през годините в екосистемата се появиха други инструменти и един от най-новите популярни е ВРЕДИ . Създаден от официален служител на Laravel Нуно Мадуро , има за цел да опрости синтаксиса, правейки писането на код за тестове още по-бързо.

Под капака работи su PHPUnit, като допълнителен слой, просто се опитва да минимизира някои предварително повтарящи се частиdefiкрая на кода PHPUnit.

Нека разгледаме един пример. Запомнете класа за предварителен тест на функциятаdefiв Laravel? Ще ви напомня:

namespace Tests\Feature;
 
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class ExampleTest extends TestCase
{
    public function test_the_application_returns_a_successful_response()
    {
        $response = $this->get('/');
 
        $response->assertStatus(200);
    }
}

Знаете ли как би изглеждал същият тест с PEST?

test('the application returns a successful response')->get('/')->assertStatus(200);

Да, ЕДИН ред код и това е всичко. И така, целта на PEST е да премахне режийните разходи за:

  • Създаване на класове и методи за всичко;
  • Разширение на тестови случаи;
  • Чрез поставяне на действия на отделни редове: в PEST можете да ги свържете заедно.

За да генерирате PEST тест в Laravel, трябва да посочите допълнителен флаг:

php artisan make:test HomepageTest --pest

Към момента на писане на тази статия PEST е доста популярен сред разработчиците на Laravel, но ваше лично предпочитание е дали да използвате този допълнителен инструмент и да научите неговия синтаксис, както и бележка за PHPUnit.

BlogInnovazione.it

Иновационен бюлетин
Не пропускайте най-важните новини за иновациите. Регистрирайте се, за да ги получавате по имейл.

Последни статии

Предимствата на страниците за оцветяване за деца - свят на магия за всички възрасти

Развитието на фини двигателни умения чрез оцветяване подготвя децата за по-сложни умения като писане. Оцветявам…

2 май 2024

Бъдещето е тук: Как корабната индустрия революционизира глобалната икономика

Военноморският сектор е истинска световна икономическа сила, която се е насочила към пазар от 150 милиарда...

1 май 2024

Издателите и OpenAI подписват споразумения за регулиране на потока от информация, обработвана от изкуствения интелект

Миналия понеделник Financial Times обяви сделка с OpenAI. FT лицензира своята журналистика от световна класа...

30 април 2024

Онлайн плащания: Ето как услугите за поточно предаване ви карат да плащате завинаги

Милиони хора плащат за стрийминг услуги, като плащат месечни абонаментни такси. Разпространено е мнението, че вие…

29 април 2024

Прочетете Иновация на вашия език

Иновационен бюлетин
Не пропускайте най-важните новини за иновациите. Регистрирайте се, за да ги получавате по имейл.

Следвайте ни