Saat memigrasikan unit test XCTest di Mac, pertahankan urutan kepemilikan: selesaikan pekerjaan, lepaskan referensi strong yang dimaksud, lalu periksa referensi weak. Untuk pembersihan sinkron, defer lokal pada test memperjelas urutan tersebut.

Panduan ini berfokus pada konversi unit test yang sensitif terhadap kepemilikan di Xcode. Contohnya menghitung jumlah sel dalam grid sintetis, lalu memeriksa bahwa kalkulatornya telah dilepaskan. Contoh ini hanya menggunakan objek Swift lokal dan operasi aritmetika bilangan bulat.

Perilaku siklus hidup apa yang harus dipertahankan saat migrasi?

XCTest menyediakan urutan yang dikelola framework. Setelah metode test berakhir, XCTest menjalankan blok teardown yang terdaftar dalam urutan terbalik dari pendaftarannya, lalu metode teardown instance. Pembersihan yang terdaftar tetap dijalankan saat assertion test gagal; penghentian proses dapat mencegahnya. Hal-hal ini penting ketika satu blok melepaskan objek dan blok lain memeriksa referensi weak-nya. Menyiapkan dan Membersihkan State dalam Test Anda

Sebagai contoh, blok teardown yang memeriksa nil harus dijalankan setelah blok yang menghapus pemilik terakhir yang dimaksud. Karena XCTest menggunakan urutan pendaftaran terbalik, pemeriksaan didaftarkan lebih dulu dan pelepasan sesudahnya. Membalik urutan pendaftaran mengubah apa yang diamati test.

Panduan migrasi Swift Testing memetakan setup ke initializer dan teardown ke deinitializer pada suite class atau actor. Pemetaan itu berguna untuk pembersihan yang terkait dengan siklus hidup suite. Assertion kepemilikan memerlukan keputusan tambahan: referensi mana tepatnya yang harus hilang sebelum assertion dijalankan? Memigrasikan test dari XCTest

Gunakan tabel keputusan ini sebelum mengubah sintaks test:

Tanggung jawab yang ada Pilihan migrasi Alasan
Membuat state fixture baru untuk setiap test instance Inisialisasi stored property atau init() Menyiapkan fixture sebelum test dijalankan
Melepaskan objek lalu langsung memeriksa referensi weak Pelepasan eksplisit diikuti #expect, biasanya di dalam defer lokal Memperjelas titik pengamatan
Melakukan pembersihan sinkron yang terkait dengan siklus hidup suite reference type Suite class atau actor dengan deinit Mengikuti pemetaan siklus hidup yang terdokumentasi
Menunggu pembersihan atau melaporkan error pembersihan Pembersihan yang ditunggu dalam fungsi asinkron, atau scope kustom yang sesuai Memerlukan jalur eksekusi yang mendukung suspensi dan penanganan error

Tabel ini merupakan panduan keputusan migrasi yang disusun berdasarkan aturan bahasa dan siklus hidup yang terdokumentasi. Tabel ini tidak menyiratkan bahwa setiap hook XCTest memiliki metode Swift Testing yang dapat dipertukarkan dengannya.

Mengapa memindahkan pemeriksaan weak ke deinit dapat mengubah test?

Swift memanggil deinitializer class tepat sebelum mendealokasikan instance tersebut. Deinitializer masih dapat mengakses stored property instance itu. Karena itu, memasuki deinit suite saja tidak membuktikan bahwa objek yang disimpan oleh salah satu property-nya telah dilepaskan. Deinisialisasi

Pertimbangkan suite dengan property layout strong. Referensi weak ke layout tersebut dapat tetap bukan nil saat deinitializer suite sedang berjalan. Test harus menghapus referensi strong dari property sebelum memeriksa apakah layout telah dilepaskan.

Ada juga kemungkinan kegagalan lain: siklus referensi yang mempertahankan suite dapat mencegah deinitializer-nya berjalan. Assertion yang hanya ditempatkan di deinitializer tersebut tidak akan mendapat kesempatan untuk melaporkan retensi itu. Ini mengikuti aturan kepemilikan ARC: referensi strong menjaga instance tetap hidup, sedangkan referensi weak tidak mencegah dealokasi dan menjadi nil saat instance yang dirujuk didealokasi. Automatic Reference Counting

Untuk test kepemilikan, tempatkan pengamat di luar objek yang pelepasannya sedang diuji. Beri test operasi pelepasan yang spesifik dan jalankan assertion melalui alur kontrol normal test. Dengan susunan ini, kontrak test dapat diperiksa tanpa bergantung pada dealokasi suite itu sendiri.

