Stub: ما هو، الأنواع والاستخدام في الاختبار

المؤلف: IT Sectr نُشر: 2026-04-10 وقت القراءة: 9 دق

Stub (الاختبار الوهمي، الجسر) — كائن اختباري يعيد استجابات محددة مسبقًا لاستدعاءات الوسائل بدلاً من التنفيذ الحقيقي. في تطوير التطبيقات المحمولة، تعزل الاختبارات الوهمية الوحدة المختبرة عن طلبات الشبكة وقواعد البيانات وأنظمة الملفات، مما يسمح بالتحقق من المنطق دون إعداد البيئة. بخلاف mock، لا يتحقق stub من السلوك — هو يقدّم البيانات فقط. لمزيد من التفاصيل، طالع مقال مارتن فاولر حول test doubles.

النقاط الرئيسية

  • Stub — اختبار وهمي يعيد قيماً محددة عند استدعاء الوسائل دون منطق
  • العزل — تعطّل الاختبارات الوهمية التبعيات الحقيقية: API، قاعدة بيانات، ملفات، حساسات
  • الفرق عن Mock — لا يتحقق stub من الاستدعاءات، هو فقط يستبدل الاستجابة
  • Android — MockWebServer (OkHttp) ك stub للحصول على HTTP، MockK.constantAnswer لـ Kotlin
  • iOS — OCMock وبروتوكولات Swift مع تطبيقات اختبارية كاختبارات وهمية

ما هو Stub وما فرقه عن test doubles الأخرى؟

Stub هو كائن اختباري يحل محل التبعية الحقيقية في الاختبار ويعيد قيماً محددة مسبقًا لاستدعاءات محددة. تم إدراج المصطلح في تصنيف Gerard Meszaros (2007) في كتاب «xUnit Test Patterns». ينتمي Stub إلى فئة test doubles — كائنات تستبدل المكوّنات الحقيقية أثناء الاختبار. الهدف الرئيسي للاختبار الوهمي هو تزويد الوحدة المختبرة ببيانات قابلة للتوقع، بإزالة غير اليقين من الأنظمة الخارجية.

مبدأ العمل — يقوم الاختبار بتكوين الاختبار الوهمي قبل التنفيذ: «عندما يتم استدعاء طريقة getUsers()، أعد هذه القائمة من المستخدمين». لا يحتوي الاختبار الوهمي على منطق أعمال، ولا يتحقق من تسلسل الاستدعاءات ولا يسجِّل سجل الاستدعاءات. هو فقط يقف مكان المكوّن الحقيقي ويعيد ما قيل له. في سياق اختبار Android، هذا يعني: عميل OkHttp لا يقوم بطلب حقيقي إلى الخادم، ولكنه يتلقى ردًا من MockWebServer المهيأ كاختبار وهمي.

  • Stub — يعيد البيانات، لا يتحقق من الاستدعاءات
  • Mock — يعيد البيانات ويتحقق من السلوك (verify)
  • Fake — تنفيذ مبسط عامل بمنطق حقيقي
  • Spy — يغلف كائناً حقيقياً، مسجّلاً الاستدعاءات
  • Dummy — يتم تمريره ولكن لا يستخدم (null، كائن فارغ)

متى استخدامها — الاختبارات الوهمية مثالية لاختبار طبقة UI (ViewModel، Presenter) ومنطق الأعمال (UseCase، Interactor)، حيث تحتاج إلى التحقق من رد الفعل بيانات محددة: قائمة فارغة، الخادم أعاد خطأ 500، انتهت صلاحية الرمز. أي حالة يتطلب فيها الاختبار حالة إدخال محددة هي مهمة للاختبار الوهمي. يتم إنشاء تكوين خاص لكل سيناريو اختباري، مما يجعل الاختبارات قابلة للقراءة والتوقع.

تصنيف test doubles حسب Meszaros

Gerard Meszaros (2007) في كتاب «xUnit Test Patterns» حدد خمسة أنواع من test doubles: dummy، stub، spy، mock، fake. كل نوع يحل مشكلته. Dummy — يتم تمريره ولكن لا يستخدم. Stub — يعيد البيانات. Spy — يسجِّل الاستدعاءات. Mock — يتحقق من السلوك. Fake — يحتوي على منطق مبسط. يساعد فهم هذا التصنيف المطور على اختيار الأداة المناسبة لكل سيناريو اختباري.

أين تستخدم الاختبارات الوهمية في اختبار التطبيقات المحمولة؟

