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:
- Pastikan titik pengamatan. Pelepasan yang dimaksud harus terjadi sebelum assertion weak.
- Periksa property fixture. Property yang masih terisi dapat tetap memiliki kalkulator meski property lain telah dikosongkan.
- Periksa alias lokal dan capture closure. Objek yang telah dibuka atau referensi yang ditangkap secara strong dapat memperpanjang kepemilikan hingga pemeriksaan dilakukan.
- Periksa scope helper. Pembersihan tertunda milik helper mungkin berjalan terlalu awal untuk kontrak yang dimaksudkan test.
- 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
Menyiapkan dan Membersihkan State dalam Test Anda — Dokumentasi Developer Apple.
Memigrasikan test dari XCTest — Proyek Swift.
Deinisialisasi — Proyek Swift, The Swift Programming Language.
Automatic Reference Counting — Proyek Swift, The Swift Programming Language.
Menambahkan test ke proyek Xcode Anda — Dokumentasi Developer Apple.
Alur Kontrol — Proyek Swift, The Swift Programming Language.
Ekspektasi dan konfirmasi — Proyek Swift.
Penanganan Error — Proyek Swift, The Swift Programming Language.
Mengatur fungsi test dengan tipe suite — Proyek Swift.
TestScoping — Dokumentasi Developer Apple.