Unit Test مقابل Integration Test
أساس استراتيجية الاختبار في iOS: هل الاختبارات الوحدوية (unit tests) التي تعزل كل جزء على حدة، أم اختبارات التكامل (integration tests) التي تتحقق من تفاعل المكونات؟ كيف تبني هرم الاختبار الصحيح؟
مكتبة اختبار Apple الجديدة مفتوحة المصدر — قائمة على الماكرو ومتكاملة بشكل طبيعي مع Swift Concurrency
إطار عمل اجتاز اختبار المعارك على مدى نحو 13 عامًا — الجهة الوحيدة لاختبارات الواجهة والأداء
هناك اتجاه واضح لكن 'الترحيل الجماعي' مخاطرة لا داعي لها. اكتب اختبارات الوحدة والتكامل الجديدة بـ Swift Testing — فهو يمنحك كودًا أقل، والتقاط تلقائي للقيم، واختبارات معلمنة جاهزة، وتوازيًا افتراضيًا. أبقِ اختبارات الواجهة والأداء في XCTest؛ فهذا ما تطلبه توثيقات Apple. بما أن الإطارين يمكن أن يتعايشا في الهدف نفسه، وأن Swift 6.4 جلب التقارير المتقاطعة بينهما، فإن الترحيل الجماعي غير ضروري — تقدّم ملفًا تلو الآخر.
| الفئة | Swift Testing | XCTest |
|---|---|---|
| الأداء | 8/10 | 8/10 |
| سهولة التعلّم | 7/10 | 6/10 |
| النظام البيئي | 6/10 | 8/10 |
| المجتمع | 6/10 | 8/10 |
| سوق العمل | 5/10 | 7/10 |
| الاستدامة المستقبلية | 9/10 | 6/10 |
// Swift Testing — اختبار وحدة async معلمن
import Testing
@testable import PaymentKit
@Suite("حساب السعر")
struct PriceCalculatorTests {
@Test("يُحسب الخصم بشكل صحيح مع رموز الكوبون الصالحة",
arguments: [
("SAVE10", 100.0, 90.0),
("SAVE20", 100.0, 80.0),
("NONE", 100.0, 100.0)
])
func discountIsApplied(code: String, base: Double, expected: Double) async throws {
let calculator = PriceCalculator()
let result = try await calculator.applyDiscount(code: code, to: base)
#expect(result == expected, "\(code) المتوقع \(expected)، الناتج \(result)")
}
@Test("كوبون غير صالح يطلق خطأ")
func invalidCouponThrows() async throws {
let calculator = PriceCalculator()
await #expect(throws: CouponError.invalid) {
try await calculator.applyDiscount(code: "XXX", to: 100.0)
}
}
@Test("إجمالي السلة مطلوب", .disabled("خدمة السلة لم تُنقل بعد"))
func cartTotalRequired() async throws {
let calculator = PriceCalculator()
try #require(calculator.cartTotal > 0)
}
}// XCTest — نفس السيناريو (حساب السعر) + اختبار واجهة
import XCTest
@testable import PaymentKit
final class PriceCalculatorTests: XCTestCase {
var calculator: PriceCalculator!
override func setUpWithError() throws {
calculator = PriceCalculator()
}
func testDiscountIsAppliedForKnownCoupons() async throws {
let cases: [(String, Double, Double)] = [
("SAVE10", 100.0, 90.0),
("SAVE20", 100.0, 80.0),
("NONE", 100.0, 100.0)
]
for (code, base, expected) in cases {
let result = try await calculator.applyDiscount(code: code, to: base)
XCTAssertEqual(result, expected, "كوبون: \(code)")
}
}
// XCTAssertThrowsError متزامنة؛ استخدم do/catch + XCTFail في الاستدعاء غير المتزامن
func testInvalidCouponThrows() async throws {
do {
_ = try await calculator.applyDiscount(code: "XXX", to: 100.0)
XCTFail("كان الخطأ متوقعًا")
} catch {
XCTAssertEqual(error as? CouponError, .invalid)
}
}
}
// اختبار واجهة — ممكن فقط عبر XCTest/XCUITest
final class CheckoutUITests: XCTestCase {
func testApplyCouponButtonUpdatesTotal() throws {
let app = XCUIApplication()
app.launch()
app.textFields["couponField"].tap()
app.textFields["couponField"].typeText("SAVE10")
app.buttons["applyCouponButton"].tap()
XCTAssertEqual(app.staticTexts["totalLabel"].label, "$90.00")
}
}هناك اتجاه واضح لكن 'الترحيل الجماعي' مخاطرة لا داعي لها. اكتب اختبارات الوحدة والتكامل الجديدة بـ Swift Testing — فهو يمنحك كودًا أقل، والتقاط تلقائي للقيم، واختبارات معلمنة جاهزة، وتوازيًا افتراضيًا. أبقِ اختبارات الواجهة والأداء في XCTest؛ فهذا ما تطلبه توثيقات Apple. بما أن الإطارين يمكن أن يتعايشا في الهدف نفسه، وأن Swift 6.4 جلب التقارير المتقاطعة بينهما، فإن الترحيل الجماعي غير ضروري — تقدّم ملفًا تلو الآخر.
احصل على استشارة مجانيةلا. أصبح Swift Testing الخيار الافتراضي الذي توصي به Apple لاختبارات الوحدة والتكامل الجديدة، لكن XCTest لم يُزَل؛ فـ XCUITest (أتمتة الواجهة) وXCTMetric (اختبار الأداء) ما زالا حصريين في XCTest. يعمل الإطاران في الهدف نفسه، ويتعاونان عبر تقارير متقاطعة منذ Swift 6.4.