ویرگول
ورودثبت نام
مجتبی پاکزاد
مجتبی پاکزادتکنیکال تیم لید شرکت داده پردازان آبشار هستم. برای خوندن بیشتر تجربیات و مطالعاتم من رو در باورژن baversion.com دنبال کنید.
مجتبی پاکزاد
مجتبی پاکزاد
خواندن ۶ دقیقه·۱۸ ساعت پیش

ست‌نویسی در لاراول با Pest و PHPUnit

اگر چند ماه روی یک پروژه لاراولی کار کرده باشید، احتمالا حداقل یک بار این تجربه را داشته‌اید: یک قابلیت جدید اضافه می‌کنید، همه چیز درست کار می‌کند، کد را دپلوی می‌کنید و ناگهان بخشی از سیستم که هیچ ارتباط مستقیمی با تغییرات شما نداشته از کار می‌افتد.

این اتفاق معمولا نتیجه نبود تست‌های خودکار است.

بسیاری از توسعه‌دهندگان تازه‌کار تصور می‌کنند تست‌نویسی فقط برای پروژه‌های بزرگ یا تیم‌های چند ده نفره است. اما واقعیت این است که حتی یک پروژه شخصی هم بدون تست به مرور زمان به کدی شکننده تبدیل می‌شود.

لاراول از همان روزهای ابتدایی خود توجه ویژه‌ای به تست‌نویسی داشته است. وجود PHPUnit به صورت پیش‌فرض، ابزارهای قدرتمند Mocking، امکان تست پایگاه داده و در سال‌های اخیر پشتیبانی فوق‌العاده از Pest PHP باعث شده لاراول یکی از بهترین اکوسیستم‌های تست‌نویسی در دنیای PHP را داشته باشد.

در این مقاله به صورت کامل یاد می‌گیریم:

  • تست‌نویسی چیست و چرا اهمیت دارد

  • تفاوت PHPUnit و Pest PHP

  • انواع تست‌ها در لاراول

  • ساخت Unit Test و Feature Test

  • تست پایگاه داده

  • Mock و Fake کردن سرویس‌ها

  • تست APIها

  • تست احراز هویت

  • بهترین شیوه‌های تست‌نویسی در پروژه‌های واقعی


تست‌نویسی چیست؟

تست‌نویسی فرآیند نوشتن کدهایی است که رفتار سایر بخش‌های برنامه را بررسی می‌کنند.

به جای اینکه بعد از هر تغییر برنامه را دستی اجرا کنیم و امیدوار باشیم چیزی خراب نشده باشد، مجموعه‌ای از تست‌ها را اجرا می‌کنیم تا مطمئن شویم همه چیز مطابق انتظار کار می‌کند.

مثلا فرض کنید متدی برای محاسبه مالیات داریم:

class TaxCalculator { public function calculate(float $amount): float { return $amount * 0.09; } }

می‌توانیم برای آن تست بنویسیم:

public function test_tax_calculation() { $calculator = new TaxCalculator(); $this->assertEquals( 90, $calculator->calculate(1000) ); }

اگر روزی کسی فرمول محاسبه را اشتباه تغییر دهد، تست بلافاصله خطا را مشخص می‌کند.


چرا تست‌نویسی اهمیت دارد؟

مزایای تست‌نویسی فقط پیدا کردن باگ نیست.

افزایش اطمینان هنگام تغییر کد

زمانی که صدها تست دارید، با خیال راحت ریفکتور می‌کنید.

کاهش هزینه نگهداری

رفع باگ در محیط پروداکشن معمولا بسیار گران‌تر از پیدا کردن آن در زمان توسعه است.

مستندسازی رفتار سیستم

تست‌ها نشان می‌دهند سیستم دقیقا چگونه باید رفتار کند.

افزایش کیفیت طراحی

کدی که تست‌پذیر باشد معمولا ساختار بهتری دارد.

توسعه سریع‌تر در بلندمدت

ممکن است ابتدا کمی کندتر پیش بروید اما در ادامه سرعت توسعه به شکل محسوسی افزایش پیدا می‌کند.


PHPUnit چیست؟

PHPUnit مشهورترین فریمورک تست در دنیای PHP است.

تقریبا تمام فریمورک‌های PHP از جمله لاراول روی PHPUnit بنا شده‌اند.

نمونه تست در PHPUnit:

class CalculatorTest extends TestCase { public function test_sum() { $this->assertEquals( 10, 5 + 5 ); } }

این ساختار سال‌هاست استاندارد صنعت محسوب می‌شود.


Pest PHP چیست؟

Pest یک لایه مدرن روی PHPUnit است.

Pest همان قدرت PHPUnit را ارائه می‌دهد اما با سینتکس ساده‌تر و خواناتر.

