«الاختبار أولاً — ثم الكود»: لماذا يعمل هذا في Swift؟
\n\nهل سبق لك أن أعدت كتابة نفس الوحدة للمرة الثالثة لأنك «كنت بحاجة إلى إطلاقها بشكل عاجل»؟ أو هل واجهت خطأ في الإنتاج كان من الأسهل منعه في مرحلة الكتابة؟ إذا كان الأمر كذلك — مرحباً بك في عالم TDD (التطوير الموجه بالاختبارات).
\n\nالتطوير عبر الاختبارات ليس مجرد منهجية عصرية. إنها طريقة تفكير تجبرك على صياغة كيف يجب أن يتصرف الكود قبل أن تكتبه. في Swift، مع كتابتها الصارمة للأنواع وإطار العمل القوي XCTest، يتحول TDD إلى درع موثوق ضد الانحدارات والانهيارات غير المتوقعة.
في هذه المقالة، سنشرح TDD بأمثلة واقعية، ونكتب اختبارات للطبقة الشبكية ونموذج البيانات، والأهم من ذلك — سنفهم كيفية تطبيق هذه الممارسة دون ألم. هيا بنا!
\n\nأحمر-أخضر-إعادة هيكلة: سحر الدورة
\n\nأساس TDD هو دورة من ثلاث خطوات:
\n\n- \n
- أحمر (Red) — نكتب اختباراً لا ينجح (لأن الكود غير موجود بعد). \n
- أخضر (Green) — نكتب الحد الأدنى من الكود ليجتاز الاختبار. \n
- إعادة هيكلة (Refactor) — نحسّن الكود دون تغيير سلوكه (الاختبارات تحمي). \n
يبدو بسيطاً؟ لنجرب على Swift.
\n\nالاختبار الأول: التحقق من نموذج User
\n\nلنفترض أن لدينا نموذج مستخدم بسيط. نريده أن يقوم بتنسيق الاسم الكامل بشكل صحيح. أولاً — الاختبار:
\n\nimport XCTest\n@testable import MyApp\n\nfinal class UserTests: XCTestCase {\n \n func testFullName_GivenFirstNameAndLastName_ReturnsFormattedString() {\n // ترتيب\n let user = User(firstName: "آنا", lastName: "كارنينا")\n \n // تنفيذ\n let fullName = user.fullName()\n \n // تأكيد\n XCTAssertEqual(fullName, "آنا كارنينا", "يجب أن يتكون الاسم الكامل من الاسم الأول واسم العائلة مفصولين بمسافة")\n }\n}\n\n\nنشغّل — يفشل الاختبار (أحمر). المترجم يشتكي من أن User غير موجود. رائع، هذا هو المقصود.
الآن نكتب الحد الأدنى من الكود لاجتياز الاختبار:
\n\nstruct User {\n let firstName: String\n let lastName: String\n \n func fullName() -> String {\n return "\\(firstName) \\(lastName)"\n }\n}\n\n\nنشغّل — أخضر! يمكننا الانتقال إلى إعادة الهيكلة. على سبيل المثال، إضافة معالجة السلاسل الفارغة أو الاسم الأوسط. لكن لا تتعجل — الاختبارات ستخبرك عندما يكون ذلك ضرورياً.
\n\nاختبار الطبقة الشبكية غير المتزامنة
\n\nواحدة من أكثر المشاكل شيوعاً في تطوير iOS هي العمل مع الشبكة. TDD مفيد هنا بشكل خاص، لأنه يفصل بوضوح المنطق عن استدعاءات الشبكة.
\n\nكتابة بروتوكول وكائن mock
\n\nأولاً، نحدد كيف يجب أن تبدو خدمتنا:
\n\nprotocol WeatherServiceProtocol {\n func fetchWeather(for city: String, completion: @escaping (Result<Weather, Error>) -> Void)\n}\n\n\nالآن — الاختبار. لا نريد الاعتماد على API حقيقي، لذلك ننشئ mock:
\n\nclass MockWeatherService: WeatherServiceProtocol {\n var shouldReturnError = false\n var mockWeather: Weather?\n \n func fetchWeather(for city: String, completion: @escaping (Result<Weather, Error>) -> Void) {\n if shouldReturnError {\n completion(.failure(NetworkError.serverError))\n } else if let weather = mockWeather {\n completion(.success(weather))\n }\n }\n}\n\nfinal class WeatherViewModelTests: XCTestCase {\n \n func testFetchWeather_Success_UpdatesModel() {\n // ترتيب\n let mockService = MockWeatherService()\n mockService.mockWeather = Weather(temperature: 25.0, condition: "مشمس")\n let viewModel = WeatherViewModel(service: mockService)\n let expectation = XCTestExpectation(description: "انتظار تحميل الطقس")\n \n