Unit Test vs Integration Test مقارنة

السرعة، العزل، وحلقة تغذية راجعة موثوقة

VS
Integration Test

تفاعل حقيقي بين المكونات، وتحقق شامل من طرف إلى طرف

8 دقائق للقراءةiOS

مقارنة الدرجات

جارٍ تحميل الرسم البياني...

التقييم التفصيلي

التقييم التفصيلي: Unit Test و Integration Test — درجات كل فئة من 10
الفئةUnit TestIntegration Test
الأداء
10/10
4/10
سهولة التعلّم
8/10
6/10
النظام البيئي
9/10
8/10
المجتمع
9/10
8/10
سوق العمل
9/10
8/10
الاستدامة المستقبلية
9/10
9/10

الإيجابيات والسلبيات

Unit Test

الإيجابيات

  • سريعة للغاية — تعمل خلال أجزاء من الثانية، وخلال ثوانٍ ضمن CI
  • معزولة — التبعيات الخارجية مضبوطة عبر المحاكاة (mock/stub)
  • موثوقة — أدنى خطر لاختبارات غير مستقرة (flaky) لعدم وجود شبكة أو قاعدة بيانات
  • كشف الانحدار (regression) — ترصد التغييرات الصغيرة في الكود فوراً
  • توثيق حي — الاختبارات الوحدوية المكتوبة جيداً توضح كيفية استخدام الكود
  • رفع جودة التصميم عبر منهجية TDD
  • قابلية التشغيل المتوازي — استغلال فعّال لأنوية المعالج المتعددة

السلبيات

  • قد لا تعكس المحاكاة الواقع تماماً — خطر نتائج إيجابية/سلبية خاطئة
  • لا ترصد مشاكل التكامل — سيناريو شائع من نوع 'نجحت الاختبارات الوحدوية لكن الإنتاج انهار'
  • خطر الإفراط في المحاكاة — قد ترتبط الاختبارات بشدة بتفاصيل التنفيذ
  • اختبار الدوال الخاصة (private) قد يتطلب تعديل التصميم
  • لا تُظهر مدى توافق الوحدات المستقلة عن بعضها في العالم الحقيقي

الأنسب لـ

التحقق من منطق العمل (business logic)اختبار الدوال الخالصة (pure functions) والخوارزمياتانتقالات الحالة (state) في ViewModel/Reducerاختبار المُحلِّلات (parsers) والمُنسِّقات (formatters) والمُتحقِّقات (validators)تطوير المكتبات وأطر العمل

Integration Test

الإيجابيات

  • يتحقق من التفاعل الحقيقي بين المكونات — بلا وهم المحاكاة
  • يرصد أخطاء التكامل — يختبر عمل الوحدات معاً
  • إمكانية اختبار طبقات قاعدة البيانات والشبكة بشكل حقيقي
  • التحقق من المسارات الحرجة من طرف إلى طرف
  • ضمان أثناء إعادة الهيكلة (refactoring) — يرصد تغييرات الواجهات
  • اختبار أقرب لسيناريوهات المستخدم الفعلية

السلبيات

  • بطيئة — الشبكة وقاعدة البيانات ونظام الملفات تعمل في الزمن الحقيقي
  • خطر عدم استقرار الاختبارات — الخدمات الخارجية قد تكون غير موثوقة
  • تعقيد في الإعداد (setup) والتفكيك (teardown)
  • صعوبة تحديد سبب فشل الاختبارات
  • التشغيل المتوازي قد يسبب تعارضات في الحالة (state)
  • يتطلب بنية تحتية إضافية للبيئة غير المحاكاة

الأنسب لـ

عمليات CRUD على قاعدة البيانات (Core Data، SwiftData، SQLite)التحقق من طبقة عميل الـ APIعمل عدة خدمات معاًالتحقق من المسارات الحرجة (الدفع، تدفق المصادقة)اختبارات ترحيل البيانات وتحويلها

مقارنة الكود

Unit Test
// Swift - أمثلة اختبار وحدوي (XCTest + Swift Testing)
import Testing
import Foundation
@testable import MyApp

// إطار Swift Testing (iOS 17+, WWDC 2024)
struct PriceFormatterTests {

    @Test("تنسيق الليرة التركية يجب أن يكون صحيحاً")
    func turkishLiraFormat() {
        let formatter = PriceFormatter(locale: Locale(identifier: "tr_TR"))
        #expect(formatter.format(1234.5) == "₺1.234,50")
        #expect(formatter.format(0) == "₺0,00")
        #expect(formatter.format(-50) == "-₺50,00")
    }

    @Test("السعر غير الصالح يجب ألا يكون سالباً",
          arguments: [-1.0, -100.0, -0.01])
    func negativePriceValidation(price: Double) {
        let validator = PriceValidator()
        #expect(!validator.isValid(price))
    }
}

