فرض کنید یک تابع Register برای ثبت نام کاربران در UserController دارید. درون این تابع قطعه کدی هست که یک رشته ۵ کاراکتری تصادفی برای کاربری که ایمیل خود را وارد سامانه کرده است، ارسال میکند.
مساله اینجاست که چطور میخواید این قطعه کد که چند کاراکتر تصادفی تولید میکند را در فرآیند Test بررسی کنید.
راه حلهای اولیه خیلی ساده هستند. مثلا یک helper function بنویسیم و در اون مشخص کنیم که اگر در محیط Test بودیم، به جای یک رشته تصادفی، یک رشته از قبل مشخص شده، مثلا "ab123" رو به ما بده. اشکال این روش این هست که شما منطق برنامهتون رو در حین Test عوض کردید. برنامه شما قراره یک رشته تصادفی برای کاربر ایمیل کنه، نه یک رشته از قبل آماده شده.
روش دوم این هست که اگر در محیط Test بودیم، اون تابع خروجی تصادفی خودش رو هم به ادامه برنامه بده و هم در یک جایی مثلا یک فایل JSON ذخیره کنه. خب این روش از قبلی، البته فقط در سطح ایده، بهتره.
اشکال کلی که به دو روش بالا وارد هست اینه اگر عناصر پویای جدیدی مثل زمان هم به سیستم ما اضافه بشند، ثابت نگه داشتن زمان در حین تست کار بیهودهای هست.
بیاید تصور کنیم چی میشد اگر میتونستیم اون رشته رو جایی ذخیره کنیم بدون اینکه بخوایم به منطق برنامه دست ببریم. اگر با لاراول کار میکنید، میدونید که از IoC استفاده میکنه. اگر نمیدونید IoC چی هست، مخفف Inversion of Control هست (ویکیپدیا) و تصور کنید یک آرایه بسیار بزرگ از تمام کلاسهای موجود در فریمورک و اپلیکیشن شما هست که در هر جایی بهشون نیاز داشتید، میتونید ازشون استفاده کنید. توضیح فنیتر در عکس زیر هست.
حالا کاری که باید انجام بدیم اینه که ۳ کلاس تعریف کنیم. یک کلاس برای Facade، یکی برای RangomGen و یکی هم برای RandomGenFake. ما کدهایی که قراره کاراکترها تصادفی و عناصر پویا مثل زمان رو مدیریت کنه رو در RandomGen مینویسیم. RandomGenFake از RandomGen ارثبری میکنه و فقط یکی از توابع کلاس RandomGen که برای get کردن کاراکترهای تصادفی تولید شده هست رو نیازه تا override کنیم. این دفعه قبل از return کردن مقدار تولید شده، اون رو در یک متغیر static نگهداری میکنیم.
حالا برای استفاده در قدم اول نیاز هست تا RandomGen رو در IoC رجیستر کنیم، در کلاس Facade نامی که در IoC به RandomGen اختصاص دادیم رو در تابع getFacadeAccessor برگردونیم و در کلاس Facade از swap استفاده کنیم. یک تابع static به نام fake میسازیم و در اونجا کلاس RandomGen اصلی رو با RandomGenFake عوض میکنیم یا اصطلاحا swap میکنیم و RandomGenFake رو برمیگردونیم. مشابه عکس زیر
یکی از بزرگترین مزیتهای این روش برای من، دسترسی به متغیر app هنگام swap هست، که میتونیم به عنوان آرگومان ورودی به تابع سازنده RandomGenFake بدمش و به کل اپلیکیشن دسترسی داشته باشم.
من ایده این پیادهسازی رو از کلاس Notification لاراول که در Facadeهاش موجود هست گرفتم، بخاطر همین اینجا کدی برای نمایش نمیذارم و صرفا ارجاعتون میدم که اون کلاس رو که در حال حاضر در آدرس زیر هست، مطالعه کنید.
Illuminate\Support\Facades\Notification
بعد از همه اینکارها، اتفاقی که میفته اینه که در طی فرآیند Test فقط کافیه اول تابع تست Facade::fake رو صدا بزنید، حالا در IoC کلاس RandomGenFake نشسته، تست رو اجرا میکنید، و بعد از response گرفتن، هنوز به کاراکترهای تولید شده حین برنامه دسترسی دارید چون در یک متغیر static نگهداری میشوند.
شاید سوالی براتون پیش اومده باشه که خب چرا نیاز هست دو کلاس داشته باشیم و میتونیم متغیر static رو در کلاس اول هم داشته باشیم. جواب من اینه که در برنامههایی که تعداد Connectionها زیاد هست و به خصوص بعد از معرفی Laravel octane و رفتن لاراول به سمت Concurrency، استفاده از متغیرهای static اگر درست مدیریت نشن منجر به Memory Leak میشه.