الاختبار في Swift: Unit، Integration، E2E — من النظرية إلى التطبيق
الاختبار عالي الجودة ليس مجرد "أسلوب حسن" في التطوير لنظامي iOS وmacOS، بل هو مرحلة حاسمة تضمن استقرار التطبيق وأمانه وقابليته للتنبؤ. في هذه المقالة، سنشرح ثلاثة مستويات من الاختبار في Swift: اختبارات الوحدة (Unit)، اختبارات التكامل (Integration)، واختبارات E2E (end-to-end). ستتعرف على الاختلافات بينها، ومتى تستخدمها، وكيفية كتابة اختبارات فعالة باستخدام أطر العمل XCTest وXCUITest.
1. اختبار الوحدة (Unit Testing): التحقق من "لبنات" التطبيق
اختبارات الوحدة هي النوع الأكثر انخفاضاً في المستوى والأسرع من بين أنواع الاختبارات. تتحقق من عمل الدوال الفردية، أو الطرق، أو الفئات (الوحدات) بمعزل عن التبعيات الخارجية (الشبكة، قاعدة البيانات، واجهة المستخدم).
المبادئ الأساسية لاختبارات الوحدة في Swift:
- العزل (Isolation): يجب ألا يعتمد الكود المختبر على حالة الوحدات الأخرى.
- قابلية التكرار (Repeatability): يجب أن يعطي الاختبار نفس النتيجة في كل تشغيل.
- السرعة (Speed): يجب أن تُنفذ آلاف اختبارات الوحدة في ثوانٍ.
مثال على اختبار وحدة باستخدام XCTest:
import XCTest
class Calculator { func add(_ a: Int, _ b: Int) -> Int { return a + b } func divide(_ a: Int, _ b: Int) throws -> Int { guard b != 0 else { throw CalculationError.divisionByZero } return a / b }}
enum CalculationError: Error { case divisionByZero}
class CalculatorTests: XCTestCase { var calculator: Calculator! override func setUp() { super.setUp() calculator = Calculator() } override func tearDown() { calculator = nil super.tearDown() } func testAddition() { // Given let a = 5 let b = 3 // When let result = calculator.add(a, b) // Then XCTAssertEqual(result, 8, "5 + 3 يجب أن يساوي 8") } func testDivisionByZeroThrowsError() { // نستخدم XCTAssertThrowsError للتحقق من الاستثناءات XCTAssertThrowsError(try calculator.divide(10, 0)) { error in XCTAssertEqual(error as? CalculationError, .divisionByZero) } }}أفضل الممارسات لاختبارات الوحدة:
- استخدم نمط AAA (Arrange-Act-Assert) — الإعداد، التنفيذ، التحقق.
- سمِّ الاختبارات بشكل ذي معنى:
testMethodName_condition_expectedResult. - تجنب اختبار الطرق الخاصة (private) — اختبر الواجهة العامة (public interface).
- للاستبدالات الوهمية (mocks) والنماذج (stubs)، استخدم البروتوكولات وحقن التبعيات (dependency injection).
2. اختبار التكامل (Integration Testing): التحقق من تفاعل المكونات
تتحقق اختبارات التكامل من كيفية عمل الوحدات المختلفة للتطبيق معاً. في سياق iOS، قد يكون هذا تفاعل ViewModel مع الشبكة، أو Core Data، أو UserDefaults.
مثال على اختبار تكامل مع استبدال وهمي (mock) لطبقة الشبكة:
import XCTest
// بروتوكول لخدمة الشبكة (يسمح بالاستبدال بسهولة)protocol NetworkServiceProtocol { func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void)}
// الخدمة الحقيقية (لا نختبرها في اختبار التكامل)class RealNetworkService: NetworkServiceProtocol { func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) { // طلب HTTP حقيقي }}
// خدمة وهمية (Mock) للاختبارclass MockNetworkService: NetworkServiceProtocol { var shouldReturnError = false var mockUser = User(id: 1, name: "Test User") func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) { if shouldReturnError { completion(.failure(NSError(domain: "test", code: 500)))