XCTest testlerinin hem tek başına hem de bir test paketi olarak geçmesini sağlamak için her teste kendi değiştirilebilir fixture'ını verin ve test edilen koda bunu enjekte edin. Bir singleton'ın paylaşımlı kalması gerekiyorsa her testten önce sıfırlayın ve testin tüm süresi boyunca çakışan erişimi önleyin.

Bu kılavuz, tek bir Mac geliştirme görevini ele alıyor: Xcode'da tek başına geçip birlikte başarısız olan yerleşim hesaplama testlerini düzeltmek. Örnekte yalnızca sentetik tamsayı genişlikleri ve yerel aritmetik kullanılıyor. Uygulama penceresi, servis taklidi, dosya depolama alanı veya ağ bağlantısı gerektirmiyor.

Bir test tek başına geçip test paketinde neden başarısız olabilir?

Yalıtılmış olarak çalıştırılan bir test, singleton'ın başlangıç durumuyla karşılaşabilir. Bir test paketi içinde önceki bir test singleton'ı zaten değiştirmiş olabilir. Bu singleton'a yapılan yeni bir başvuru yine aynı değiştirilebilir nesneyi gösterir.

Swift, örnek özellikleriyle tür özelliklerini birbirinden ayırır: her örneğin kendine ait örnek özellikleri varken, bir tür özelliğinin tek bir paylaşılan kopyası vardır. Saklanan tür özellikleri ilk erişimde bir kez başlatılır. Bu nedenle SpacingCalculator.shared'ı bir test özelliğine atamak yeni bir hesaplayıcı oluşturmaz. Swift projesinin Özellikler belgelerine bakın.

[10, 20, 30] genişliğinde öğeler içeren sentetik bir satır düşünün. Toplam genişliği, öğe genişliklerinin toplamı ile aralarındaki boşlukların toplamıdır. Varsayılan boşluk 2 olduğunda sonuç 64; boşluk 8 olduğunda ise 76 olur.

Bir testin hesaplayıcıyı yapılandırmadan varsayılan genişliği denetlediğini, diğerinin ise paylaşılan boşluğu 8 olarak değiştirdiğini varsayalım. Aşağıdaki tablo, her satıra boşluğu 2 olan yeni bir hesaplayıcıyla başlayan sıralı çalıştırmayı açıklıyor. Bunlar örnek amaçlı hesaplamalardır; kaydedilmiş Xcode test sonuçları değildir.

Çalıştırma Sıfırlama olmadan paylaşılan hesaplayıcı Her test için yeni hesaplayıcı
Tek başına varsayılan test 64: geçer 64: geçer
Tek başına geniş boşluk testi 76: geçer 76: geçer
Önce varsayılan, sonra geniş boşluk İkisi de geçer İkisi de geçer
Önce geniş boşluk, sonra varsayılan Varsayılan 76 alır: başarısız olur İkisi de geçer

Bu, işe yarar bir tanılama yöntemi sunar: başarısız olan doğrulamanın kullandığı durumu hangi testin değiştirdiğini inceleyin. Uygun bir sıra elde etmek için test adlarını değiştirmek, bağımlılığı ortadan kaldırmaz.

Fixture'ın sahibi hangi XCTest yaşam döngüsü yöntemi olmalı?

Her testin ihtiyaç duyduğu değiştirilebilir durum için örnek düzeyinde bir kurulum yöntemi kullanın. Apple, her test yönteminden önce örnek kurulumunun, test sınıfından önce ise sınıf düzeyinde kurulumun çalıştığını belgeliyor. Bu nedenle sınıf düzeyindeki kurulumda oluşturulan değiştirilebilir fixture, yöntemler arasında yeni bir başlangıç durumu almaz. Testlerinizde durumu ayarlama ve temizleme başlığına bakın.

Yapılacak işe uygun kurulum yöntemini seçin: eşzamanlı ve hata fırlatmayan fixture oluşturma setUp() ile; hata fırlatabilen oluşturma setUpWithError() ile; eşzamansız hazırlık ise setUp() async throws ile uyumludur. Mevcut testin temizliğini tamamlamak için teardown kullanın. Apple, çökme teardown'ın çalışmasını engelleyebileceğinden sonraki testleri hazırlamak için teardown'a güvenilmemesini özellikle öneriyor. Aynı yaşam döngüsü belgeleri bu sınırları açıklıyor.

