سلام! من آرش فدائی هستم و تو این قسمت از سری آموزشی لاراول ۱۲ قراره درباره تست‌نویسی صحبت کنیم. تست‌نویسی به شما کمک می‌کنه مطمئن بشید کدتون درست کار می‌کنه و با تغییرات بعدی خراب نمی‌شه. لاراول ابزارهای فوق‌العاده‌ای مثل PHPUnit و Artisan برای تست داره که کار رو خیلی راحت می‌کنن. تو این مقاله، یاد می‌گیرید چطور تست‌های واحد (Unit Tests) و تست‌های فیچر (Feature Tests) بنویسید و یه سری نکات عملی رو بررسی می‌کنیم. بریم شروع کنیم!

تست‌نویسی: تجربه شخصی من

اولین باری که تست‌نویسی رو امتحان کردم، فکر می‌کردم فقط وقت‌تلف‌کردنه! اما وقتی تو یه پروژه یه باگ عجیب پیدا کردم که ساعت‌ها طول کشید تا درستش کنم، فهمیدم اگه تست نوشته بودم، خیلی زودتر مشکل رو پیدا می‌کردم. تو یکی از پروژه‌هام، داشتم یه سیستم مدیریت کاربر می‌ساختم و تست‌ها باعث شدن خیالم راحت باشه که قابلیت‌های حساس مثل لاگین درست کار می‌کنن. تو لاراول ۱۲، ابزارهای تست حتی قوی‌تر شدن و قابلیت‌هایی مثل بهبودهای دیباگ و تست‌های موازی اضافه شده. حالا بیاید با یه مثال عملی وارد بشیم!

پیش‌نیازها

قبل از شروع، مطمئن بشید که:

  • پروژه لاراول‌تون درست تنظیم شده (مثل قسمت اول).

  • مدل Post و کنترلر PostController رو از قسمت‌های قبلی دارید.

  • سیستم احراز هویت (مثل چیزی که تو قسمت پنجم با Breeze نصب کردیم) فعاله.

  • PHPUnit به صورت پیش‌فرض با لاراول نصب شده (نیازی به نصب جداگونه نیست).

آشنایی با انواع تست‌ها

لاراول از PHPUnit برای تست‌نویسی پشتیبانی می‌کنه و دو نوع تست اصلی داره:

  • Unit Tests: برای تست بخش‌های کوچک و مستقل کد (مثل یه تابع).

  • Feature Tests: برای تست قابلیت‌های کامل اپلیکیشن (مثل یه مسیر API یا فرم).

ما تو این آموزش روی Feature Tests تمرکز می‌کنیم چون برای اکثر پروژه‌ها کاربردی‌تره.

ساخت اولین تست فیچر

بیاید یه تست برای مسیر /posts بنویسیم که چک کنه آیا لیست پست‌ها درست برمی‌گرده. دستور زیر رو اجرا کنید تا یه فایل تست بسازید:

php artisan make:test PostTest

فایل tests/Feature/PostTest.php رو باز کنید و اینجوری اصلاحش کنید:

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\Post;

class PostTest extends TestCase
{
    use RefreshDatabase;

    public function test_posts_index_displays_posts()
    {
        Post::factory()->create([
            'title' => 'پست آزمایشی',
            'content' => 'محتوای آزمایشی',
        ]);

        $response = $this->get('/posts');

        $response->assertStatus(200);
        $response->assertSee('پست آزمایشی');
    }
}

اینجا:

  • RefreshDatabase دیتابیس رو برای هر تست ریست می‌کنه.

  • Post::factory() یه پست تستی می‌سازه.

  • assertStatus(200) چک می‌کنه که پاسخ HTTP کد 200 (موفق) برگردونه.

  • assertSee چک می‌کنه که متن “پست آزمایشی” تو صفحه باشه.

برای اجرای تست‌ها، این دستور رو بزنید:

php artisan test

یه تجربه از خودم: یه بار فراموش کردم RefreshDatabase رو اضافه کنم و تست‌ها به داده‌های قدیمی وابسته شدن و نتایج عجیبی دادن. همیشه از RefreshDatabase برای تست‌های دیتابیس استفاده کنید!

ساخت Factory برای پست‌ها