Menetapkan baseline XCTest di Xcode

Gunakan target unit test yang sudah ada di Xcode 16 atau versi lebih baru. Apple mendokumentasikan bahwa XCTest dan Swift Testing dapat digunakan bersama dalam target dan bundle yang sama, sehingga migrasi tidak memerlukan bundle unit testing lain. Untuk menambahkan file test, gunakan File > New > New File From Template dan pilih XCTest Unit Test atau Swift Testing Unit Test yang sesuai. Menambahkan test ke proyek Xcode Anda

Berikut file test XCTest yang lengkap. Kalkulatornya mengembalikan 3 * 4, sehingga hasil yang diharapkan adalah 12. Dua pendaftaran teardown sengaja menunjukkan urutan eksekusi yang diperlukan.

import XCTest

private final class GridCalculator {
    func cellCount(columns: Int, rows: Int) -> Int {
        columns * rows
    }
}

final class GridCalculatorXCTests: XCTestCase {
    private var calculator: GridCalculator?

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

    func testCellCountAndRelease() {
        weak var observed = calculator

        // Registered first, so this executes second.
        addTeardownBlock { [weak observed] in
            XCTAssertNil(observed)
        }

        // Registered second, so this executes first.
        addTeardownBlock { [self] in
            calculator = nil
        }

        XCTAssertEqual(
            calculator?.cellCount(columns: 3, rows: 4),
            12
        )
    }
}

Blok pelepasan mempertahankan test case agar dapat memperbarui property fixture. Blok pengamatan menangkap kalkulator secara weak, sehingga pemeriksaan itu sendiri tidak menambahkan pemilik strong. Apple mendokumentasikan blok teardown sebagai tempat untuk pembersihan khusus test yang didaftarkan bersama setup terkait. Menyiapkan dan Membersihkan State dalam Test Anda

Kontrak baseline ini didefinisikan secara terbatas: setelah badan test selesai dan property fixture dikosongkan, kalkulator tidak boleh lagi ada. Kontrak ini tidak mengukur penggunaan memori atau memastikan bahwa setiap objek yang dibuat aplikasi telah dilepaskan.

Memigrasikan urutan kepemilikan ke scope lokal

Untuk kasus sinkron ini, gunakan suite struct dan tempatkan pelepasan serta verifikasi dalam satu blok defer. Swift menjalankan kode tertunda saat alur kontrol meninggalkan scope yang menaunginya, termasuk saat terjadi early return atau error dilempar. Beberapa blok tertunda dijalankan dalam urutan terbalik; satu blok membuat urutan pelepasan sebelum pemeriksaan dalam contoh ini lebih mudah dibaca. Alur Kontrol

Berikut file Swift Testing yang lengkap dan mandiri. Ganti file baseline dengan file ini, atau simpan contoh-contohnya dalam file terpisah agar tipe helper private-nya tetap terpisah. Test kedua adalah kontrol diagnostik: test ini menunjukkan bahwa pemilik strong tambahan membuat referensi weak tetap terisi sampai pemilik tersebut dikosongkan.

import Testing

private final class GridCalculator {
    func cellCount(columns: Int, rows: Int) -> Int {
        columns * rows
    }
}

private final class GridFixture {
    var calculator: GridCalculator? = GridCalculator()
    var additionalOwner: GridCalculator?
}

struct GridCalculatorTests {
    @Test
    func cellCountAndRelease() throws {
        let fixture = GridFixture()
        weak var observed = fixture.calculator

        defer {
            fixture.calculator = nil
            #expect(observed == nil)
        }

        let result = try #require(
            fixture.calculator?.cellCount(columns: 3, rows: 4)
        )
        #expect(result == 12)
    }

    @Test
    func weakProbeDetectsAnAdditionalOwner() {
        let fixture = GridFixture()
        weak var observed = fixture.calculator

        fixture.additionalOwner = fixture.calculator
        fixture.calculator = nil
        #expect(observed != nil)

        fixture.additionalOwner = nil
        #expect(observed == nil)
    }
}

Dalam test pertama, fixture memiliki kalkulator, sedangkan observed mengamatinya tanpa memilikinya. Perhitungan menghasilkan integer opsional, yang dibuka dengan #require. Kalkulator yang sudah dibuka tidak disimpan dalam variabel lokal lain. Saat scope berakhir, blok tertunda menghapus referensi fixture dan memeriksa pengamat.

#expect yang gagal mencatat masalah dan memungkinkan eksekusi berlanjut. #require yang gagal melempar error sehingga menghentikan jalur eksekusi tersebut. Karena defer didaftarkan sebelum requirement, pembersihan dan pemeriksaan weak-nya tetap dijalankan saat requirement melempar error. Ekspektasi dan konfirmasi, Penanganan Error