اختبارات وهمية لطلبات الشبكة

طلبات الشبكة — السيناريو الأكثر شيوعًا لاستخدام الاختبارات الوهمية. يقوم التطبيق بإجراء استدعاءات HTTP للواجهة البرمجية (API)، ويحتاج الاختبار إلى التحقق من رد الفعل لاستجابات مختلفة: JSON ناجح، خطأ 401 (غير مصرّح)، انقضاء المهلة، مصفوفة فارغة. يعمل MockWebServer (OkHttp) على Android و URLProtocol (على iOS) كاختبارات وهمية، معيدة استجابات HTTP محددة مسبقًا دون اتصال حقيقي بالخادم. يعمل ذلك على تسريع الاختبارات من ثوانٍ إلى ملي ثانية.

قاعدة البيانات — لدى Room (Android) و CoreData (iOS) نسخ في الذاكرة، ولكن إعدادها ما زال يستغرق وقتًا. يعيد الاختبار الوهمي بدلاً من المستودع قوائم Entity محضرة مسبقًا دون لمس قاعدة البيانات. هذا فعّال بشكل خاص لاختبار ViewModel، حيث تحتاج إلى التحقق من الترتيب أو التصفية أو تحويل البيانات. يتم تنفيذ الاختبار خلال ميلي ثانية بغض النظر عن حجم البيانات.

خدمات النظام — تتطلب LocationManager، SensorManager، SharedPreferences جهازاً حقيقياً أو محاكياً. يعيد الاختبار الوهمي لـ LocationProvider إحداثيات محددة، ولـ SensorManager — قيماً ثابتة لمقياس التسارع. على iOS، النظير هو CLLocationManager مع تنفيذ مفوض اختباري. بدون الاختبارات الوهمية، تتطلب هذه الاختبارات جهازاً مادياً بظروف محددة.

نظام الملفات والذاكرة المؤقتة — تحميل الصور، تخزين الاستجابات مؤقتًا، العمل مع ملفات التهيئة — جميع هذه العمليات تعتمد على حالة القرص. يعيد الاختبار الوهمي لـ FileManager أو ImageCache النجاح/الخطأ دون قراءة ملفات حقيقية. يقضي ذلك على إخفاقات اختبار خاطئة نتيجة عن عدم تطابق المسارات أو حقوق الوصول على أجهزة المطورين المختلفة.

Stub vs Mock vs Fake: الاختلافات الرئيسية

تقسيم المسؤوليات — تحل ثلاثة أنواع من test doubles مهامًّ مختلفة. Stub: «أعطني البيانات». Mock: «تحقق من أنه تم استدعائي». Fake: «أعمل مثل الحقيقي، ولكن بشكل أبسط». الفرق حاسم لقراءة الاختبارات: إذا استخدم الاختبار mock حيث يحتاج stub، فهو مثقل بأستدعاءات verify غير مرتبطة بالسيناريو المختبر.

الخصيصةStubMockFake
الغرضتوفير البياناتالتحقق من التفاعلتنفيذ مبسط
المنطقلا يوجدلا يوجديوجد (ولكن مبسط)
التحققلا يوجدنعم (verify)غير مباشر (عبر الحالة)
المرونةمنخفضة — إجابات ثابتةمتوسطةعالية — المنطق يتكيّف
السرعةقصوىعاليةمتوسطة
مثالMockWebServer يعيد JSONMockito.verify(repository).save()InMemoryRepository مع HashMap

قاعدة عملية — إذا كان الاختبار يتحقق من البيانات التي تلقاها المكوّن المختبر — استخدم stub. إذا كان الاختبار يتحقق من هل استدعى المكوّن طريقة التبعية بالوسائط الصحيحة — استخدم mock. إذا كنت تريد ببساطة استبدال قاعدة بيانات بجدول تجزئة — هذا fake. يجعل خلط الأنواع في اختبار واحد الاختبار هشاً: عند تغيير التنفيذ، سيتعين عليك إعادة كتابة منطق stub و verify معًا.

النمط المضاد: Stub مع verify

Stub مع verify — خطأ شائع حيث يقوم المطور بإعداد stub ثم يضيف verify(stub).method(). لا يجب أن يتم التحقق من stub حسب التعريف — لذلك يوجد mock. إذا كنت تحتاج إلى التحقق من أن طريقة ما تم استدعاؤها بوسائط محددة، استخدم Mockito.mock() بدلاً من Mockito.stub(). يحافظ هذا الفصل على وضوح قصد الاختبار للمطورين الآخرين.