struct CartViewModelTests {
    var sut: CartViewModel!
    var mockRepository: MockCartRepository!

    @Test("السعر الإجمالي يجب أن يتحدث عند إضافة منتج")
    mutating func addProductUpdatesTotalPrice() async throws {
        mockRepository = MockCartRepository()
        sut = CartViewModel(repository: mockRepository)

        let product = Product(id: "p1", name: "MacBook", price: 75000)
        await sut.addProduct(product)

        #expect(sut.totalPrice == 75000)
        #expect(sut.itemCount == 1)
    }

    @Test("الكمية يجب أن تزداد عند إضافة نفس المنتج مرتين")
    mutating func addSameProductIncreasesQuantity() async throws {
        mockRepository = MockCartRepository()
        sut = CartViewModel(repository: mockRepository)

        let product = Product(id: "p1", name: "MacBook", price: 75000)
        await sut.addProduct(product)
        await sut.addProduct(product)

        #expect(sut.items.count == 1)
        #expect(sut.items.first?.quantity == 2)
        #expect(sut.totalPrice == 150000)
    }
}

// تنفيذ وهمي (Mock)
class MockCartRepository: CartRepositoryProtocol {
    var savedItems: [CartItem] = []

    func save(_ item: CartItem) async throws {
        savedItems.append(item)
    }

    func fetchAll() async throws -> [CartItem] {
        return savedItems
    }
}
Integration Test
// Swift - أمثلة اختبار تكامل
import XCTest
@testable import MyApp

// اختبار تكامل باستخدام SQLite في الذاكرة
class UserRepositoryIntegrationTests: XCTestCase {
    var repository: UserRepository!
    var database: TestDatabase!

    override func setUp() async throws {
        // نستخدم قاعدة بيانات SQLite حقيقية في الذاكرة
        database = try await TestDatabase.inMemory()
        repository = UserRepository(database: database)
    }

    override func tearDown() async throws {
        try await database.cleanup()
        database = nil
        repository = nil
    }

    func testCreateAndFetchUser() async throws {
        // إنشاء
        let userId = try await repository.createUser(
            name: "Ahmet Yılmaz",
            email: "[email protected]"
        )

        // جلب
        let fetchedUser = try await repository.fetchUser(id: userId)

        XCTAssertNotNil(fetchedUser)
        XCTAssertEqual(fetchedUser?.name, "Ahmet Yılmaz")
        XCTAssertEqual(fetchedUser?.email, "[email protected]")
    }

    func testDeleteUserCascadesToPosts() async throws {
        let userId = try await repository.createUser(name: "Test", email: "[email protected]")
        let postRepo = PostRepository(database: database)
        _ = try await postRepo.createPost(title: "Post 1", userId: userId)
        _ = try await postRepo.createPost(title: "Post 2", userId: userId)

        // حذف المستخدم
        try await repository.deleteUser(id: userId)

        // يجب حذف منشورات المستخدم أيضاً (cascade)
        let posts = try await postRepo.fetchPosts(userId: userId)
        XCTAssertTrue(posts.isEmpty, "حذف المستخدم يجب أن يحذف منشوراته أيضاً")
    }
}

// اختبار تكامل شبكة باستخدام URLSession حقيقي
class APIClientIntegrationTests: XCTestCase {
    func testFetchPublicAPIData() async throws {
        // يستخدم هذا الاختبار اتصال شبكة حقيقي
        // يجب أن يعمل فقط في بيئات CI المتصلة بالشبكة
        try XCTSkipUnless(ProcessInfo.processInfo.environment["INTEGRATION_TESTS"] == "1")

        let client = APIClient(baseURL: URL(string: "https://jsonplaceholder.typicode.com")!)
        let posts: [Post] = try await client.request(path: "/posts")
        XCTAssertFalse(posts.isEmpty)
        XCTAssertEqual(posts.count, 100)
    }
}

الخلاصة

هرم الاختبار المثالي: عدد كبير من الاختبارات الوحدوية (70%)، وعدد أقل من اختبارات التكامل (20%)، والحد الأدنى من اختبارات الواجهة/الشاملة (10%). استخدم الاختبارات الوحدوية لتحصل على تغذية راجعة سريعة، واختبارات التكامل للتحقق من المسارات الحرجة معاً — لا تستبدل أحدهما بالآخر بل استخدمهما كعنصرين متكاملين، فتلك هي الاستراتيجية الصحيحة.

احصل على استشارة مجانية
الأسئلة الشائعة

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

لا يوجد رقم سحري. تغطية تتجاوز 80% لمنطق العمل (business logic) هدف جيد. لكن الأهم ليس نسبة التغطية بل اختبار الأشياء الصحيحة — والسعي الأعمى لتغطية 100% قد يكون ضاراً.

مقالات مدونة ذات صلة

عرض جميع المقالات

مشاريع ذات صلة

عرض جميع المشاريع
جميع المقارنات