Kontrol diagnostik menetapkan dua state yang diharapkan secara terpisah: mengosongkan satu dari dua pemilik membuat pengamat tetap bukan nil; mengosongkan pemilik yang tersisa membuatnya menjadi nil. Ini adalah pemeriksaan kepemilikan ilustratif, bukan hasil pengukuran aplikasi. Contoh Swift telah diperiksa, tetapi tidak dapat dikompilasi di lingkungan riset karena executable Swift tidak tersedia.

Memeriksa batas helper, konkurensi, dan pembersihan asinkron

defer lokal berlaku untuk scope yang menaunginya. Memindahkan blok tertunda ke helper factory membuatnya dijalankan saat helper itu kembali, yang mungkin terjadi sebelum test menggunakan objek yang dikembalikan. Letakkan blok tersebut di dalam test, atau rancang helper yang menjalankan seluruh operasi di dalam scope helper itu. Scope yang menaungi blok merupakan bagian dari kontrak kepemilikan. Alur Kontrol

Periksa juga di mana fixture berada. Swift Testing membuat instance suite yang berbeda untuk setiap metode test instance, dan secara default test dalam satu suite berjalan secara paralel. State lokal instance atau lokal test cocok dengan model itu; fixture global atau koleksi static memperkenalkan kepemilikan yang digunakan bersama oleh beberapa test. Mengatur fungsi test dengan tipe suite

Untuk kode aplikasi Mac yang memerlukan main actor, isolasikan test Swift Testing secara eksplisit dengan @MainActor. Panduan migrasi mencatat bahwa Swift Testing tidak mewarisi perilaku main actor default XCTest untuk metode test sinkron. Kalkulator sintetis di atas tidak memiliki persyaratan tersebut. Memigrasikan test dari XCTest

Blok tertunda yang sinkron bukan pengganti teardown yang ditunggu. Aksi tertunda Swift tidak dapat melempar error ke luar blok. Jika pembersihan dapat gagal, tangani dan laporkan kegagalan tersebut secara eksplisit, alih-alih membuangnya. Tempatkan pembersihan yang ditunggu di jalur eksekusi asinkron dan pastikan jalur badan yang berhasil maupun yang melempar error tetap mencapainya. Penanganan Error

Untuk setup dan teardown yang dikelola framework serta dapat digunakan kembali, Swift Testing saat ini menyediakan TestScoping, yang memasok eksekusi kustom di sekitar test atau suite. Apple mencantumkan ketersediaannya bersama Swift 6.1 dan Xcode 16.3. Ini merupakan pendekatan yang didukung saat beberapa test memerlukan scope bersama, tetapi memerlukan trait kustom dan penyedia scope, bukan sekadar mengganti nama addTeardownBlock. TestScoping

Mendiagnosis pemeriksaan weak yang berubah setelah migrasi

Jika test kepemilikan hasil migrasi gagal, periksa hal-hal berikut secara berurutan:

  1. Pastikan titik pengamatan. Pelepasan yang dimaksud harus terjadi sebelum assertion weak.
  2. Periksa property fixture. Property yang masih terisi dapat tetap memiliki kalkulator meski property lain telah dikosongkan.
  3. Periksa alias lokal dan capture closure. Objek yang telah dibuka atau referensi yang ditangkap secara strong dapat memperpanjang kepemilikan hingga pemeriksaan dilakukan.
  4. Periksa scope helper. Pembersihan tertunda milik helper mungkin berjalan terlalu awal untuk kontrak yang dimaksudkan test.
  5. Periksa pekerjaan yang belum selesai. Jika objek sebenarnya memulai pekerjaan asinkron, tentukan dan tunggu penyelesaiannya sebelum menerapkan ekspektasi pelepasan sinkron.

Referensi weak yang bukan nil menunjukkan bahwa objek tersebut masih ada pada titik pengamatan. Selidiki pemilik yang tersisa sebelum mengubah assertion atau menambahkan jeda. Dalam contoh sintetis ini, tidak ada pekerjaan asinkron yang perlu ditunggu, dan kontrol diagnostik menunjukkan dengan tepat bagaimana pemilik tambahan memengaruhi hasil.

Migrasi selesai ketika perhitungan dan assertion kepemilikan menyatakan hasil yang dimaksud sama dengan baseline: hitung 12, hapus kepemilikan fixture yang dimaksud, dan verifikasi pelepasan melalui referensi weak yang dipegang secara independen. Pertahankan urutan tersebut agar terlihat saat test berkembang.

Sumber