تست خوب قرار است اجازه تغییر بدهد. اگر با هر refactor ده‌ها تست بی‌دلیل بشکنند یا suite آن‌قدر کند باشد که کسی اجرا نکند، تعداد تست بالا فایده کمی دارد. در تست‌نویسی Laravel انتخاب مرز تست از انتخاب assertion مهم‌تر است.

از ریسک شروع کنید، نه از پوشش فایل‌ها

جریان پرداخت، سطح دسترسی، قیمت‌گذاری و مهاجرت داده ریسک بیشتری از getter ساده دارند. ابتدا سناریوهایی را بنویسید که شکست‌شان به پول، داده یا اعتماد کاربر آسیب می‌زند. coverage می‌تواند نقطه‌های فراموش‌شده را نشان دهد، اما هدف تجاری نیست.

Unit Test برای منطق مستقل

کلاس محاسبه تخفیف، state machine سفارش یا value object بدون boot کردن Laravel سریع تست می‌شوند. ورودی و خروجی روشن دارند و mock کمی می‌خواهند. اگر برای ساخت subject به ده mock نیاز دارید، شاید طراحی بیش از حد وابسته است.

public function test_vip_discount_never_exceeds_order_total(): void
{
    $discount = (new DiscountPolicy())->forVip(total: 120_000, requested: 150_000);

    $this->assertSame(120_000, $discount);
}

Feature Test انتخاب پیش‌فرض بسیاری از رفتارهای Laravel

route، middleware، validation، Policy، database و Resource در feature test کنار هم سنجیده می‌شوند. برای endpoint مهم، مسیر موفق، ورودی نامعتبر، کاربر بدون مجوز و یک حالت مرزی را پوشش دهید. این تست‌ها از mock کردن خود framework معتبرترند.

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

SQLite با MySQL در constraint، collation، JSON و SQL تفاوت دارد. اگر production روی MySQL است، suite integration باید همان engine را ببیند. factory داده خوانا می‌سازد، اما stateهای خاص را با state method نام‌دار نگه دارید.

تست migration سنگین یا index نیز می‌تواند در pipeline جدا با snapshot شبیه production اجرا شود.

سرویس بیرونی را در دو سطح تست کنید

در تست روزانه، HTTP fake پاسخ موفق، timeout و خطا را شبیه‌سازی می‌کند. این تست سریع و deterministic است. در کنار آن یک contract test دوره‌ای در sandbox بررسی می‌کند فرض ما درباره API واقعی هنوز درست است.

SDK را پشت adapter قرار دهید تا business code به shape vendor وابسته نشود. تست adapter هم mapping و error handling را پوشش می‌دهد.

Queue، Event و Mail را هوشمندانه fake کنید

در تست controller می‌توان بررسی کرد job درست dispatch شده است. سپس خود job را جدا با dependency واقعی لازم تست کنید. اگر همیشه همه‌چیز fake شود، احتمال دارد زنجیره‌ای داشته باشیم که dispatch می‌شود اما هنگام اجرا می‌شکند.

تست شکننده چه نشانه‌ای دارد؟

  • به ترتیب داخلی فراخوانی‌ها وابسته است، نه نتیجه.
  • زمان فعلی، randomness یا سرویس شبکه کنترل نشده دارد.
  • داده بسیار بزرگی می‌سازد که ربطی به سناریو ندارد.
  • چند رفتار نامرتبط را در یک تست پوشش می‌دهد.
  • نام تست نمی‌گوید چه قانون یا خطری محافظت می‌شود.

Suite را در CI سریع نگه دارید

تست‌های unit و feature را parallel اجرا کنید، cache مناسب dependency داشته باشید و تست کند را اندازه بگیرید. تست browser ارزشمند اما گران است؛ فقط جریان‌های اصلی را به آن بسپارید. اگر suite طولانی است، صرفاً تقسیم آن مشکل query و setup کند را پنهان نکند.

برای کد legacy از characterization test شروع کنید

پیش از refactor، رفتار فعلی را ثبت کنید. لازم نیست رفتار ایده‌آل باشد؛ ابتدا حفاظ می‌سازیم، سپس تغییر آگاهانه انجام می‌دهیم. این رویکرد در مهاجرت PHP قدیمی به Laravel حیاتی است.

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

پرسش‌های متداول

در Laravel بیشتر Unit Test بنویسیم یا Feature Test؟

برای رفتارهای HTTP و جریان‌های متصل به framework، Feature test ارزش بیشتری دارد؛ منطق دامنه مستقل جای Unit test است.

آیا درصد coverage معیار خوبی است؟

یک علامت کمکی است، نه هدف. coverage بالا بدون assertion معنادار می‌تواند حس امنیت اشتباه ایجاد کند.