Fixture seçimi hem sahipliğe hem de yürütme kapsamına bağlıdır:

Durum Uygun yaklaşım Denetlenecek koşul
Testler bağımlılığı oluşturabiliyor Her test için bir örnek oluşturup enjekte edin Tüketici bu enjekte edilmiş örneği kullanmalı
Testlerin gerçek singleton'ı sınaması gerekiyor Her testten önce eksiksiz bir başlangıç durumu oluşturun Çakışan hiçbir test bu singleton'ı değiştirmemeli
Paylaşılan yapılandırma değiştirilemez Değiştirilemez değeri yeniden kullanın Başvurulan nesneler de değişmeden kalmalı
Test eşzamansız çalışma başlatıyor Testi bitirmeden önce çalışmanın tamamlanmasını bekleyin Sonrasında fixture'ı değiştirebilecek tamamlanmamış bir iş kalmamalı

Bu hesaplayıcı için ilk seçenek en basitidir. Üretim kodu paylaşılan bir varsayılanı korurken testler bağımsız örnekleri açıkça seçebilir.

Bağımsız olarak sahip olunan bir XCTest fixture'ı oluşturun

Xcode yüklü bir Mac ve mevcut bir macOS birim testi hedefi kullanın. Aşağıdaki eksiksiz örneği bu test hedefine tek bir Swift dosyası olarak ekleyin. Kendi hesaplayıcısını, tüketicisini ve testlerini tanımlar; bu nedenle uygulama modülünü içe aktarması gerekmez. Hedefin varsayılan aktör yalıtımı nonisolated olarak ayarlanmış bir Swift 6 araç zinciri kullanın.

Hesaplayıcı, değiştirilebilir durumunun eşzamanlılık sınırını açık hale getirmek için bir aktördür. Swift aktörleri değiştirilebilir durumlarına erişimi kısıtlar ve aktörün dışından yapılan çağrılarda await kullanılır. Bu kurallar Swift projesinin Eşzamanlılık bölümünde belgelenmiştir. Aktör kullanılsa bile bağımsız fixture sahipliği gereklidir.

import XCTest

actor SpacingCalculator {
    static let shared = SpacingCalculator()

    private var gap = 2

    func setGap(_ value: Int) {
        precondition(value >= 0)
        gap = value
    }

    func width(of itemWidths: [Int]) -> Int {
        precondition(itemWidths.allSatisfy { $0 >= 0 })
        let gapCount = max(itemWidths.count - 1, 0)
        return itemWidths.reduce(0, +) + gapCount * gap
    }
}

struct RowLayout {
    let calculator: SpacingCalculator

    init(calculator: SpacingCalculator = .shared) {
        self.calculator = calculator
    }

    func width(of itemWidths: [Int]) async -> Int {
        await calculator.width(of: itemWidths)
    }
}

final class RowLayoutTests: XCTestCase {
    private var calculator: SpacingCalculator?

    override func setUp() {
        super.setUp()
        calculator = SpacingCalculator()
    }

    override func tearDown() {
        calculator = nil
        super.tearDown()
    }

    func testDefaultGap() async throws {
        let fixture = try XCTUnwrap(calculator)
        let layout = RowLayout(calculator: fixture)

        let actual = await layout.width(of: [10, 20, 30])

        XCTAssertEqual(actual, 64)
    }

    func testWideGap() async throws {
        let fixture = try XCTUnwrap(calculator)
        await fixture.setGap(8)
        let layout = RowLayout(calculator: fixture)

        let actual = await layout.width(of: [10, 20, 30])

        XCTAssertEqual(actual, 76)
    }

    func testEmptyRow() async throws {
        let fixture = try XCTUnwrap(calculator)
        let layout = RowLayout(calculator: fixture)

        let actual = await layout.width(of: [])

        XCTAssertEqual(actual, 0)
    }

    func testSingleItem() async throws {
        let fixture = try XCTUnwrap(calculator)
        let layout = RowLayout(calculator: fixture)

        let actual = await layout.width(of: [10])

        XCTAssertEqual(actual, 10)
    }
}

