iOS handleiding voor unit-testen en UI-tests

Update: Michael Katz heeft deze handleiding bijgewerkt voor Xcode 10.1, Swift 4.2 en iOS 12. Audrey Tam schreef het origineel.

Het schrijven van tests is niet erg glamoureus, maar omdat tests ervoor zorgen dat je sprankelende app niet in een door bugs geteisterd stuk rommel verandert, is het noodzakelijk. Als je deze tutorial leest, weet je al dat je tests moet schrijven voor je code en UI, maar je weet misschien niet hoe.

Je hebt misschien een werkende app, maar je wilt wijzigingen testen die je aanbrengt om de app uit te breiden. Misschien hebt u al tests geschreven, maar weet u niet zeker of het de juiste tests zijn. Of u bent begonnen aan een nieuwe app en wilt gaandeweg testen.

Deze tutorial laat het je zien:

  • Hoe u Xcode’s Test navigator kunt gebruiken om het model en de asynchrone methoden van een app te testen
  • Hoe u interacties met bibliotheek- of systeemobjecten kunt namaken door stubs en mocks te gebruiken
  • Hoe u UI en prestaties kunt testen
  • Hoe u de code coverage tool kunt gebruiken

Gaandeweg, zul je wat van de woordenschat oppikken die gebruikt wordt door test ninja’s.

Ontdekken wat je moet testen

Voordat je tests schrijft, is het belangrijk om de basis te kennen. Wat moet je testen?

Als u een bestaande app wilt uitbreiden, moet u eerst tests schrijven voor elk onderdeel dat u van plan bent te veranderen.

In het algemeen moeten tests betrekking hebben op:

  • Kernfunctionaliteit: Modelklassen en -methoden en hun interacties met de controller
  • De meest voorkomende UI-workflows
  • Begrenzingsvoorwaarden
  • Bugfixes

Best Practices for Testing

Het acroniem FIRST beschrijft een beknopte set criteria voor effectieve unit tests. Deze criteria zijn:

  • Snel: Tests moeten snel lopen.
  • Onafhankelijk/geïsoleerd: Tests mogen geen state met elkaar delen.
  • Herhaalbaar: U moet dezelfde resultaten verkrijgen elke keer dat u een test uitvoert. Externe data providers of concurrency problemen kunnen intermitterende fouten veroorzaken.
  • Zelf-validerend: Tests moeten volledig geautomatiseerd zijn. De output moet ofwel “pass” of “fail” zijn, in plaats van te vertrouwen op de interpretatie van een log file door een programmeur.
  • Tijdig: Idealiter moeten tests worden geschreven voordat u de productiecode schrijft die ze testen (Test-Driven Development).

Het volgen van de FIRST-principes houdt uw tests duidelijk en nuttig, in plaats van te veranderen in wegversperringen voor uw app.

Getting Started

Begin met het downloaden van de projectmaterialen met behulp van de knop Download Materials bovenaan of onderaan deze tutorial. Er zijn twee afzonderlijke startprojecten: BullsEye en HalfTunes.

  • BullsEye is gebaseerd op een voorbeeld-app in iOS Apprentice. De logica van het spel zit in de BullsEyeGame klasse, die je tijdens deze tutorial zult testen.
  • HalfTunes is een bijgewerkte versie van de voorbeeld app uit de URLSession Tutorial. Gebruikers kunnen de iTunes API bevragen op liedjes, en vervolgens liedjes downloaden en afspelen.

Unit Testing in Xcode

De Test navigator biedt de makkelijkste manier om met tests te werken; je zult het gebruiken om test targets te maken en tests uit te voeren tegen je app.

Eenheidstestdoel maken

Open het BullsEye-project en druk op Command-6 om de Test-navigator te openen.

Klik op de knop + linksonder en selecteer vervolgens Nieuwe eenheidstestdoel… in het menu:

Accepteer de standaardnaam, BullsEyeTests. Wanneer de testbundel in de Testnavigator verschijnt, klikt u op om de bundel in de editor te openen. Als de bundel niet automatisch verschijnt, los dan problemen op door op een van de andere navigatoren te klikken, en ga vervolgens terug naar de Test-navigator.

Het standaardsjabloon importeert het testraamwerk, XCTest, en definieert een BullsEyeTests-subklasse van XCTestCase, met setUp(), tearDown(), en voorbeeldtestmethoden.