تنفيذ الاختبارات الوهمية على Android باستخدام MockWebServer و MockK

MockWebServer — مكتبة OkHttp لإنشاء اختبارات HTTP وهمية على Android و JVM. تقوم بتشغيل خادم HTTP محلي على منفذ محدد يعترض طلبات عميل OkHttp ويعيد استجابات محددة مسبقًا. يستغرق الإعداد ثلاث أسطر: إنشاء الخادم، إدراج الاستجابة في الطابور، تشغيله. يمكن للاختبار إدراج عدة استجابات بالتسلسل لسيناريوهات التصفح أو إعادة المحاولة.

kotlin
class UserRepositoryTest {

    private val server = MockWebServer()

    fun setup() {
        server.start(8080)
        val client = OkHttpClient.Builder()
            .readTimeout(1, TimeUnit.SECONDS)
            .build()
    }

    fun test_user_list_success() {
        val json = "[{\"id\":1,\"name\":\"Alice\"}]"
        server.enqueue(MockResponse()
            .setBody(json)
            .setResponseCode(200)
        )
        val result = repository.getUsers()
        assertEquals(1, result.size)
    }

    fun teardown() {
        server.shutdown()
    }
}

MockK — بديل لـ Mockito لـ Kotlin مع دعم من الدرجة الأولى لـ كوروتينز ودوال الامتداد والفئات المنتومة. تنشأ الاختبارات الوهمية في MockK عبر coEvery (للدوال suspend) و every (للدوال العادية). بخلاف MockWebServer، يقوم MockK باختبار طرق التبعية الفردية بدلاً من طبقة HTTP بأكملها. هذا مفيد لاختبارات الوحدة لـ UseCase أو Interactor، حيث التبعيات هي تجريدات للمستودعات.

kotlin
interface UserRepository {
    suspend fun getUsers(): List<User>
}

class GetUsersUseCaseTest {

    private val repo = mockk<UserRepository>()

    private val useCase = GetUsersUseCase(repo)

    fun test_empty_list() = runTest {
        coEvery { repo.getUsers() } returns emptyList()

        val result = useCase.invoke()

        assertTrue(result.isEmpty())
        coVerify(exactly = 1) { repo.getUsers() }
    }
}

أفضل الممارسات — لاختبارات التكامل استخدم MockWebServer (يعترض HTTP الحقيقي)، ولاختبارات الوحدة استخدم MockK (يختبر الواجهات). لا تختبر ما لست تختبره: إذا كان الاختبار يتحقق من Repository، لا تختبر عميل OkHttp داخله — استخدم MockWebServer حقيقياً على مستوى HTTP. تحافظ هذه القاعدة على صلاحية الاختبارات وتقلل هشاشتها أثناء إعادة الهيكلة.

تنفيذ الاختبارات الوهمية على iOS باستخدام OCMock والبروتوكولات

بروتوكولات Swift كاختبارات وهمية — في النهج الأصلي لـ iOS، يتم تنفيذ الاختبار الوهمي عن طريق استبدال هيكل اختباري يتوافق مع بروتوكول التبعية. بدلاً من خدمة NetworkService الحقيقية، يتلقى الاختبار StubNetworkService الذي يعيد بيانات ثابتة. Swift لغة ذات تيميض ثابت، لذا يجب أن يتوافق الاختبار الوهمي مع نفس البروتوكول الذي يتبعه الخدمة الحقيقية. يضمن المجمع أن الاختبار الوهمي ينفِّذ جميع الطرق المطلوبة.

swift
protocol NetworkServiceProtocol {
    func fetchUsers() async throws -> [User]
}

struct StubNetworkService: NetworkServiceProtocol {
    let result: Result<[User], Error>

    func fetchUsers() async throws -> [User] {
        try result.get()
    }
}

final class UsersViewModelTests: XCTestCase {
    func test_success_state() async {
        let stub = StubNetworkService(
            result: .success([User(name: "Alice")])
        )
        let vm = UsersViewModel(service: stub)
        await vm.load()
        XCTAssertEqual(vm.users.count, 1)
    }
}

OCMock لـ Objective-C — مكتبة لإنشاء الاختبارات الوهمية والموكات في مشاريع iOS القديمة. يدعم OCMock طرق الاختبار الوهمي بالوسائط والقيم المعادة. تفضّل المشاريع الحديثة بـ Swift نهجًا قائماً على البروتوكولات مع اختبارات وهمية يدوية — يمنح ذلك التحكّم في كل طريقة ولا يتطلب تبعيات خارجية. يبقى OCMock خيارًا للمشاريع حيث لا يكون تحويل جميع التبعيات إلى بروتوكولات مجدياً اقتصادياً.

