الاختبارات المتقدمة في Swift: الوحدة والتكامل و E2E
الاختبارات ليست مجرد فحص، بل هي أساس الكود الموثوق. على المستوى المتقدم، يحتاج مطور Swift ليس فقط إلى كتابة الاختبارات، بل إلى بناء هرم الاختبارات بشكل صحيح: من اختبارات الوحدة السريعة إلى سيناريوهات end-to-end (e2e) المعقدة. في هذه المقالة، سنستعرض التقنيات المتقدمة لمشاريع iOS/macOS: العمل مع التبعيات، اختبارات تكامل CoreData، وأتمتة واجهة المستخدم.
1. اختبارات الوحدة: إدارة التبعيات والمحاكاة (Mocks)
على المستوى المتقدم، يجب أن تكون اختبارات الوحدة معزولة. التقنية الأساسية هي حقن التبعيات (Dependency Injection - DI). بدلاً من اختبار طبقة الشبكة الحقيقية أو قاعدة البيانات، نقوم باستبدالها بمحاكاة (Mocks).
مثال: البروتوكول و DI
protocol NetworkServiceProtocol { func fetchData(completion: @escaping (Result<String, Error>) -> Void)}
class ViewModel { private let networkService: NetworkServiceProtocol init(networkService: NetworkServiceProtocol) { self.networkService = networkService } func loadData() { networkService.fetchData { result in // معالجة النتيجة } }}
// محاكاة للاختباراتclass MockNetworkService: NetworkServiceProtocol { var shouldReturnError = false func fetchData(completion: @escaping (Result<String, Error>) -> Void) { if shouldReturnError { completion(.failure(NSError(domain: "test", code: -1))) } else { completion(.success("Mock data")) } }}في الاختبار، تقوم بإنشاء MockNetworkService وتمريره إلى ViewModel. هذا يسمح باختبار سيناريوهات الخطأ والنجاح دون الحاجة إلى API حقيقي.
XCTestExpectation للعمليات غير المتزامنة
func testViewModelLoadDataSuccess() { let mock = MockNetworkService() let viewModel = ViewModel(networkService: mock) let expectation = self.expectation(description: "تم تحميل البيانات") viewModel.loadData() // محاكاة استدعاء غير متزامن DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { expectation.fulfill() } waitForExpectations(timeout: 1.0) // التحقق من حالة ViewModel}2. اختبارات التكامل: CoreData والعمل مع البيانات الحقيقية
تتحقق اختبارات التكامل من تفاعل المكونات. بالنسبة لـ CoreData، نستخدم حاوية في الذاكرة (in-memory container) لتجنب تلويث قاعدة البيانات الرئيسية.
إعداد CoreData في الذاكرة
class CoreDataTestStack { lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "MyModel") let description = NSPersistentStoreDescription() description.type = NSInMemoryStoreType container.persistentStoreDescriptions = [description] container.loadPersistentStores { _, error in if let error = error { fatalError("فشل تحميل المخزن: \\(error)") } } return container }() var context: NSManagedObjectContext { return persistentContainer.viewContext }}مثال اختبار تكامل
func testSaveAndFetchUser() { let stack = CoreDataTestStack() let repository = UserRepository(context: stack.context) // إنشاء مستخدم let user = User(context: stack.context) user.name = "مستخدم اختبار" user.age = 30 try? stack.context.save() // التحقق من حفظ البيانات let fetchRequest: NSFetchRequest<User> = User.fetchRequest() let users = try? stack.context.fetch(fetchRequest) XCTAssertEqual(users?.count, 1) XCTAssertEqual(users?.first?.name, "مستخدم اختبار")}هام: بعد كل اختبار، قم بمسح السياق (context) لضمان عدم تأثير الاختبارات على بعضها البعض.