Belirleyici satır calculator = SpacingCalculator() ifadesidir. Her kurulum, boşluğu 2 olan bağımsız bir aktör oluşturur. Her test bu aktörü RowLayout'a geçirir. Bu testlerde RowLayout() çağrısı .shared değerini seçip fixture'ı devre dışı bırakır.

Örnek, doğrudan oluşturmaya bilerek izin verir. Mevcut singleton'ınızda özel bir başlatıcı varsa dahili bir oluşturma yolu ekleyin veya değiştirilebilir hesaplama durumunu ayrı olarak oluşturulabilen bir bağımlılığa çıkarın. Dahili bir başlatıcı, uygulama modülünün yerleşik test edilebilir içe aktarma düzenlemesiyle test hedefine açılabilir; singleton başvurusunu genel olarak değiştirilebilir hale getirmek gerekmez.

Aritmetik ayrıca iki yararlı sınır durumu tanımlar: boş bir satırın genişliği 0 olur ve tek öğe için arada boşluk bulunmaz. Örnek, tamsayı taşmasını önleyecek kadar küçük, negatif olmayan tamsayıları kabul eder; negatif değerler önkoşulları ihlal eder ve bir doğrulama hatası üretmek yerine yürütmeyi sonlandırır.

Beklenen aritmetik ve iki testin her iki sıralaması, yerel olarak deterministik Python hesaplamalarıyla denetlendi. Swift örnekleri incelendi ancak bu araştırma ortamında Swift araç zinciri bulunmadığından derlenmedi veya çalıştırılmadı. Hedefiniz için derleme ve yürütmeyi doğrulamak üzere bunları Xcode'da çalıştırın.

Singleton'ı sıfırlamak ne zaman yeterlidir?

Testlerin gerçekten paylaşılan örneğe ihtiyaç duyduğu ve testlerin görebildiği tüm durumun geri yüklenebildiği durumlarda sıfırlama, geçiş sürecinde işe yarayan bir yaklaşımdır. Testin varsayılan olduğunu varsaydığı değerler de dahil olmak üzere her testten önce başlangıç durumunu oluşturun. Yalnızca o anda başarısız olan testin değiştirdiği özelliği sıfırlamak, keşfedilmemiş başka bağımlılıkları geride bırakabilir.

Bu hesaplayıcı için eşzamansız kurulum, SpacingCalculator.shared'ı seçip her yöntemden önce setGap(2) ifadesini bekleyebilir. Bu, yalnızca başka bir çağıran doğrulamadan önce boşluğu değiştirmediği sürece aritmetik başlangıç durumunu oluşturur. Fixture'a daha sonra önbelleğe alınmış sonuçlar veya ek yapılandırmalar eklenirse başlangıç durumu işlemi bunları da kapsamalıdır.

Bir aktör, birden fazla bekleme noktasını aşan bir test için sahiplik sağlamaz. Örneğin A testi paylaşılan boşluğu 2 olarak ayarlayabilir, B testi bunu 8 olarak değiştirebilir ve A testi ardından 8 kullanarak genişlik hesaplayabilir. Her aktör işlemi doğru biçimde yürütülürken genel test varsayımı başarısız olabilir. Bu durum, Swift'in belgelenmiş aktör yürütme ve askıya alma kurallarından kaynaklanır.

Sıfırlamaya dayalı yalıtımı seçmeden önce paylaşılan nesneye erişebilecek tüm testleri ve arka plan görevlerini belirleyin. Hazırlık, yürütme ve temizlik boyunca özel kullanımı sağlayamıyorsanız bunun yerine bağımsız durum enjekte edin. Sonradan bir anlık görüntüyü geri yüklemek, başka bir çağıranın test sırasında geçici değişiklikleri görmesini engelleyemez.

Fixture, Swift Testing'e nasıl uyarlanır?

Swift Testing, her örnek test işlevi için ayrı bir suite örneği başlatır. Bu nedenle bir hesaplayıcı oluşturan örnek özelliği, XCTest kurulum geçersiz kılmaları olmadan da aynı sahiplik düzenini sağlar. .shared değerinden başlatılan bir özellik yine singleton'ı gösterir. Apple, örnek yaşam döngüsünü Suite türleriyle test işlevlerini düzenleme başlığında belgeliyor.