URLProtocol لاختبارات HTTP الوهمية — آلية نظامية في iOS لاعتراض طلبات الشبكة من خلال فئة فرعية من URLProtocol. يسجِّل الاختبار URLProtocol مخصصًا يعترض URLSession ويعيد استجابات وهمية. الميزة عن الاختبارات اليدوية: لا حاجة لتغيير هيكلة التطبيق — تبقى URLSession حقيقية، ولكن يتم استبدال البيانات على مستوى البروتوكول. العيب: أكثر صعوبة في التصحيح مقارنة بخدمة اختبار وهمي واضح.

الأسئلة الشائعة

كيف يختلف Stub عن Mock؟

Stub يعيد بيانات محددة مسبقًا ولا يتحقق من وقوع الاستدعاء. Mock يتحقق إضافياً من أن الطريقة تم استدعاؤها بالوسائط الصحيحة (verify). Stub يجيب على سؤال «ماذا نعيد؟»، Mock يجيب على «هل تم الاستدعاء؟». استخدم stub للتحقق من الحالة، و mock للتحقق من التفاعل.

متى استخدام Fake بدلاً من Stub؟

Fake مطلوب عندما يحتاج الاختبار إلى تنفيذ عامل (ولو مبسط) — على سبيل المثال، قاعدة بيانات في الذاكرة بدلاً من Room. Stub مناسب للسيناريوهات الفردية ببيانات محددة مسبقًا. إذا كررت نفس stub في 10 اختبارات — على الأرجح أنك تحتاج Fake. يقلِّل Fake التكرار لأن المنطق يعيش في فئة واحدة.

هل يمكن اختبار الطرق الثابتة؟

على Android — MockK لكائنات Kotlin (object) يدعم mockkObject()، بما في ذلك الطرق الثابتة لفئات Java عبر mockkStatic(). على iOS — لا يمكن اختبار الطرق الثابتة في Swift مباشرة؛ استخدم البروتوكولات وحقن التبعيات لاستبدال استدعاء static بطريقة مثال لبروتوكول. الاختبارات الوهمية الثابتة هي دين فني ويجب تجنبها في الكود الجديد.

كيف اختبار طلبات الشبكة على Android؟

استخدم MockWebServer (OkHttp) — يعمل كخادم HTTP محلي يقوم بإدراج الاستجابات في الطابور. لـ Retrofit، يكفي استبدال عنوان URL الأساسي بـ localhost:8080. لـ Ktor، استخدم MockEngine — آلية مضمنة لاستبدال HttpStatement. كلا النهجين يعملان دون اتصال حقيقي بالإنترنت ويعطيان تحكّمًا كاملاً في رمز الحالة، النص ورئوس الاستجابة.

Stub vs Spy — ما الفرق؟

Spy يغلف كائناً حقيقياً ويسجِّل الاستدعاءات، بينما Stub يستبدل الكائن تماماً بإجابات ثابتة. يسمح Spy باستخدام جزئي للتنفيذ الحقيقي (تعمل الطرق الأخرى كما هي)، بينما لا يفعل stub. إذا كنت تحتاج إلى التحقق من أن طريقة ما تم استدعاؤها ولكن يجب أن يتم تنفيذ جزء من المنطق — استخدم spy، لا stub.

الملخص

  • Stub — كائن اختباري يعيد استجابات محددة مسبقًا لاستدعاءات الوسائل أثناء الاختبار
  • عزل التبعيات — تستبدل الاختبارات الوهمية طلبات الشبكة وقواعد البيانات وخدمات النظام وأنظمة الملفات
  • الفرق عن Mock — لا يتحقق stub من الاستدعاءات، هو فقط يعيد البيانات دون تحقق من السلوك
  • أدوات Android — MockWebServer لـ HTTP، MockK لواجهات Kotlin مع دعم الكوروتينز
  • أدوات iOS — اختبارات وهمية قائمة على البروتوكولات في Swift، URLProtocol لـ HTTP، OCMock لـ Objective-C
  • لا تخلط الأدوار — لا تضف verify إلى stub، استخدم mock للتحقق من الاستدعاءات
  • Stub + MockWebServer — نهج قياسي لاختبارات التكامل دون خادم حقيقي

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا