Behoud bij het migreren van XCTest-unittests op een Mac de volgorde van eigenaarschap: rond het werk af, geef de bedoelde sterke referenties vrij en controleer daarna de zwakke referentie. Voor synchrone opruiming maakt een lokale defer in de test die volgorde expliciet.
Deze handleiding richt zich op het omzetten van een unittests die gevoelig is voor eigenaarschap in Xcode. Het voorbeeld berekent het aantal cellen in een synthetisch raster en controleert vervolgens of de calculator wordt vrijgegeven. Het gebruikt alleen lokale Swift-objecten en gehele getallen.
Welk gedrag van de levenscyclus moet bij de migratie behouden blijven?
XCTest biedt een door het framework beheerde volgorde. Nadat een testmethode eindigt, voert XCTest geregistreerde teardown-blokken uit in omgekeerde volgorde van registratie, gevolgd door de teardown-methoden van de instantie. Geregistreerde opruimcode wordt ook uitgevoerd wanneer een testassertie mislukt; beëindiging van het proces kan dat verhinderen. Deze details zijn van belang wanneer het ene blok een object vrijgeeft en een ander de zwakke referentie controleert. De status in je tests instellen en opruimen
Een teardown-blok dat controleert op nil moet bijvoorbeeld worden uitgevoerd nadat het blok de laatste bedoelde eigenaar heeft verwijderd. Omdat XCTest de blokken in omgekeerde registratievolgorde uitvoert, registreer je de controle eerst en het vrijgeven daarna. Als je deze registraties omdraait, verandert wat de test waarneemt.
In de migratiehandleiding van Swift Testing worden setup en teardown gekoppeld aan respectievelijk een initializer en een deinitializer van een klasse- of actorsuite. Die koppeling is nuttig voor opruiming die samenhangt met de levensduur van de suite. Een eigenaarschapsassertie vereist een aanvullende keuze: welke referentie moet precies verdwijnen voordat de assertie wordt uitgevoerd? Een test migreren vanuit XCTest
Gebruik deze beslissingstabel voordat je de syntaxis van de test wijzigt:
| Bestaande verantwoordelijkheid | Migratiekeuze | Reden |
|---|---|---|
| Voor elke instancetest nieuwe fixturestatus aanmaken | Initialisatie van opgeslagen eigenschappen of init() |
Zorgt dat de fixture klaarstaat voordat de test wordt uitgevoerd |
| Een object vrijgeven en direct daarna een zwakke referentie inspecteren | Expliciet vrijgeven, gevolgd door #expect, meestal in een lokale defer |
Maakt het observatiemoment zichtbaar |
| Synchrone opruiming uitvoeren die gekoppeld is aan de levensduur van een referentietypesuite | Klasse- of actorsuite met deinit |
Volgt de gedocumenteerde koppeling van de levenscyclus |
| Opruiming afwachten of een opruimfout melden | Opruiming afwachten in een asynchrone functie, of een geschikte aangepaste scope gebruiken | Vereist een uitvoeringspad dat opschorting en foutafhandeling ondersteunt |
De tabel helpt bij migratiekeuzes op basis van de gedocumenteerde levenscyclus en taalregels. Dit betekent niet dat elke XCTest-hook een uitwisselbare methode in Swift Testing heeft.
Waarom kan het verplaatsen van de zwakke controle naar deinit de test veranderen?
Swift roept een deinitializer van een klasse aan vlak voordat de instantie wordt gedealloceerd. De deinitializer heeft dan nog toegang tot de opgeslagen eigenschappen van die instantie. Daardoor bewijst het starten van de deinit van een suite op zichzelf niet dat een object dat door een van die eigenschappen wordt vastgehouden al is vrijgegeven. Deinitialisatie
Stel dat een suite een sterke eigenschap layout heeft. Een zwakke referentie naar die layout kan tijdens de uitvoering van de deinitializer van de suite nog steeds niet-nil zijn. De test moet de sterke eigenschapsreferentie verwijderen voordat deze controleert of de layout is vrijgegeven.
Er is nog een andere manier waarop dit kan misgaan: een referentiecyclus die de suite vasthoudt, kan voorkomen dat de deinitializer wordt uitgevoerd. Een assertie die alleen in die deinitializer staat, krijgt dan geen kans om het vasthouden te melden. Dit volgt uit de eigenaarschapsregels van ARC: sterke referenties houden instanties in leven, terwijl zwakke referenties de deallocatie niet verhinderen en nil worden wanneer de instantie waarnaar ze verwijzen, wordt gedealloceerd. Automatisch referentietellen
Houd bij een eigenschapstest de waarnemer buiten het object waarvan je de vrijgave test. Geef de test een specifieke vrijgavebewerking en voer de assertie uit via de normale controlestroom van de test. Zo kan het contract van de test worden gecontroleerd zonder afhankelijk te zijn van de deallocatie van de suite zelf.
Leg de XCTest-basis vast in Xcode
Gebruik een bestaande unittesttarget in Xcode 16 of hoger. Apple documenteert dat XCTest en Swift Testing naast elkaar kunnen bestaan in dezelfde target en bundel, zodat je voor de migratie geen andere unittestbundel nodig hebt. Als je een testbestand wilt toevoegen, kies je File > New > New File From Template en selecteer je, afhankelijk van wat je nodig hebt, XCTest Unit Test of Swift Testing Unit Test. Tests toevoegen aan je Xcode-project
Hieronder staat een volledig XCTest-testbestand. De calculator geeft 3 * 4 terug, wat een verwacht resultaat van 12 oplevert. De twee teardown-registraties maken de vereiste uitvoeringsvolgorde bewust zichtbaar.
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
)
}
}
Het vrijgaveblok houdt de test case vast, zodat het de fixture-eigenschap kan bijwerken. Het observatieblok legt de calculator zwak vast en voorkomt zo dat de controle zelf een extra sterke eigenaar toevoegt. Apple beschrijft teardown-blokken als de plek voor testspecifieke opruiming die naast de bijbehorende setup wordt geregistreerd. De status in je tests instellen en opruimen
Het contract van de basistest is nauwkeurig afgebakend: nadat de testcode is afgerond en de fixture-eigenschap is gewist, mag de calculator niet meer bestaan. De test meet geen geheugengebruik en stelt niet vast dat elk object dat de applicatie heeft aangemaakt, is vrijgegeven.
Verplaats de volgorde van eigenaarschap naar een lokale scope
Gebruik voor dit synchrone geval een structsuite en plaats het vrijgeven en controleren samen in één defer-blok. Swift voert uitgestelde code uit wanneer de uitvoering de omsluitende scope verlaat, ook bij een vroege return of een gegooide fout. Meerdere uitgestelde blokken worden in omgekeerde volgorde uitgevoerd; één blok maakt de volgorde vrijgeven-en-dan-controleren in dit voorbeeld eenvoudiger te volgen. Controlestroom
Hieronder staat een volledig, zelfstandig Swift Testing-bestand. Vervang het basisbestand hierdoor, of houd de voorbeelden in aparte bestanden zodat hun private hulptypen gescheiden blijven. De tweede test is een diagnostische controle: die laat zien dat een extra sterke eigenaar de zwakke referentie gevuld houdt totdat die eigenaar wordt gewist.
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)
}
}
In de eerste test is de calculator eigendom van de fixture, en houdt observed er een zwakke referentie naar bij zonder eigenaar te zijn. De berekening levert een optionele integer op, die #require unwrapt. De unwrapte calculator wordt niet in een andere lokale variabele opgeslagen. Wanneer de scope wordt verlaten, wist het uitgestelde blok de referentie van de fixture en controleert het de waarnemer.
Een mislukte #expect registreert een probleem en laat de uitvoering doorgaan. Een mislukte #require gooit een fout en stopt dat uitvoeringspad. Omdat de defer vóór de vereiste wordt geregistreerd, worden de opruiming en de zwakke controle nog steeds uitgevoerd als de vereiste een fout gooit. Verwachtingen en bevestigingen, Foutafhandeling
De diagnostische controle stelt twee afzonderlijke verwachte toestanden vast: als één van twee eigenaars wordt gewist, blijft de waarnemer niet-nil; als de resterende eigenaar wordt gewist, wordt de waarnemer nil. Dit zijn illustratieve eigenaarschapscontroles, geen gemeten applicatieresultaten. De Swift-voorbeelden zijn nagekeken, maar konden in de onderzoeksomgeving niet worden gecompileerd omdat er geen Swift-uitvoerbaar bestand beschikbaar was.
Controleer de grenzen van hulpfuncties, concurrency en asynchrone opruiming
Een lokale defer hoort bij de omsluitende scope. Als je het uitgestelde blok naar een fabrieksfunctie verplaatst, wordt het uitgevoerd wanneer die functie terugkeert, mogelijk voordat de test het geretourneerde object gebruikt. Houd het blok in de test, of ontwerp een hulpfunctie die de volledige bewerking binnen de eigen scope uitvoert. De omsluitende scope maakt deel uit van het eigendomscontract. Controlestroom
Controleer ook waar de fixture zich bevindt. Swift Testing maakt voor elke instancetestmethode een aparte suite-instantie aan, en tests binnen een suite worden standaard parallel uitgevoerd. Instantiegebonden of testgebonden status past bij dit model; een globale fixture of statische verzameling introduceert eigenaarschap dat over tests wordt gedeeld. Testfuncties organiseren met suitetypen
Isoleer Swift Testing-tests voor Mac-applicatiecode die de main actor vereist expliciet met @MainActor. De migratiehandleiding vermeldt dat Swift Testing het standaardgedrag van XCTest voor synchronische testmethoden niet overneemt. De synthetische calculator hierboven heeft die vereiste niet. Een test migreren vanuit XCTest
Het synchrone uitgestelde blok vervangt geen teardown die moet worden afgewacht. De uitgestelde acties van Swift kunnen geen fout uit het blok naar buiten gooien. Als opruiming kan mislukken, handel die fout dan expliciet af en rapporteer deze in plaats van haar te negeren. Houd opruiming die moet worden afgewacht in een asynchroon uitvoeringspad en zorg dat zowel bij een geslaagde als bij een falende uitvoering van de testcode de opruiming wordt bereikt. Foutafhandeling
Voor herbruikbare, door het framework beheerde setup en teardown biedt de huidige Swift Testing TestScoping, dat aangepaste uitvoering rond een test of suite mogelijk maakt. Apple vermeldt beschikbaarheid vanaf Swift 6.1 en Xcode 16.3. Dit is een ondersteunde aanpak wanneer meerdere tests een gemeenschappelijke scope nodig hebben, maar vereist een aangepaste trait en scopeprovider in plaats van alleen addTeardownBlock een andere naam te geven. TestScoping
Onderzoek een zwakke controle die na de migratie verandert
Als de gemigreerde eigendomstest mislukt, controleer dan deze punten in deze volgorde:
- Bevestig het observatiemoment. De bedoelde vrijgave moet plaatsvinden vóór de zwakke assertie.
- Inspecteer de fixture-eigenschappen. Een overgebleven eigenschap kan de calculator nog steeds vasthouden nadat een andere eigenschap is gewist.
- Inspecteer lokale aliassen en closure-captures. Een unwrapt object of sterk vastgelegde referentie kan het eigenaarschap tijdens de controle verlengen.
- Inspecteer de hulpfunctiescope. Uitgestelde opruiming in een hulpfunctie kan te vroeg plaatsvinden voor het bedoelde contract van de test.
- Inspecteer werk dat nog loopt. Als het echte object asynchroon werk start, definieer en wacht dan op de voltooiing daarvan voordat je een synchrone verwachting over vrijgave toepast.
Een niet-nil zwakke referentie duidt op een object dat op het observatiemoment nog bestaat. Onderzoek de overgebleven eigenaar voordat je de assertie wijzigt of een vertraging toevoegt. In dit synthetische voorbeeld is er geen asynchroon werk waarop gewacht moet worden, en de diagnostische controle laat precies zien welk effect een extra eigenaar op het resultaat heeft.
De migratie is afgerond wanneer de berekening en eigenaarschapsasserties dezelfde bedoelde uitkomst uitdrukken als de basistest: bereken 12, verwijder het bedoelde eigenaarschap van de fixture en controleer de vrijgave via een onafhankelijk bijgehouden zwakke referentie. Houd die volgorde zichtbaar wanneer de test verder wordt ontwikkeld.
Bronnen
De status in je tests instellen en opruimen — Apple Developer Documentation.
Een test migreren vanuit XCTest — Swift-project.
Deinitialisatie — Swift-project, The Swift Programming Language.
Automatisch referentietellen — Swift-project, The Swift Programming Language.
Tests toevoegen aan je Xcode-project — Apple Developer Documentation.
Controlestroom — Swift-project, The Swift Programming Language.
Verwachtingen en bevestigingen — Swift-project.
Foutafhandeling — Swift-project, The Swift Programming Language.
Testfuncties organiseren met suitetypen — Swift-project.
TestScoping — Apple Developer Documentation.