المحاكاة والبدائل باستخدام Mockery
اعزل الوحدات باستخدام بدائل اختبار مرنة
المحاكاة والبدائل باستخدام Mockery درس مجاني في PHP Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في PHP Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة PHP Academy 4 دروس في المجموع.
لماذا تحتاجون إلى مكتبة مخصّصة للمحاكاة
يوفّر PHPUnit واجهة برمجة خاصة به للبدائل الاختبارية، لكن Mockery يقدّم صياغة أكثر سلاسة ووضوحًا، بالإضافة إلى إمكانات تفتقر إليها محاكيات PHPUnit — مثل المحاكيات الجزئية، والجواسيس، والمطابقة المرنة للوسائط، والتوقعات المرتبة. في الكود الذي يعتمد بكثافة على التعاون بين الكائنات، تكون قراءة Mockery غالبًا أفضل بكثير. يغطي هذا الدرس الطيف الكامل للبدائل ومتى تستخدمون كل نوع.
composer require --dev mockery/mockeryStub أم Mock أم Spy
تمنعكم المفردات الدقيقة من كتابة اختبارات مربكة:
- Stub — يعيد قيمًا محددة مسبقًا؛ وتتحققون من الحالة، أي النتيجة.
- Mock — يملك توقعات بشأن الاستدعاءات؛ وتتحققون من السلوك، أي أنه استُدعي بصورة صحيحة. يؤدي عدم تحقق التوقعات إلى فشل الاختبار.
- Spy — يسجّل الاستدعاءات ويتيح لكم التحقق بعد وقوعها.
قاعدة عامة: فضّلوا Stubs للاستعلامات، وMocks أو Spies للأوامر.
Stub باستخدام allows()
استخدموا Mockery::mock() وallows()، أو shouldReceive()->andReturn()، لتوفير قيم إرجاع محددة مسبقًا دون التحقق من حدوث الاستدعاء. يزوّد Stub الخاص بالبوابة هنا سعر صرف معروفًا، حتى نتمكن من اختبار الحساب بمعزل.
<?php
use Mockery;
use PHPUnit\Framework\TestCase;
final class ConverterTest extends TestCase
{
public function test_converts_using_rate(): void
{
$rates = Mockery::mock(RateGateway::class);
$rates->allows()->rateFor('USD', 'EUR')->andReturn(0.9);
$sut = new Converter($rates);
self::assertSame(90.0, $sut->convert(100.0, 'USD', 'EUR'));
Mockery::close();
}
}
Mock باستخدام expects()
عندما يكون التفاعل نفسه هو العقد — مثلًا، عندما يجب إرسال بريد إلكتروني مرة واحدة بالضبط — استخدموا expects() أو shouldReceive()->once(). يتحقق Mockery من التوقع أثناء Mockery::close()؛ ويؤدي عدم حدوث الاستدعاء المتوقع إلى فشل الاختبار.
<?php
use Mockery;
$mailer = Mockery::mock(Mailer::class);
$mailer->expects()
->send(Mockery::type(Message::class))
->once();
$service = new SignupService($mailer);
$service->register('ada@example.com');
Mockery::close(); // fails here if send() was never called
مطابقات الوسائط
تتيح مطابقات Mockery أن تكون التوقعات متساهلة أو صارمة حسب الحاجة:
Mockery::any()— أي قيمة.Mockery::type('string')/ اسم صنف — التحقق من النوع.Mockery::on(fn($a) => ...)— مسند مخصّص.Mockery::capture($var)— التقاط الوسيط للتحقق منه لاحقًا.
<?php
use Mockery;
$repo = Mockery::mock(UserRepo::class);
$repo->expects()
->save(Mockery::on(fn(User $u) => $u->isActive()))
->once()
->andReturnTrue();
تسلسلات الإرجاع والقيم الديناميكية
يمكنكم برمجة عدة قيم إرجاع عبر الاستدعاءات، أو حساب القيمة المرجعة من الوسائط باستخدام andReturnUsing(). وبهذا يمكنكم تمثيل منطق إعادة المحاولة، أو التقسيم إلى صفحات، أو المتعاونين ذوي الحالة.
<?php
use Mockery;
$api = Mockery::mock(HttpClient::class);
// First call throws, second succeeds (retry test):
$api->shouldReceive('get')
->twice()
->andThrow(new \RuntimeException('timeout'))
->andReturn('{"ok":true}');
// Or derive the return from the input:
$api->shouldReceive('echo')
->andReturnUsing(fn(string $in) => strtoupper($in));
Spies: التحقّق بعد وقوع الاستدعاء
يقلب Spy الترتيب: نفّذوا أولًا، ثم تحقّقوا. يسجّل Mockery::spy() الاستدعاءات؛ وبعد ذلك تستعلمون عنها باستخدام shouldHaveReceived(). تحافظ Spies على نظافة بنية الترتيب-التنفيذ-التأكيد عندما لا تريدون أن تزدحم مرحلة الإعداد بتوقعات محددة مسبقًا.
<?php
use Mockery;
$logger = Mockery::spy(Logger::class);
$service = new PaymentService($logger);
$service->charge(500);
// Assertions happen AFTER the action:
$logger->shouldHaveReceived('info')
->with(Mockery::pattern('/charged 500/'))
->once();
Mockery::close();
المحاكيات الجزئية
قد ترغبون أحيانًا في كائن حقيقي مع استبدال طريقة واحدة فيه — وهذا هو Mock الجزئي. تستدعي makePartial() في Mockery الطرق الحقيقية، باستثناء الطرق التي تحددون توقعات لها. استخدموا ذلك باعتدال: فالاعتماد الكبير على المحاكيات الجزئية يشير عادةً إلى أن الصنف ينفّذ مهامًا كثيرة جدًا.
<?php
use Mockery;
$report = Mockery::mock(Report::class)->makePartial();
// Only stub the slow/external method; the rest runs for real
$report->shouldReceive('fetchRawData')->andReturn(['a', 'b', 'c']);
// real summarize() runs, using the stubbed data
$summary = $report->summarize();
لا تحاكوا ما لا تملكونه
مبدأ أساسي في الاختبار: تجنّبوا محاكاة أصناف الجهات الخارجية مباشرةً. فقد تتغير واجهاتها البرمجية، وتبتعد محاكاتكم عنها بصمت. بدلًا من ذلك، غلّفوها خلف واجهة تملكونها أنتم وحاكوا تلك الواجهة. عندها يتحقق Mock من عقدكم أنتم، بينما يتحقق اختبار تكامل للمحوّل من الارتباط الحقيقي.
<?php
interface PaymentGateway { // you own this
public function charge(int $cents, string $token): string;
}
final class StripeGateway implements PaymentGateway {
public function __construct(private \Stripe\StripeClient $client) {}
public function charge(int $cents, string $token): string {
return $this->client->paymentIntents->create([/* ... */])->id;
}
}
// Tests mock PaymentGateway, never \Stripe\StripeClient directly.
أغلقوا دائمًا وتحققوا من الأعداد
هناك أمران تشغيليان لا بد منهما:
- استدعوا
Mockery::close()فيtearDown()، أو استخدموا السمةMockeryPHPUnitIntegration، حتى يجري التحقق من التوقعات فعلًا وتنظيف المتغيرات العامة. - استخدموا أعدادًا صريحة مثل
once()وtimes(n)وnever()— فالمحاكيات الغامضة تسمح بمرور الأخطاء.
<?php
use PHPUnit\Framework\TestCase;
use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration;
final class OrderServiceTest extends TestCase
{
use MockeryPHPUnitIntegration; // auto-calls Mockery::close()
public function test_does_not_refund_paid_orders(): void
{
$gw = \Mockery::mock(PaymentGateway::class);
$gw->shouldReceive('refund')->never();
// ... exercise SUT ...
}
}
التوقعات المرتبة
تكون أحيانًا ترتيبات الاستدعاءات جزءًا من العقد — إذ يجب استدعاء beginTransaction() قبل commit(). تفرض ordered() في Mockery التسلسل، وتفشل الاختبار إذا وصلت الاستدعاءات بترتيب خاطئ. استخدموها فقط عندما يكون الترتيب مهمًا فعلًا؛ فالإفراط في تحديد الترتيب يجعل الاختبارات هشة.
<?php
use Mockery;
$tx = Mockery::mock(Transaction::class);
$tx->shouldReceive('begin')->once()->ordered();
$tx->shouldReceive('commit')->once()->ordered();
$service = new TransferService($tx);
$service->run();
Mockery::close(); // fails if commit() happened before begin()
تحقّق سريع
متى نستخدم Stub ومتى نستخدم Mock؟
مراجعة
لقد أتقنتم بدائل Mockery:
- استخدموا Stubs، مثل
allows، للاستعلامات؛ وMocks، مثلexpects، للأوامر؛ وSpies للتحقق بعد وقوع الاستدعاء. - تضبط مطابقات الوسائط، مثل
typeوonوcapture، مستوى الصرامة. - تحاكي تسلسلات الإرجاع و
andReturnUsingالمتعاونين ذوي الحالة أو القيم الديناميكية. - تستبدل المحاكيات الجزئية طرقًا مفردة؛ فاستخدموها باعتدال.
- لا تحاكوا ما لا تملكونه — غلّفوا الجهات الخارجية خلف واجهتكم.
- استدعوا دائمًا
Mockery::close()وتحققوا من أعداد الاستدعاءات صراحةً.
التالي: اختبار التكامل والاختبار الوظيفي.
الأسئلة الشائعة
هل درس «المحاكاة والبدائل باستخدام Mockery» مجاني؟
نعم — نص درس «المحاكاة والبدائل باستخدام Mockery» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة PHP Academy، انتقل إلى CoddyKit PRO. تتضمن دورة PHP Academy 4 دروس في المجموع.
ماذا ستتعلم في «المحاكاة والبدائل باستخدام Mockery»؟
اعزل الوحدات باستخدام بدائل اختبار مرنة تتمرن على PHP Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ PHP Academy؟
لا تُشترط خبرة سابقة. PHP Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «المحاكاة والبدائل باستخدام Mockery»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس PHP Academy هذا؟
نعم. كل درس في PHP Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- سير عمل التطوير المدفوع بالاختبارات
- المحاكاة والبدائل باستخدام Mockery
- اختبار التكامل والاختبار الوظيفي
- اختبار الطفرات باستخدام Infection