labZero:~$ cd ./notes / note-concurrent-test-hid...
اختبار اجتاز دائماً محلياً — لماذا فشل في الإنتاج تحت ضغط طفيف؟
اختبار يتحقق من رصيد المحفظة اجتاز 500 مرة متتالية، ثم في الإنتاج خصم نفس الرصيد مرتين في دقيقتين.
بنيت نظام محافظ رقمية بسيطاً لتطبيق خدمات. منطق الخصم: تحقق من الرصيد → إذا كافٍ → اخصم → احفظ. كتبت اختباراً كامل التغطية، اجتاز 500 مرة في CI بلا خطأ. بعد أسبوع من الإنتاج، عميل يرسل لقطة شاشة: نفس الخصم ظهر مرتين.
الجوهر: الاختبار كان يشغّل الحالات بشكل تسلسلي — طلب واحد ينتهي قبل أن يبدأ الآخر. في الإنتاج، طلبان وصلا في نفس الوقت (مستخدم نقر مرتين)، كلاهما قرأ الرصيد قبل أن يكتب الآخر تحديثه. قراءتان متزامنتان للرصيد القديم = خصمان ناجحان. الاختبار لم يكذب — شغّل سيناريو حقيقياً وصح فيه. لكنه لم يُشغّل سيناريو التزامن أبداً.
الحل: lockForUpdate() على صف المحفظة داخل transaction يجبر الطلب الثاني على الانتظار حتى يكمل الأول.
DB::transaction(function () use ($userId, $amount) {
$wallet = Wallet::where('user_id', $userId)->lockForUpdate()->first();
throw_if($wallet->balance < $amount, InsufficientFundsException::class);
$wallet->decrement('balance', $amount);
});
الآن أكتب اختباراً موازياً لأي عملية تقرأ ثم تكتب: أُشغّل نفس الدالة مرتين متزامنتين بـ Laravel Concurrency وأتحقق من النتيجة النهائية. السيناريو الذي لا يحدث في التطوير المنفرد هو الأكثر احتمالاً أن يحدث حين يستخدم التطبيق فعلاً.