Önceki SpacingCalculator ve RowLayout tanımları aynı test hedefinde bir kez mevcutsa bu, Swift Testing'e ait eksiksiz ek bir suite'tir. Xcode 16 veya sonrası gibi Testing modülünü içeren bir araç zinciri gerektirir. Varsayılan aktör yalıtımı da nonisolated olmalıdır.

import Testing

struct RowLayoutSwiftTests {
    let calculator = SpacingCalculator()

    @Test func defaultGap() async {
        let layout = RowLayout(calculator: calculator)
        let actual = await layout.width(of: [10, 20, 30])
        #expect(actual == 64)
    }

    @Test func wideGap() async {
        await calculator.setGap(8)
        let layout = RowLayout(calculator: calculator)
        let actual = await layout.width(of: [10, 20, 30])
        #expect(actual == 76)
    }
}

Daha kapsamlı hazırlık için Swift Testing, eşzamansız veya hata fırlatan başlatıcılar da dahil olmak üzere suite başlatıcılarını destekler. Apple'ın XCTest'ten bir testi taşıma belgesi, XCTest kurulum yöntemlerinin yerini alan bu yaklaşımı açıklıyor. Bu fixture yalnızca yerel hesaplama durumuna sahip olduğundan ve arka plan işi başlatmadığından açık bir temizleme gerektirmez.

Swift Testing, testleri varsayılan olarak paralel çalıştırır. Bir suite'e .serialized uygulamak, içindeki testleri ve iç içe suite'leri sıralı çalıştırır ancak ilgisiz testlerin bunlarla birlikte çalışmasını engellemez. Bu niteliğin sıradan, parametreli olmayan bir test işlevine uygulanmasının etkisi yoktur. Bu sınırlar Testleri sıralı veya paralel çalıştırma belgesinde açıkça belirtilmiştir.

Sonuç olarak, singleton'a bağlı iki suite'i ayrı .serialized açıklamalarının altına yerleştirmek aralarında karşılıklı dışlama sağlamaz. Tüm rakip kullanıcılar kapsam içinde olduğu sürece ortak bir sıralı üst suite, alt öğelerini eşgüdümleyebilir. Bu hesaplama testlerinde yeni fixture'lar bu eşgüdüm gereksinimini ortadan kaldırır.

Xcode'da tekil ve paket sonuçlarını doğrulayın

Doğrulama boyunca aynı şemayı, test planını ve Mac hedefini kullanın. Apple, tekil yöntemlerin, suite'lerin ve etkin test planının çalıştırılmasını Testleri çalıştırma ve sonuçları yorumlama başlığında belgeliyor.

  1. Dört XCTest yönteminin her birini, düzenleyici kenar boşluğundaki oynat düğmesiyle ayrı ayrı çalıştırın. Sırasıyla 64, 76, 0 ve 10 sonuçlarını bekleyin.
  2. Bildiriminin yanındaki oynat düğmesiyle RowLayoutTests sınıfını çalıştırın. Dört yöntemin tamamı birlikte geçmelidir.
  3. Etkin test planını çalıştırmak için Product > Test menüsünü seçin. Bu testlerin atlanmak veya kapsam dışında bırakılmak yerine gerçekten çalıştığını doğrulayın.
  4. Swift Testing örneğini eklediyseniz iki işlevini de ayrı ayrı ve birlikte çalıştırın.

Tekil testler geçerken tüm plan başarısız oluyorsa başarısız tüketicinin kullandığı nesnenin izini sürün. Atlanmış bir enjeksiyon, .shared çağıran bir yardımcı veya kaynak testi başlattıktan sonra da devam eden eşzamansız bir çalışma arayın. Bir test tek başına başarısız oluyorsa, başarısızlığı test paketindeki sıraya bağlamadan önce girdilerini ve beklenen hesaplamayı denetleyin.

Tamamlanmış fixture, başlangıç durumunu mevcut testin oluşturma yolunda görünür kılmalıdır. Bu yerleşim hesaplamalarında her test bir hesaplayıcıya sahip olur, onu açıkça tüketiciye verir ve işlemini bekler. Böylece beklenen sonuç, komşu testlerden hangisinin önce çalıştığına bağlı olmaz.

Kaynaklar