Er zijn drie manieren om de tests uit te voeren:

  1. Product ▸ Test of Command-U. Beide voeren alle testklassen uit.
  2. Klik op de pijlknop in de Test navigator.
  3. Klik op de diamantknop in de goot.

U kunt ook een individuele testmethode uitvoeren door op het diamantje ervan te klikken, hetzij in de Test navigator of in de goot.

Probeer de verschillende manieren om tests uit te voeren om een gevoel te krijgen van hoe lang het duurt en hoe het eruit ziet. De voorbeeldtests doen nog niets, dus ze lopen heel snel!

Wanneer alle tests slagen, worden de diamanten groen en tonen vinkjes. U kunt op de grijze diamant aan het eind van testPerformanceExample() klikken om het Performance Result te openen:

U hebt testPerformanceExample() of testExample() niet nodig voor deze tutorial, dus verwijder ze.

XCTAssert gebruiken om modellen te testen

Eerst zult u XCTAssert functies gebruiken om een kernfunctie van BullsEye’s model te testen: Berekent een BullsEyeGame object de score van een ronde correct?

In BullsEyeTests.swift, voeg deze regel toe net onder het import statement:

@testable import BullsEye

Dit geeft de unit tests toegang tot de interne types en functies in BullsEye.

Top de BullsEyeTests klasse voegt u deze eigenschap toe:

var sut: BullsEyeGame!

Dit creëert een plaatshouder voor een BullsEyeGame, die het System Under Test (SUT) is, of het object dat deze testcase-klasse wil testen.

Volgende, vervang de inhoud van setup() door deze:

super.setUp()sut = BullsEyeGame()sut.startNewGame()

Dit creëert een BullsEyeGame object op klasse-niveau, zodat alle tests in deze test klasse toegang hebben tot de eigenschappen en methoden van het SUT object.

Hier roept u ook de startNewGame() van het spel aan, die de targetValue initialiseert. Veel van de tests zullen targetValue gebruiken om te testen of het spel de score correct berekent.

Voordat u het vergeet, laat uw SUT object in tearDown() los. Vervang de inhoud door:

sut = nilsuper.tearDown()
Opmerking: Het is een goede gewoonte om de SUT te maken in setUp() en deze vrij te geven in tearDown() om er zeker van te zijn dat elke test met een schone lei begint. Voor meer discussie, zie Jon Reid’s post over dit onderwerp.

Het schrijven van je eerste test

Nu, ben je klaar om je eerste test te schrijven!

Voeg de volgende code toe aan het einde van BullsEyeTests:

func testScoreIsComputed() { // 1. given let guess = sut.targetValue + 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

De naam van een testmethode begint altijd met test, gevolgd door een beschrijving van wat het test.

Het is een goed gebruik om de test op te maken in gegeven, wanneer en dan secties:

  1. Gegeven: Hier stel je de nodige waarden in. In dit voorbeeld maakt u een guess waarde, zodat u kunt aangeven hoeveel het verschilt van targetValue.
  2. Wanneer: In dit gedeelte voert u de code uit die wordt getest: Bel check(guess:).
  3. Dan: Dit is het gedeelte waar je het resultaat dat je verwacht bevestigt met een bericht dat wordt afgedrukt als de test mislukt. In dit geval moet sut.scoreRound gelijk zijn aan 95 (100 – 5).

Uitvoeren van de test door te klikken op het diamant icoon in de goot of in de Test navigator. Hierdoor wordt de app gebouwd en uitgevoerd, en het diamantpictogram verandert in een groen vinkje!

Opmerking: Om een volledige lijst van XCTestAssertions te zien, gaat u naar Apple’s Assertions Listed by Category.

Debugging a Test

Er is met opzet een bug ingebouwd in BullsEyeGame, en u zult nu oefenen om deze te vinden. Om de bug in actie te zien, zult u een test maken die 5 aftrekt van targetValue in de gegeven sectie, en al het andere hetzelfde laat.

Voeg de volgende test toe:

func testScoreIsComputedWhenGuessLTTarget() { // 1. given let guess = sut.targetValue - 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

Het verschil tussen guess en targetValue is nog steeds 5, dus de score zou nog steeds 95 moeten zijn.

In de Breakpoint navigator, voeg een Test Failure Breakpoint toe. Hiermee stopt u de test wanneer een testmethode een foutmelding geeft.

Run uw test en deze zou moeten stoppen op de regel XCTAssertEqual met een testfout.

Controleer sut en guess in de debugconsole:

guess is targetValue - 5 maar scoreRound is 105, niet 95!

Om dit verder te onderzoeken, gebruikt u het normale debuggingproces: Zet een breekpunt op het when statement en ook een in BullsEyeGame.swift, binnen check(guess:), waar het difference maakt. Voer dan de test opnieuw uit, en stap over het let difference statement om de waarde van difference in de app te inspecteren:

Het probleem is dat difference negatief is, dus de score is 100 – (-5). Om dit op te lossen, moet u de absolute waarde van difference gebruiken. In check(guess:), uncomment de juiste regel en verwijder de onjuiste.

Verwijder de twee breakpoints, en voer de test opnieuw uit om te bevestigen dat het nu slaagt.

Using XCTestExpectation to Test Asynchronous Operations

Nu dat u geleerd hebt hoe u modellen test en test mislukkingen debugt, is het tijd om over te gaan tot het testen van asynchrone code.

Open de HalfTunes project. Het gebruikt URLSession om de iTunes API te ondervragen en voorbeelden van liedjes te downloaden. Stel dat je het wilt wijzigen om AlamoFire te gebruiken voor netwerk operaties. Om te zien of er iets kapot gaat, moet u tests schrijven voor de netwerkbewerkingen en deze uitvoeren voor en na het wijzigen van de code.

URLSession methoden zijn asynchroon: ze keren direct terug, maar zijn pas later klaar met werken. Om asynchrone methoden te testen, gebruikt u XCTestExpectation om uw test te laten wachten tot de asynchrone bewerking is voltooid.

Asynchrone tests zijn meestal traag, dus u moet ze gescheiden houden van uw snellere unit tests.

Maak een nieuwe unit test target met de naam HalfTunesSlowTests. Open de HalfTunesSlowTests klasse, en importeer de HalfTunes app module net onder de bestaande import verklaring:

@testable import HalfTunes

Alle tests in deze klasse gebruiken de standaard URLSession om verzoeken naar Apple’s servers te sturen, dus declareer een sut object, maak het aan in setUp() en geef het vrij in tearDown().

Vervang de inhoud van de HalfTunesSlowTests klasse met:

var sut: URLSession!override func setUp() { super.setUp() sut = URLSession(configuration: .default)}override func tearDown() { sut = nil super.tearDown()}

Volgende, voeg deze asynchrone test toe:

// Asynchronous test: success fast, failure slowfunc testValidCallToiTunesGetsHTTPStatusCode200() { // given let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") // 1 let promise = expectation(description: "Status code: 200") // when let dataTask = sut.dataTask(with: url!) { data, response, error in // then if let error = error { XCTFail("Error: \(error.localizedDescription)") return } else if let statusCode = (response as? HTTPURLResponse)?.statusCode { if statusCode == 200 { // 2 promise.fulfill() } else { XCTFail("Status code: \(statusCode)") } } } dataTask.resume() // 3 wait(for: , timeout: 5)}

Deze test controleert of het verzenden van een geldige query naar iTunes een 200 status code retourneert. Het grootste deel van de code is hetzelfde als wat u in de app zou schrijven, met deze extra regels:

  1. expectation(description:): Geeft een XCTestExpectation object terug, opgeslagen in promise. De description parameter beschrijft wat u verwacht dat er zal gebeuren.
  2. promise.fulfill(): Roep dit aan in de success condition closure van de completion handler van de asynchrone methode om te markeren dat aan de verwachting is voldaan.
  3. wait(for:timeout:): Houdt de test draaien totdat aan alle verwachtingen is voldaan, of de timeout interval eindigt, wat het eerst gebeurt.

Run de test. Als u verbinding heeft met internet, duurt het ongeveer een seconde voordat de test slaagt, nadat de app in de simulator is geladen.

Snel falen

Falen doet pijn, maar het hoeft niet eeuwig te duren.

Om een mislukking te ervaren, verwijdert u gewoon de ‘s’ uit “itunes” in de URL:

let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")

Uitvoeren van de test. Het mislukt, maar het duurt de volledige time-out interval! Dit komt omdat je aannam dat het verzoek altijd zou slagen, en dat is waar je promise.fulfill() aanriep. Omdat het verzoek faalde, eindigde het pas toen de time-out was verstreken.

Je kunt dit verbeteren en de test sneller laten falen door de aanname te veranderen: In plaats van te wachten tot het verzoek slaagt, wacht je alleen tot de completion handler van de asynchrone methode wordt aangeroepen. Dit gebeurt zodra de app een antwoord – hetzij OK of fout – van de server ontvangt, dat aan de verwachting voldoet. Uw test kan dan controleren of het verzoek geslaagd is.

Om te zien hoe dit werkt, maakt u een nieuwe test.

Maar eerst, repareer de vorige test door het ongedaan maken van de wijziging die u hebt aangebracht in de url.
Dan, voeg de volgende test aan uw klasse:

func testCallToiTunesCompletes() { // given let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba") let promise = expectation(description: "Completion handler invoked") var statusCode: Int? var responseError: Error? // when let dataTask = sut.dataTask(with: url!) { data, response, error in statusCode = (response as? HTTPURLResponse)?.statusCode responseError = error promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertNil(responseError) XCTAssertEqual(statusCode, 200)}

Het belangrijkste verschil is dat gewoon het invoeren van de completion handler voldoet aan de verwachting, en dit duurt slechts ongeveer een seconde om te gebeuren. Als het verzoek faalt, faalt de then assertie.

Run de test. Het zou nu ongeveer een seconde moeten duren voordat hij faalt. Het faalt omdat het verzoek faalde, niet omdat de test timeout overschreed.

Verhelp de url, en voer dan de test opnieuw uit om te bevestigen dat het nu slaagt.

Faking Objects and Interactions

Asynchrone tests geven u vertrouwen dat uw code correcte input genereert voor een asynchrone API. Misschien wilt u ook testen of uw code correct werkt wanneer het invoer ontvangt van een URLSession, of dat het de standaarddatabase van de gebruiker of een iCloud-container correct bijwerkt.

De meeste apps hebben interactie met systeem- of bibliotheekobjecten – objecten die u niet controleert – en tests die interactie hebben met deze objecten kunnen traag en niet-herhaalbaar zijn, waardoor ze twee van de FIRST-principes schenden. In plaats daarvan kun je de interacties faken door input te krijgen van stubs of door mock-objecten te updaten.

Gebruik fakery wanneer je code afhankelijk is van een systeem- of bibliotheekobject. Je kunt dit doen door een nep-object te maken om die rol te spelen en deze nep in je code te injecteren. Dependency Injection van Jon Reid beschrijft verschillende manieren om dit te doen.

Fake Input From Stub

In deze test controleert u of updateSearchResults(_:) van de app de door de sessie gedownloade gegevens correct parseert, door te controleren of searchResults.count correct is. De SUT is de view controller, en je zult de sessie namaken met stubs en wat vooraf gedownloade data.

Ga naar de Test navigator en voeg een nieuwe Unit Test Target toe. Noem het HalfTunesFakeTests. Open HalfTunesFakeTests.swift en importeer de HalfTunes app module net onder de import verklaring:

@testable import HalfTunes

Nu, vervang de inhoud van de HalfTunesFakeTests class door deze:

var sut: SearchViewController!override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? SearchViewController}override func tearDown() { sut = nil super.tearDown()}

Dit verklaart de SUT, die een SearchViewController is, creëert het in setUp() en geeft het vrij in tearDown():

Opmerking: De SUT is de view controller, omdat HalfTunes een enorm view controller probleem heeft – al het werk wordt gedaan in SearchViewController.snel. Het verplaatsen van de netwerk code naar een aparte module zou dit probleem verminderen en, ook, het testen makkelijker maken.

Volgende, je hebt wat voorbeeld JSON data nodig die je nep sessie aan je test zal leveren. Een paar items zijn genoeg, dus om je download resultaten in iTunes te beperken voeg je &limit=3 toe aan de URL string:

https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3

Kopieer deze URL en plak hem in een browser. Dit download een bestand met de naam 1.txt, 1.txt.js of iets dergelijks. Bekijk het om te bevestigen dat het een JSON bestand is, hernoem het dan abbaData.json.

Nu, ga terug naar Xcode en ga naar de Project navigator. Voeg het bestand toe aan de HalfTunesFakeTests groep.

Het HalfTunes project bevat het ondersteunende bestand DHURLSessionMock.swift. Dit definieert een eenvoudig protocol met de naam DHURLSession, met methoden (stubs) om een gegevenstaak te maken met ofwel een URL of een URLRequest. Het definieert ook URLSessionMock, dat zich conformeert aan dit protocol met initializers waarmee u een mock URLSession object kunt maken met uw keuze van data, response en error.

Om de fake op te zetten, ga naar HalfTunesFakeTests.swift en voeg het volgende toe in setUp(), na het statement dat de SUT aanmaakt:

let testBundle = Bundle(for: type(of: self))let path = testBundle.path(forResource: "abbaData", ofType: "json")let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")let urlResponse = HTTPURLResponse( url: url!, statusCode: 200, httpVersion: nil, headerFields: nil)let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)sut.defaultSession = sessionMock

Dit zet de nep data en respons op en maakt het nep sessie object. Tenslotte, aan het eind, injecteert het de nep-sessie in de app als een eigenschap van sut.

Nu, ben je klaar om de test te schrijven die controleert of het aanroepen van updateSearchResults(_:) de nep-gegevens parseert. Voeg de volgende test toe:

func test_UpdateSearchResults_ParsesData() { // given let promise = expectation(description: "Status code: 200") // when XCTAssertEqual( sut.searchResults.count, 0, "searchResults should be empty before the data task runs") let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") let dataTask = sut.defaultSession.dataTask(with: url!) { data, response, error in // if HTTP request is successful, call updateSearchResults(_:) // which parses the response data into Tracks if let error = error { print(error.localizedDescription) } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { self.sut.updateSearchResults(data) } promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertEqual(sut.searchResults.count, 3, "Didn't parse 3 items from fake response")}

Je moet dit nog steeds schrijven als een asynchrone test omdat de stub doet alsof het een asynchrone methode is.

De wanneer bewering is dat searchResults leeg is voordat de data-taak wordt uitgevoerd. Dit zou waar moeten zijn, omdat je een compleet nieuwe SUT hebt gemaakt in setUp().

De nep data bevat de JSON voor drie Track objecten, dus de then bewering is dat de view controller’s searchResults array drie items bevat.

Run de test. Hij zou vrij snel moeten slagen, omdat er geen echte netwerkverbinding is!

Fake Update to Mock Object

De vorige test gebruikte een stub om invoer van een nep-object te leveren. Nu zult u een schijnobject gebruiken om te testen of uw code UserDefaults correct bijwerkt.

Open het BullsEye-project. De app heeft twee spelstijlen: De gebruiker beweegt de schuifregelaar om de doelwaarde te evenaren of raadt de doelwaarde uit de schuifregelaarpositie. Een gesegmenteerde controle in de rechter benedenhoek schakelt de spelstijl en slaat het op in de standaardinstellingen van de gebruiker.

Uw volgende test zal controleren of de app de gameStyle eigenschap correct opslaat.

Klik in de Test navigator op New Unit Test Class en noem het BullsEyeMockTests. Voeg het volgende toe onder het import statement:

@testable import BullsEyeclass MockUserDefaults: UserDefaults { var gameStyleChanged = 0 override func set(_ value: Int, forKey defaultName: String) { if defaultName == "gameStyle" { gameStyleChanged += 1 } }}

MockUserDefaults overrides set(_:forKey:) to increment the gameStyleChanged flag. Vaak ziet u soortgelijke tests die een Bool-variabele instellen, maar het ophogen van een Int geeft u meer flexibiliteit – uw test zou bijvoorbeeld kunnen controleren of de methode slechts eenmaal wordt aangeroepen.

Declare de SUT en het mock object in BullsEyeMockTests:

var sut: ViewController!var mockUserDefaults: MockUserDefaults!

Volgende, vervang de standaard setUp() en tearDown() door deze:

override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? ViewController mockUserDefaults = MockUserDefaults(suiteName: "testing") sut.defaults = mockUserDefaults}override func tearDown() { sut = nil mockUserDefaults = nil super.tearDown()}

Dit creëert de SUT en het mock object en injecteert het mock object als een eigenschap van de SUT.

Vervang nu de twee standaard testmethoden in het sjabloon met deze:

func testGameStyleCanBeChanged() { // given let segmentedControl = UISegmentedControl() // when XCTAssertEqual( mockUserDefaults.gameStyleChanged, 0, "gameStyleChanged should be 0 before sendActions") segmentedControl.addTarget(sut, action: #selector(ViewController.chooseGameStyle(_:)), for: .valueChanged) segmentedControl.sendActions(for: .valueChanged) // then XCTAssertEqual( mockUserDefaults.gameStyleChanged, 1, "gameStyle user default wasn't changed")}

De when-bewering is dat de gameStyleChanged-vlag 0 is voordat de testmethode de gesegmenteerde besturing wijzigt. Dus, als de dan-bewering ook waar is, betekent dit dat set(_:forKey:) precies één keer is aangeroepen.

Run de test; deze zou moeten slagen.

UI-testen in Xcode

UI-testen stelt u in staat interacties met de gebruikersinterface te testen. UI-testen werkt door het vinden van UI-objecten van een app met query’s, het synthetiseren van gebeurtenissen, en vervolgens het verzenden van de gebeurtenissen naar die objecten. Met de API kunt u de eigenschappen en status van een UI-object onderzoeken om deze te vergelijken met de verwachte status.

In de Test-navigator van het BullsEye-project voegt u een nieuw UI Test Target toe. Controleer of het te testen doel BullsEye is en accepteer de standaardnaam BullsEyeUITests.

Open BullsEyeUITests.swift en voeg deze eigenschap toe bovenaan de klasse BullsEyeUITests:

var app: XCUIApplication!

In setUp() vervangt u de verklaring XCUIApplication().launch() door het volgende:

app = XCUIApplication()app.launch()

Verander de naam van testExample() in testGameStyleSwitch().

Open een nieuwe regel in testGameStyleSwitch() en klik op de rode knop Record onder in het editorvenster:

Dit opent de app in de simulator in een modus die uw interacties als testopdrachten vastlegt. Zodra de app is geladen, tikt u op het schuifsegment van de spelstijlschakelaar en het bovenste label. Klik vervolgens op de Xcode Record-knop om de opname te stoppen.

U hebt nu de volgende drie regels in testGameStyleSwitch():

let app = XCUIApplication()app.buttons.tap()app.staticTexts.tap()

De Recorder heeft code gemaakt om dezelfde acties te testen die u in de app hebt getest. Stuur een tik naar de schuifbalk en het label. U zult deze gebruiken als basis om uw eigen UI-test te maken.
Als u andere verklaringen ziet, verwijder ze dan gewoon.

De eerste regel dupliceert de eigenschap die u hebt gemaakt in setUp(), dus verwijder die regel. U hoeft nog niets te tikken, dus verwijder ook .tap() aan het eind van regel 2 en 3. Open nu het kleine menu naast en selecteer segmentedControls.buttons.

Wat u nu overhoudt zou het volgende moeten zijn:

app.segmentedControls.buttonsapp.staticTexts

Tik op andere objecten om de recorder u te laten helpen de code te vinden die u in uw tests kunt gebruiken. Vervang nu deze regels door deze code om een bepaalde sectie te maken:

// givenlet slideButton = app.segmentedControls.buttonslet typeButton = app.segmentedControls.buttonslet slideLabel = app.staticTextslet typeLabel = app.staticTexts

Nu je namen hebt voor de twee knoppen in de gesegmenteerde controle, en de twee mogelijke labels bovenaan, voeg je de volgende code hieronder toe:

// thenif slideButton.isSelected { XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists) typeButton.tap() XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists)} else if typeButton.isSelected { XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists) slideButton.tap() XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists)}

Dit controleert om te zien of het juiste label bestaat wanneer je tap() op elke knop in de gesegmenteerde controle. Voer de test uit – alle asserties zouden moeten slagen.

Prestatietest

Uit de documentatie van Apple: Een prestatietest neemt een blok code dat u wilt evalueren en voert het tien keer uit, waarbij u de gemiddelde uitvoeringstijd en de standaardafwijking voor de runs verzamelt. Het gemiddelde van deze individuele metingen vormt een waarde voor de testrun die vervolgens kan worden vergeleken met een basislijn om succes of falen te evalueren.

Het is heel eenvoudig om een prestatietest te schrijven: U plaatst gewoon de code die u wilt meten in de afsluiting van de measure().

Om dit in actie te zien, opent u het HalfTunes-project opnieuw en voegt u in HalfTunesFakeTests.swift, voegt u de volgende test toe:

func test_StartDownload_Performance() { let track = Track( name: "Waterloo", artist: "ABBA", previewUrl: "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a") measure { self.sut.startDownload(track) }}

Loop de test en klik vervolgens op het pictogram dat naast het begin van de measure()-afsluiting verschijnt om de statistieken te bekijken.

Klik op Set Baseline om een referentietijd in te stellen. Voer vervolgens de prestatietest opnieuw uit en bekijk het resultaat – het kan beter of slechter zijn dan de uitgangswaarde. Met de knop Bewerken kunt u de basislijn terugzetten op dit nieuwe resultaat.

De basislijnen worden per apparaatconfiguratie opgeslagen, zodat u dezelfde test op verschillende apparaten kunt laten uitvoeren en elk apparaat een andere basislijn kunt laten behouden, afhankelijk van de processorsnelheid van de specifieke configuratie, het geheugen, enzovoort.

Telkens wanneer u wijzigingen aanbrengt in een app die van invloed kunnen zijn op de prestaties van de geteste methode, voert u de prestatietest opnieuw uit om te zien hoe deze zich verhoudt tot de basislijn.

Code Coverage

De code coverage tool vertelt u welke app code daadwerkelijk wordt uitgevoerd door uw tests, zodat u weet welke delen van de app code (nog) niet zijn getest.

Om codedekking in te schakelen, bewerkt u de Test-actie van het schema en schakelt u het selectievakje Verzamel dekking voor in op het tabblad Opties:

Loop alle tests (Command-U) en open vervolgens de Rapportnavigator (Command-9). Selecteer Dekking onder het bovenste item in die lijst:

Klik op de openbaarmakingsdriehoek om de lijst met functies en sluitingen in SearchViewController.swift te zien:

Schuif omlaag naar updateSearchResults(_:) om te zien dat de dekking 87,9% is.

Klik op de pijlknop voor deze functie om het bronbestand voor de functie te openen. Als u met de muis over de dekkingsannotaties in de rechterzijbalk beweegt, lichten delen van de code groen of rood op:

De dekkingsannotaties laten zien hoe vaak een test elke codesectie raakt; secties die niet werden aangeroepen, zijn rood gemarkeerd. Zoals je zou verwachten, werd de for-lus 3 keer uitgevoerd, maar niets in de foutpaden werd uitgevoerd.

Om de dekking van deze functie te vergroten, zou je abbaData.json kunnen dupliceren, en het dan bewerken zodat het de verschillende fouten veroorzaakt. Bijvoorbeeld, verander "results" in "result" voor een test die print("Results key not found in dictionary") raakt.

100% Dekking?

Hoe hard moet je streven naar 100% code dekking? Google “100% unit test coverage” en je zult een scala aan argumenten voor en tegen dit vinden, samen met discussie over de definitie zelf van “100% dekking”. Argumenten ertegen zeggen dat de laatste 10-15% de moeite niet waard is. Argumenten voor zeggen dat de laatste 10-15% het belangrijkst is, omdat het zo moeilijk te testen is. Google “hard to unit test bad design” om overtuigende argumenten te vinden dat ontestbare code een teken is van diepere ontwerpproblemen.

Where to go from here?

Je hebt nu een aantal geweldige tools om te gebruiken bij het schrijven van tests voor je projecten. Ik hoop dat deze iOS Unit Testing en UI Testing tutorial je het vertrouwen heeft gegeven om alles te testen!

Je kunt de voltooide versie van het project downloaden met behulp van de Download Materialen knop boven of onder aan deze tutorial. Blijf uw vaardigheden ontwikkelen door zelf extra tests toe te voegen.

Hier zijn enkele bronnen voor verdere studie:

  • Er zijn verschillende WWDC-video’s over het onderwerp testen. Twee goede van WWDC17 zijn: Engineering for Testability en Testing Tips & Tricks.
  • De volgende stap is automatisering: Continuous Integration en Continuous Delivery. Begin met Apple’s Automating the Test Process with Xcode Server en xcodebuild, en Wikipedia’s continuous delivery artikel, dat put uit expertise van ThoughtWorks.
  • Als je al een app hebt, maar er nog geen tests voor hebt geschreven, kun je misschien Working Effectively with Legacy Code van Michael Feathers raadplegen, want code zonder tests is legacy code!
  • Jon Reid’s Quality Coding sample app archieven zijn geweldig om meer te leren over Test Driven Development.

raywenderlich.com Weekly

De nieuwsbrief van raywenderlich.com is de eenvoudigste manier om op de hoogte te blijven van alles wat u als mobiele ontwikkelaar moet weten.

Wekelijks ontvangt u een overzicht van onze tutorials en cursussen, en als bonus ontvangt u een gratis diepgaande e-mailcursus!

Gemiddelde waardering

4.7/5

Voeg een waardering toe voor deze inhoud

Meld u aan om een waardering toe te voegen

90 beoordelingen

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.