برای تست بالا، نیاز به یه Factory برای مدل Post داریم. لاراول به صورت پیش‌فرض پشتیبانی از Factory داره. فایل database/factories/PostFactory.php رو با این دستور بسازید:

php artisan make:factory PostFactory --model=Post

و اینجوری اصلاحش کنید:

<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    protected $model = \App\Models\Post::class;

    public function definition()
    {
        return [
            'title' => $this->faker->sentence,
            'content' => $this->faker->paragraph,
        ];
    }
}

این Factory داده‌های تستی برای پست‌ها تولید می‌کنه. من تو یه پروژه داشتم تست برای یه سیستم وبلاگ می‌نوشتم و Factoryها کلی تو تولید داده‌های تستی کمکم کردن.

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

بیاید یه تست بنویسیم که چک کنه فقط کاربرهای لاگین‌شده می‌تونن پست جدید بسازن. تو PostTest.php این تست رو اضافه کنید:

public function test_only_authenticated_users_can_create_posts()
{
    // تست بدون لاگین
    $response = $this->post('/posts', [
        'title' => 'پست جدید',
        'content' => 'محتوای جدید',
    ]);

    $response->assertRedirect('/login');

    // تست با لاگین
    $user = \App\Models\User::factory()->create();
    $response = $this->actingAs($user)->post('/posts', [
        'title' => 'پست جدید',
        'content' => 'محتوای جدید',
    ]);

    $response->assertRedirect();
    $this->assertDatabaseHas('posts', [
        'title' => 'پست جدید',
    ]);
}

اینجا:

  • actingAs($user) یه کاربر رو به‌عنوان کاربر لاگین‌شده شبیه‌سازی می‌کنه.

  • assertRedirect(‘/login’) چک می‌کنه که کاربرهای لاگین‌نشده به صفحه لاگین هدایت بشن.

  • assertDatabaseHas چک می‌کنه که پست جدید تو دیتابیس ذخیره شده.

یه تجربه از خودم: یه بار تست احراز هویت نوشتم ولی فراموش کردم middleware رو تو مسیرها بذارم و تست‌ها پاس شدن در حالی که نباید! همیشه مطمئن شید middlewareها درست تنظیم شدن.

تست API

بیاید یه تست برای API که تو قسمت هشتم ساختیم بنویسیم. تو PostTest.php این تست رو اضافه کنید:

public function test_api_returns_posts_list()
{
    Post::factory()->count(3)->create();

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

    $response->assertStatus(200)
             ->assertJsonCount(3)
             ->assertJsonStructure([
                 '*' => ['id', 'title', 'content', 'created_at'],
             ]);
}

این تست چک می‌کنه که مسیر /api/posts یه لیست JSON با 3 پست و ساختار درست برگردونه. من تو یه پروژه داشتم API برای یه اپ موبایل تست می‌کردم و این روش برای اطمینان از صحت پاسخ‌ها خیلی به کارم اومد.

نکات تکمیلی

  • تست‌های واحد: اگه یه تابع خاص دارید (مثل یه helper برای فرمت تاریخ)، تست واحد بنویسید. مثلاً:

public function test_format_date_helper()
{
    $date = '2023-10-18';
    $formatted = formatDate($date);
    $this->assertEquals('18 مهر 1402', $formatted);
}
  • اشکال‌زدایی: اگه تست شکست خورد، از dd($response->content()) استفاده کنید تا خروجی رو ببینید. من خودم بارها با این روش مشکلات تست‌هام رو پیدا کردم.

  • لاراول ۱۲ و بهبودها: تو نسخه ۱۲، تست‌نویسی با ابزارهای جدید مثل پشتیبانی از تست‌های موازی سریع‌تر شده. می‌تونید با php artisan test –parallel تست‌ها رو سریع‌تر اجرا کنید.

قسمت بعدی قراره درباره بهینه‌سازی و استقرار (Deployment) صحبت کنیم که چطور می‌تونید اپلیکیشن‌تون رو بهینه کنید و روی سرور بذارید. اگه سوالی دارید یا جایی گیر کردید، تو کامنت‌ها بپرسید. من همیشه از بازخوردهای شما کلی چیز جدید یاد می‌گیرم!

تا قسمت بعدی، موفق باشید!