الاختبار في Swift: من النظرية إلى التطبيق مع الموك والـ Fixtures
\n\nتخيل أنك تخبز كعكة ونفد الدقيق لديك. لا يمكنك التحقق مما إذا كانت الكريمة ستنجح حتى تذهب إلى المتجر. يشعر المطور بنفس الشعور تقريبًا عندما يكتب اختبارات وحدة لكود يتفاعل مع الشبكة أو قاعدة البيانات أو التبعيات المعقدة. بدلاً من انتظار خادم حقيقي أو قاعدة بيانات حقيقية، نستخدم mock-الكائنات و fixtures في اختبارات Swift — وهي "بدائل الدقيق" لدينا، والتي تسمح لنا باختبار أجزاء معزولة من الكود هنا والآن.
\n\nفي هذه المقالة، لن نقتصر على سرد التعريفات. سنقوم بتحليل كيفية تطبيق اختبار Swift بشكل صحيح عمليًا، ومتى نستخدم الموك، ومتى نستخدم الـ stubs، وكيف تساعد الـ fixtures في عدم فقدان العقل عند إعداد البيانات. هيا بنا!
\n\nما هو mock و stub و fixture؟ فهم المصطلحات
\n\nقبل كتابة الكود، دعونا نتفق على المفاهيم. في عالم اختبارات الوحدة لنظام iOS، غالبًا ما يتم الخلط بين الموك والـ stubs. تذكر قاعدة بسيطة:
\n\n- \n
- Stub (وصلة) — كائن يعيد بيانات محددة مسبقًا. لا يتحقق من كيفية استدعائه. فقط يعيد الرد. \n
- Mock (محاكاة) — كائن لا يعيد البيانات فحسب، بل يتحقق أيضًا مما إذا تم استدعاء طرق معينة وبأي معاملات. إنه "جاسوس". \n
- Fixture (بيانات ثابتة) — مجموعة بيانات مُعدة مسبقًا (عادةً JSON أو هيكل) تُستخدم في الاختبارات لإنشاء بيئة يمكن التنبؤ بها. \n
في اختبار Swift، غالبًا ما نجمع بين هذه الأساليب. على سبيل المثال، نقوم بتحميل fixture بصيغة JSON، ونقوم بتحليلها، ثم نستبدل طبقة الشبكة بـ mock يتحقق من إرسال الطلب إلى عنوان URL الصحيح.
\n\nكتابة أول mock في Swift باستخدام البروتوكولات
\n\nالطريقة الأكثر شيوعًا لإنشاء mock-كائنات في Swift هي استخدام البروتوكولات. في مشروع حقيقي، لديك خدمة تتصل بالشبكة. في الاختبارات، تستبدلها بـ mock.
\n\nإليك مثال على التجريد:
\n\n// بروتوكول لطبقة الشبكة\nprotocol NetworkServiceProtocol {\n func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void)\n}\n\n// التنفيذ الحقيقي (يتصل بالشبكة)\nclass RealNetworkService: NetworkServiceProtocol {\n func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) {\n // URLSession, Alamofire إلخ.\n }\n}\n\n// Mock للاختبارات\nclass MockNetworkService: NetworkServiceProtocol {\n var fetchUsersCalled = false\n var resultToReturn: Result<[User], Error> = .success([])\n\n func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) {\n fetchUsersCalled = true\n completion(resultToReturn)\n }\n}\n\nالآن في الاختبار يمكننا التحقق من استدعاء الطريقة:
\n\nfunc testViewModelFetchesUsers() {\n let mockService = MockNetworkService()\n mockService.resultToReturn = .success([User(id: 1, name: "Test")])\n\n let viewModel = UserViewModel(networkService: mockService)\n viewModel.loadUsers()\n\n XCTAssertTrue(mockService.fetchUsersCalled, "يجب استدعاء طريقة fetchUsers")\n}\n\nلاحظ: نحن نستخدم حقن التبعية في Swift — نمرر التبعية عبر المُهيئ. هذا هو النمط الأساسي للاختبار.
\n\nالـ Fixtures: كيف لا تكتب 100500 سطر في الاختبارات
\n\nعند اختبار تحليل JSON أو العمل مع CoreData، تحتاج إلى بيانات حقيقية. لكن توليدها في كل مرة في الكود هو جحيم. هنا تأتي fixtures في اختبارات Swift للإنقاذ.
\n\nأنشئ في مجلد الاختبار ملفًا باسم users_fixture.json:
[\n {"id": 1, "name": "Alice", "email": "alice@example.com"},\n {"id": 2, "name": "Bob", "email": "bob@example.com"}\n]\n\nالآن