مثال قبلی در Pest:

it('calculates sum correctly', function () { expect(5 + 5)->toBe(10); });

بسیاری از توسعه‌دهندگان لاراول امروزه Pest را به دلیل خوانایی بیشتر ترجیح می‌دهند.


PHPUnit یا Pest؟

این سوال تقریبا در تمام پروژه‌های جدید مطرح می‌شود.

PHPUnit

مزایا:

  • استاندارد صنعت

  • مستندات فراوان

  • سازگاری کامل با اکوسیستم PHP

  • مناسب پروژه‌های قدیمی

معایب:

  • سینتکس طولانی‌تر

  • خوانایی کمتر


Pest

مزایا:

  • سینتکس تمیز

  • خوانایی بسیار بالا

  • مناسب BDD

  • سرعت توسعه بیشتر

معایب:

  • جدیدتر از PHPUnit

  • برخی تیم‌ها هنوز ترجیح می‌دهند فقط PHPUnit استفاده کنند


پیشنهاد عملی

اگر پروژه جدیدی را شروع می‌کنید:

Pest انتخاب بهتری است.

اگر روی پروژه‌ای قدیمی کار می‌کنید که صدها تست PHPUnit دارد:

همان PHPUnit را ادامه دهید.


نصب Pest در لاراول

composer require pestphp/pest --dev

سپس:

php artisan pest:install

انواع تست در لاراول

به طور کلی دو دسته اصلی داریم:

Unit Test

کوچک‌ترین واحد برنامه را تست می‌کند.

مثال:

TaxCalculator PriceFormatter DiscountService

Feature Test

رفتار چند بخش سیستم را با هم بررسی می‌کند.

مثال:

  • ثبت نام کاربر

  • ورود

  • ثبت سفارش

  • ایجاد محصول

Feature Test در پروژه‌های لاراولی بیشترین کاربرد را دارد.


ساخت اولین تست

php artisan make:test TaxCalculatorTest

یا:

php artisan make:test TaxCalculatorTest --unit

ساخت Unit Test

کلاس:

class TaxCalculator { public function calculate(float $amount): float { return $amount * 0.09; } }

تست:

public function test_tax_is_calculated() { $calculator = new TaxCalculator(); $result = $calculator->calculate(1000); $this->assertEquals( 90, $result ); }

اجرای تست:

php artisan test

ساخت Feature Test

فرض کنید Route زیر وجود دارد:

Route::get('/health', function () { return response()->json([ 'status' => 'ok' ]); });

تست:

public function test_health_endpoint() { $response = $this->get('/health'); $response->assertStatus(200); $response->assertJson([ 'status' => 'ok' ]); }

تست API در لاراول

فرض کنید API محصولات داریم.

$response = $this->getJson('/api/products'); $response->assertOk();

بررسی ساختار پاسخ:

$response->assertJsonStructure([ '*' => [ 'id', 'title', 'price' ] ]);

تست ثبت نام کاربران

$response = $this->post('/register', [ 'name' => 'Ashkan', 'email' => 'ashkan@test.com', 'password' => 'password', 'password_confirmation' => 'password', ]);

بررسی:

$response->assertRedirect();

تست لاگین کاربران

$user = User::factory()->create(); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password' ]);
$this->assertAuthenticated();

تست خروج از حساب کاربری

$this->actingAs($user); $this->post('/logout'); $this->assertGuest();

استفاده از Model Factory

به جای ساخت دستی داده‌ها:

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

یا:

$users = User::factory() ->count(10) ->create();

این روش باعث تمیزتر شدن تست‌ها می‌شود.


تست پایگاه داده

لاراول امکانات فوق‌العاده‌ای برای تست دیتابیس دارد.

ابتدا:

use RefreshDatabase;

سپس:

uses(RefreshDatabase::class);

یا:

use RefreshDatabase;

در PHPUnit:

class ExampleTest extends TestCase { use RefreshDatabase; }

بررسی ثبت اطلاعات در دیتابیس

$this->assertDatabaseHas( 'users', [ 'email' => 'ashkan@test.com' ] );

بررسی عدم وجود داده

$this->assertDatabaseMissing( 'users', [ 'email' => 'wrong@test.com' ] );

تست Soft Delete

$this->assertSoftDeleted($user);

Mock چیست؟

گاهی نمی‌خواهیم سرویس واقعی اجرا شود.

مثلا:

  • سرویس پیامک

  • سرویس پرداخت

  • API خارجی

در این شرایط از Mock استفاده می‌کنیم.


Mock کردن سرویس

فرض کنید:

interface SmsProvider { public function send( string $phone, string $message ); }

در تست:

$sms = Mockery::mock( SmsProvider::class ); $sms->shouldReceive('send') ->once();

استفاده از Fake در لاراول

لاراول ابزارهای Fake آماده دارد.


Mail Fake

Mail::fake();

بررسی:

Mail::assertSent( WelcomeMail::class );

Queue Fake

Queue::fake();
Queue::assertPushed( SendInvoiceJob::class );

Event Fake

Event::fake();
Event::assertDispatched( UserRegistered::class );

Notification Fake

Notification::fake();
Notification::assertSentTo( $user, WelcomeNotification::class );

تست میدلور

فرض کنید Route فقط برای کاربران احراز هویت شده قابل دسترس است.

$response = $this->get('/dashboard'); $response->assertRedirect( '/login' );

تست کاربران لاگین‌شده

$user = User::factory()->create(); $response = $this ->actingAs($user) ->get('/dashboard'); $response->assertOk();

تست Validation

فرض کنید ایمیل الزامی است.

$response = $this->post( '/register', [] );

بررسی:

$response ->assertSessionHasErrors([ 'email' ]);

تست Exceptionها

$this->expectException( InvalidArgumentException::class );

سپس:

$service->execute();

پوشش تست (Test Coverage)

برای مشاهده میزان پوشش تست:

php artisan test --coverage

نمونه خروجی:

Classes: 82% Methods: 89% Lines: 91%

اما مراقب باشید.

Coverage بالا الزاما به معنی تست خوب نیست.

ممکن است ۹۵ درصد کاوریج داشته باشید اما سناریوهای مهم را تست نکرده باشید.


ساختار مناسب پوشه‌های تست

tests/ ├── Unit ├── Feature ├── Helpers ├── Traits └── Factories

این ساختار در پروژه‌های بزرگ نگهداری تست‌ها را ساده‌تر می‌کند.


اشتباهات رایج در تست‌نویسی

تست بیش از حد جزئیات

نباید Implementation را تست کنید.

رفتار را تست کنید.

وابستگی تست‌ها به هم

هر تست باید مستقل باشد.

نام‌گذاری ضعیف

بد:

test1()

خوب:

test_user_can_create_order()

استفاده نکردن از Factory

داده‌های تست را دستی نسازید.

تست نکردن سناریوهای خطا

فقط مسیر موفقیت را تست نکنید.

سناریوهای شکست معمولا مهم‌تر هستند.


چه چیزهایی را باید تست کنیم؟

اولویت پیشنهادی در پروژه‌های واقعی:

  1. Authentication

  2. Authorization

  3. Payment Logic

  4. Business Rules

  5. API Endpoints

  6. Critical Jobs

  7. Event Listeners

و در انتها:

  1. Helperهای ساده


آیا باید همه چیز را تست کنیم؟

خیر.

مثلا برای این کد:

return strtoupper($name);

نوشتن تست معمولا ارزش چندانی ندارد.

اما برای:

calculateInvoiceTax()

یا:

approveOrder()

یا:

generateMonthlyReport()

قطعاً باید تست وجود داشته باشد.


جمع‌بندی

تست‌نویسی در لاراول دیگر یک قابلیت لوکس یا مخصوص شرکت‌های بزرگ نیست، بلکه بخشی ضروری از فرآیند توسعه نرم‌افزار محسوب می‌شود. هرچه پروژه بزرگ‌تر شود، ارزش تست‌ها بیشتر نمایان می‌شود. بدون تست، هر تغییر می‌تواند باعث ایجاد باگ‌های غیرمنتظره شود و توسعه‌دهنده را از ریفکتور کردن یا اضافه کردن قابلیت‌های جدید بترساند.

لاراول با فراهم کردن ابزارهایی مانند PHPUnit، Pest PHP، Model Factory، Database Testing، Mocking و Fakeها، تقریبا تمام امکانات مورد نیاز برای ایجاد یک مجموعه تست حرفه‌ای را در اختیار شما قرار می‌دهد.

اگر امروز هیچ تستی در پروژه خود ندارید، لازم نیست از همان ابتدا صدها تست بنویسید. از بخش‌های حیاتی سیستم شروع کنید؛ ثبت‌نام، ورود، قوانین کسب‌وکار و APIها. سپس به تدریج پوشش تست را افزایش دهید. در مدت کوتاهی متوجه خواهید شد که تست‌نویسی نه تنها زمان شما را هدر نمی‌دهد، بلکه سرعت توسعه، کیفیت کد و اعتماد به تغییرات را به شکل محسوسی افزایش می‌دهد.

unit testتستtestلاراول
۰
۰
مجتبی پاکزاد
مجتبی پاکزاد
تکنیکال تیم لید شرکت داده پردازان آبشار هستم. برای خوندن بیشتر تجربیات و مطالعاتم من رو در باورژن baversion.com دنبال کنید.
شاید از این پست‌ها خوشتان بیاید