Scrierea testelor nu este plină de farmec, dar din moment ce testele împiedică aplicația dvs. strălucitoare să se transforme într-o bucată de gunoi plină de erori, este necesară. Dacă citiți acest tutorial, știți deja că ar trebui să scrieți teste pentru codul și interfața de utilizare, dar s-ar putea să nu știți cum.
Se poate să aveți o aplicație funcțională, dar doriți să testați modificările pe care le faceți pentru a extinde aplicația. Poate că aveți deja teste scrise, dar nu sunteți sigur că sunt testele potrivite. Sau, ați început să lucrați la o nouă aplicație și doriți să testați pe parcurs.
Acest tutorial vă va arăta:
- Cum să folosiți navigatorul Test din Xcode pentru a testa modelul și metodele asincrone ale unei aplicații
- Cum să falsificați interacțiunile cu obiecte de bibliotecă sau de sistem folosind stubs și mocks
- Cum să testați interfața utilizator și performanța
- Cum să folosiți instrumentul de acoperire a codului
Pe parcurs, veți prelua o parte din vocabularul folosit de ninja de testare.
Descoperirea a ceea ce trebuie testat
Înainte de a scrie orice test, este important să cunoașteți elementele de bază. Ce trebuie să testați?
Dacă obiectivul dvs. este de a extinde o aplicație existentă, ar trebui să scrieți mai întâi teste pentru orice componentă pe care intenționați să o modificați.
În general, testele ar trebui să acopere:
- Funcționalitatea de bază: Clasele și metodele modelului și interacțiunile acestora cu controlerul
- Cele mai frecvente fluxuri de lucru ale interfeței utilizator
- Condiții limită
- Reglare de erori
Bune practici de testare
Acronimul FIRST descrie un set concis de criterii pentru teste unitare eficiente. Aceste criterii sunt:
- Rapid: Testele trebuie să ruleze rapid.
- Independent/Isolat: Testele nu ar trebui să împartă starea între ele.
- Repetabil: Ar trebui să obțineți aceleași rezultate de fiecare dată când executați un test. Furnizorii de date externi sau problemele de concurență ar putea cauza eșecuri intermitente.
- Autovalidare: Testele ar trebui să fie complet automatizate. Rezultatul ar trebui să fie fie „pass” sau „fail”, mai degrabă decât să se bazeze pe interpretarea de către programator a unui fișier jurnal.
- La timp: În mod ideal, testele ar trebui să fie scrise înainte de a scrie codul de producție pe care îl testează (Test-Driven Development).
Să urmați principiile FIRST va face ca testele dumneavoastră să fie clare și utile, în loc să se transforme în obstacole pentru aplicația dumneavoastră.
Începem
Începeți prin a descărca materialele proiectului folosind butonul Download Materials din partea de sus sau de jos a acestui tutorial. Există două proiecte de pornire separate: BullsEye și HalfTunes.
- BullsEye se bazează pe un exemplu de aplicație din iOS Apprentice. Logica jocului se află în clasa
BullsEyeGame
, pe care o veți testa în timpul acestui tutorial. - HalfTunes este o versiune actualizată a aplicației de probă din Tutorialul URLSession. Utilizatorii pot interoga iTunes API pentru melodii, apoi pot descărca și reda fragmente de melodii.
Unit Testing in Xcode
Navigatorul Test oferă cel mai simplu mod de a lucra cu testele; îl veți utiliza pentru a crea ținte de testare și pentru a rula testele în raport cu aplicația dumneavoastră.
Crearea unei ținte de test unitar
Deschideți proiectul BullsEye și apăsați Command-6 pentru a deschide navigatorul Test.
Click pe butonul + din colțul din stânga jos, apoi selectați New Unit Test Target… din meniu:
Acceptați numele implicit, BullsEyeTests. Când pachetul de teste apare în navigatorul Test, faceți clic pentru a deschide pachetul în editor. Dacă pachetul nu apare automat, remediați problemele făcând clic pe unul dintre celelalte navigatoare, apoi reveniți la navigatorul Test.
Șablonul implicit importă cadrul de testare, XCTest, și definește o subclasă BullsEyeTests
de XCTestCase
, cu setUp()
, tearDown()
și metode de testare de exemplu.
Există trei moduri de a rula testele:
- Product ▸ Test sau Command-U. Ambele execută toate clasele de test.
- Click pe butonul săgeată din navigatorul de teste.
- Click pe butonul diamant din jgheab.
De asemenea, puteți rula o metodă de test individuală făcând clic pe diamantul acesteia, fie în navigatorul Test, fie în jgheab.
Încercați diferitele moduri de a rula testele pentru a vă face o idee despre cât timp durează și cum arată. Testele de probă nu fac nimic încă, așa că se execută foarte repede!
Când toate testele reușesc, diamantele vor deveni verzi și vor afișa semne de verificare. Puteți face clic pe diamantul gri de la sfârșitul lui testPerformanceExample()
pentru a deschide Rezultatul Performanței:
Nu aveți nevoie de testPerformanceExample()
sau testExample()
pentru acest tutorial, așa că ștergeți-le.
Utilizarea XCTAssert pentru a testa modelele
În primul rând, veți folosi funcțiile XCTAssert
pentru a testa o funcție de bază a modelului BullsEye: Un obiect BullsEyeGame
calculează corect scorul pentru o rundă?
În BullsEyeTests.swift, adăugați această linie chiar sub declarația import
:
@testable import BullsEye
Aceasta oferă testelor unitare acces la tipurile și funcțiile interne din BullsEye.
În partea de sus a clasei BullsEyeTests
, adăugați această proprietate:
var sut: BullsEyeGame!
Aceasta creează un spațiu rezervat pentru un BullsEyeGame
, care este sistemul testat (SUT), sau obiectul pe care această clasă de caz de test se ocupă de testare.
În continuare, înlocuiți conținutul lui setup()
cu acesta:
super.setUp()sut = BullsEyeGame()sut.startNewGame()
Aceasta creează un obiect BullsEyeGame
la nivelul clasei, astfel încât toate testele din această clasă de testare să poată accesa proprietățile și metodele obiectului SUT.
Aici, apelați, de asemenea, startNewGame()
al jocului, care inițializează targetValue
. Multe dintre teste vor folosi targetValue
pentru a testa dacă jocul calculează corect scorul.
Înainte de a uita, eliberați obiectul SUT în tearDown()
. Înlocuiți conținutul său cu:
sut = nilsuper.tearDown()
setUp()
și să îl eliberați în tearDown()
pentru a vă asigura că fiecare test începe cu o tabula rasa curată. Pentru mai multe discuții, consultați postarea lui Jon Reid pe această temă. Scrierea primului test
Acum, sunteți gata să vă scrieți primul test!
Adaugați următorul cod la sfârșitul lui 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")}
Numele unei metode de test începe întotdeauna cu test, urmat de o descriere a ceea ce testează.
Este o bună practică să formatați testul în secțiunile dat, când și apoi:
guess
astfel încât să puteți specifica cât de mult diferă de targetValue
.check(guess:)
..
sut.scoreRound
ar trebui să fie egal cu 95 (100 – 5).Executați testul făcând clic pe pictograma cu diamant din jgheab sau din navigatorul Test. Acest lucru va construi și va rula aplicația, iar pictograma diamant se va schimba într-o bifă verde!
Debugging a Test
Există un bug încorporat intenționat în BullsEyeGame
și veți exersa găsirea lui acum. Pentru a vedea bug-ul în acțiune, veți crea un test care scade 5 din targetValue
în secțiunea dată, iar restul lasă totul la fel.
Adaugați următorul test:
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")}
Diferența dintre guess
și targetValue
este tot 5, așa că scorul ar trebui să fie tot 95.
În navigatorul Breakpoint, adăugați un Test Failure Breakpoint. Acesta va opri rularea testului atunci când o metodă de test postează o afirmație de eșec.
Rulați testul și acesta ar trebui să se oprească la linia XCTAssertEqual
cu un eșec al testului.
Inspectați sut
și guess
în consola de depanare:
guess
este targetValue - 5
dar scoreRound
este 105, nu 95!
Pentru a investiga mai departe, folosiți procesul normal de depanare: Setați un punct de întrerupere la instrucțiunea when și, de asemenea, unul în BullsEyeGame.swift, în interiorul lui check(guess:)
, unde se creează difference
. Apoi rulați din nou testul și treceți peste instrucțiunea let difference
pentru a inspecta valoarea lui difference
în aplicație:
Problema este că difference
este negativ, deci scorul este 100 – (-5). Pentru a remedia acest lucru, ar trebui să folosiți valoarea absolută a lui difference
. În check(guess:)
, decomentați linia corectă și ștergeți-o pe cea incorectă.
Îndepărtați cele două puncte de întrerupere și rulați din nou testul pentru a confirma că acum reușește.
Utilizarea XCTestExpectation pentru a testa operațiile asincrone
Acum că ați învățat cum să testați modelele și să depanați eșecurile testelor, este timpul să treceți la testarea codului asincron.
Deschideți proiectul HalfTunes. Acesta folosește URLSession
pentru a interoga iTunes API și a descărca mostre de melodii. Să presupunem că doriți să îl modificați pentru a utiliza AlamoFire pentru operațiunile de rețea. Pentru a vedea dacă se strică ceva, ar trebui să scrieți teste pentru operațiile de rețea și să le rulați înainte și după ce modificați codul.
URLSession
Metodele sunt asincrone: ele se întorc imediat, dar nu termină de executat decât mai târziu. Pentru a testa metodele asincrone, folosiți XCTestExpectation
pentru a face ca testul dumneavoastră să aștepte finalizarea operației asincrone.
Testele asincrone sunt de obicei lente, așa că ar trebui să le păstrați separate de testele unitare mai rapide.
Creați o nouă țintă de test unitar numită HalfTunesSlowTests. Deschideți clasa HalfTunesSlowTests
și importați modulul aplicației HalfTunes chiar sub declarația import
existentă:
@testable import HalfTunes
Toate testele din această clasă utilizează modulul implicit URLSession
pentru a trimite cereri către serverele Apple, deci declarați un obiect sut
, creați-l în setUp()
și eliberați-l în tearDown()
.
Înlocuiți conținutul clasei HalfTunesSlowTests
cu:
var sut: URLSession!override func setUp() { super.setUp() sut = URLSession(configuration: .default)}override func tearDown() { sut = nil super.tearDown()}
În continuare, adăugați acest test asincron:
// 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)}
Acest test verifică dacă trimiterea unei interogări valide către iTunes returnează un cod de stare 200. Cea mai mare parte a codului este același cu cel pe care l-ați scrie în aplicație, cu aceste linii suplimentare:
- expectation(description:): Returnează un obiect
XCTestExpectation
, stocat înpromise
. Parametruldescription
descrie ceea ce vă așteptați să se întâmple. - promise.fulfill(): Apelați acest lucru în închiderea condiției de succes a gestionarului de finalizare a metodei asincrone pentru a semnala faptul că așteptarea a fost îndeplinită.
- wait(for:timeout:): Menține testul în execuție până când toate așteptările sunt îndeplinite sau până când se termină intervalul
timeout
, în funcție de ce se întâmplă mai întâi.
Execută testul. Dacă sunteți conectat la internet, testul ar trebui să dureze aproximativ o secundă pentru a reuși după ce aplicația se încarcă în simulator.
Eșec rapid
Eșecul doare, dar nu trebuie să dureze o veșnicie.
Pentru a experimenta eșecul, pur și simplu ștergeți „s” din „itunes” în URL:
let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")
Rulați testul. Acesta eșuează, dar durează tot intervalul de timp de așteptare! Acest lucru se datorează faptului că ați presupus că solicitarea va reuși întotdeauna, și de aici ați apelat la promise.fulfill()
. Din moment ce cererea a eșuat, s-a terminat doar când a expirat intervalul de timp de așteptare.
Puteți îmbunătăți acest lucru și puteți face ca testul să eșueze mai repede schimbând presupunerea: În loc să așteptați ca solicitarea să reușească, așteptați doar până când este invocat gestionarul de finalizare al metodei asincrone. Acest lucru se întâmplă imediat ce aplicația primește un răspuns – fie OK, fie eroare – de la server, care îndeplinește așteptarea. Testul dumneavoastră poate verifica apoi dacă solicitarea a reușit.
Pentru a vedea cum funcționează acest lucru, creați un nou test.
Dar mai întâi, reparați testul anterior, anulând modificarea pe care ați făcut-o la url
.
Apoi, adăugați următorul test la clasa dvs.:
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)}
Diferența cheie este că simpla introducere a gestionarului de finalizare îndeplinește așteptarea, iar acest lucru durează doar aproximativ o secundă pentru a se întâmpla. Dacă cererea eșuează, afirmațiile then
eșuează.
Executați testul. Acum ar trebui să dureze aproximativ o secundă pentru a eșua. Acesta eșuează pentru că cererea a eșuat, nu pentru că execuția testului a depășit timeout
.
Reparați url
, apoi rulați din nou testul pentru a confirma că acum reușește.
Facerea de obiecte și interacțiuni
Testele asincrone vă dau încredere că codul dvs. generează o intrare corectă la o API asincronă. De asemenea, este posibil să doriți să testați dacă codul dvs. funcționează corect atunci când primește intrare de la un URLSession
, sau dacă actualizează corect baza de date cu valorile implicite ale utilizatorului sau un container iCloud.
Majoritatea aplicațiilor interacționează cu obiecte de sistem sau de bibliotecă – obiecte pe care nu le controlați – iar testele care interacționează cu aceste obiecte pot fi lente și irepetabile, încălcând două dintre principiile FIRST. În schimb, puteți falsifica interacțiunile prin obținerea de intrări de la stubs sau prin actualizarea obiectelor mock.
Implementați falsificarea atunci când codul dvs. are o dependență de un obiect de sistem sau de bibliotecă. Puteți face acest lucru creând un obiect fals care să joace acel rol și injectând acest fals în codul dumneavoastră. Dependency Injection de Jon Reid descrie mai multe moduri de a face acest lucru.
Fake Input From Stub
În acest test, veți verifica dacă updateSearchResults(_:)
din aplicație analizează corect datele descărcate de sesiune, verificând că searchResults.count
este corect. SUT este controlerul de vizualizare, iar dvs. veți falsifica sesiunea cu stub-uri și câteva date descărcate în prealabil.
Accesați navigatorul Test și adăugați o nouă țintă de test unitar. Numiți-l HalfTunesFakeTests. Deschideți HalfTunesFakeTests.swift și importați modulul aplicației HalfTunes chiar sub declarația import
:
@testable import HalfTunes
Acum, înlocuiți conținutul clasei HalfTunesFakeTests
cu următorul:
var sut: SearchViewController!override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? SearchViewController}override func tearDown() { sut = nil super.tearDown()}
Aceasta declară SUT, care este un SearchViewController
, îl creează în setUp()
și îl eliberează în tearDown()
:
În continuare, veți avea nevoie de câteva mostre de date JSON pe care sesiunea falsă le va furniza testului dumneavoastră. Doar câteva elemente vor fi suficiente, așa că pentru a limita rezultatele descărcării în iTunes adăugați &limit=3
la șirul URL:
https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3
Copiați acest URL și lipiți-l într-un browser. Acest lucru descarcă un fișier numit 1.txt, 1.txt.js sau similar. Previzualizați-l pentru a confirma că este un fișier JSON, apoi redenumiți-l abbaData.json.
Acum, reveniți la Xcode și mergeți la navigatorul Project. Adăugați fișierul în grupul HalfTunesFakeTests.
Proiectul HalfTunes conține fișierul suport DHURLSessionMock.swift. Acesta definește un protocol simplu numit DHURLSession
, cu metode (stubs) pentru a crea o sarcină de date fie cu un URL
, fie cu un URLRequest
. De asemenea, definește URLSessionMock
, care se conformează acestui protocol cu inițializatori care vă permit să creați un obiect simulat URLSession
cu datele, răspunsul și eroarea la alegere.
Pentru a configura simularea, mergeți la HalfTunesFakeTests.swift și adăugați următoarele în setUp()
, după instrucțiunea care creează SUT:
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
Aceasta setează datele și răspunsul false și creează obiectul de sesiune fals. În cele din urmă, la sfârșit, injectează sesiunea falsă în aplicație ca o proprietate a sut
.
Acum, sunteți gata să scrieți testul care verifică dacă apelarea updateSearchResults(_:)
analizează datele false. Adăugați următorul test:
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")}
Încă trebuie să scrieți acest test ca test asincron deoarece stub-ul pretinde a fi o metodă asincronă.
Aserțiunea when este că searchResults
este goală înainte ca task-ul de date să ruleze. Acest lucru ar trebui să fie adevărat, deoarece ați creat un SUT complet nou în setUp()
.
Datele false conțin JSON pentru trei obiecte Track
, astfel încât afirmația then este că array-ul searchResults
al controlerului de vizualizare conține trei elemente.
Executați testul. Ar trebui să reușească destul de repede pentru că nu există o conexiune reală la rețea!
Fake Update to Mock Object
Testul anterior a folosit un stub pentru a furniza date de intrare de la un obiect fals. În continuare, veți folosi un obiect fals pentru a testa dacă codul dvs. actualizează corect UserDefaults
.
Refaceți proiectul BullsEye. Aplicația are două stiluri de joc: Utilizatorul fie mută cursorul pentru a se potrivi cu valoarea țintă, fie ghicește valoarea țintă din poziția cursorului. Un control segmentat din colțul din dreapta jos comută stilul de joc și îl salvează în setările implicite ale utilizatorului.
Următorul test va verifica dacă aplicația salvează corect proprietatea gameStyle
.
În navigatorul Test, faceți clic pe New Unit Test Class și numiți-o BullsEyeMockTests. Adăugați următoarele sub instrucțiunea import
:
@testable import BullsEyeclass MockUserDefaults: UserDefaults { var gameStyleChanged = 0 override func set(_ value: Int, forKey defaultName: String) { if defaultName == "gameStyle" { gameStyleChanged += 1 } }}
MockUserDefaults
suprascrie set(_:forKey:)
pentru a incrementa steagul gameStyleChanged
. Adesea, veți vedea teste similare care setează o variabilă Bool
, dar incrementarea unui Int
vă oferă mai multă flexibilitate – de exemplu, testul dumneavoastră ar putea verifica dacă metoda este apelată o singură dată.
Declarați SUT și obiectul simulat în BullsEyeMockTests
:
var sut: ViewController!var mockUserDefaults: MockUserDefaults!
În continuare, înlocuiți valorile implicite setUp()
și tearDown()
cu următoarele:
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()}
Aceasta creează SUT și obiectul simulat și injectează obiectul simulat ca o proprietate a SUT.
Înlocuiți acum cele două metode de testare implicite din șablon cu acest lucru:
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")}
Aserțiunea „when” este că steagul gameStyleChanged
este 0 înainte ca metoda de testare să modifice controlul segmentat. Deci, dacă aserțiunea atunci este, de asemenea, adevărată, înseamnă că set(_:forKey:)
a fost apelată exact o singură dată.
Executați testul; ar trebui să reușească.
Testarea UI în Xcode
Testarea UI vă permite să testați interacțiunile cu interfața cu utilizatorul. Testarea UI funcționează prin găsirea obiectelor UI ale unei aplicații cu ajutorul interogărilor, prin sintetizarea evenimentelor și apoi prin trimiterea evenimentelor către aceste obiecte. API-ul vă permite să examinați proprietățile și starea unui obiect UI pentru a le compara cu starea așteptată.
În navigatorul Test al proiectului BullsEye, adăugați o nouă țintă UI Test Target. Verificați că ținta de testat este BullsEye, apoi acceptați numele implicit BullsEyeUITests.
Deschideți BullsEyeUITests.swift și adăugați această proprietate în partea de sus a clasei BullsEyeUITests
:
var app: XCUIApplication!
În setUp()
, înlocuiți declarația XCUIApplication().launch()
cu următoarea:
app = XCUIApplication()app.launch()
Schimbați numele lui testExample()
în testGameStyleSwitch()
.
Deschideți o nouă linie în testGameStyleSwitch()
și faceți clic pe butonul roșu Record (Înregistrare) din partea de jos a ferestrei editorului:
Aceasta deschide aplicația în simulator într-un mod care înregistrează interacțiunile dumneavoastră ca și comenzi de test. După ce aplicația se încarcă, atingeți segmentul Slide al comutatorului de stil de joc și eticheta de sus. Apoi, faceți clic pe butonul Xcode Record pentru a opri înregistrarea.
Acum aveți următoarele trei linii în testGameStyleSwitch()
:
let app = XCUIApplication()app.buttons.tap()app.staticTexts.tap()
Înregistratorul a creat cod pentru a testa aceleași acțiuni pe care le-ați testat în aplicație. Trimiteți o atingere la cursorul și la etichetă. Le veți folosi ca bază pentru a vă crea propriul test UI.
Dacă vedeți alte declarații, ștergeți-le.
Prima linie dublează proprietatea pe care ați creat-o în setUp()
, așa că ștergeți această linie. Nu trebuie să atingeți nimic încă, așa că ștergeți și .tap()
de la sfârșitul liniilor 2 și 3. Acum, deschideți micul meniu de lângă și selectați
segmentedControls.buttons
.
Ceea ce vă rămâne ar trebui să fie următoarele:
app.segmentedControls.buttonsapp.staticTexts
Atingeți orice alte obiecte pentru a permite înregistratorului să vă ajute să găsiți codul pe care îl puteți accesa în testele dumneavoastră. Acum, înlocuiți aceste linii cu acest cod pentru a crea o anumită secțiune:
// givenlet slideButton = app.segmentedControls.buttonslet typeButton = app.segmentedControls.buttonslet slideLabel = app.staticTextslet typeLabel = app.staticTexts
Acum că aveți nume pentru cele două butoane din controlul segmentat și cele două posibile etichete de sus, adăugați următorul cod de mai jos:
// 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)}
Acest cod verifică dacă există eticheta corectă atunci când tap()
pe fiecare buton din controlul segmentat. Rulați testul – toate afirmațiile ar trebui să reușească.
Testarea performanței
Din documentația Apple: Un test de performanță ia un bloc de cod pe care doriți să îl evaluați și îl execută de zece ori, colectând timpul mediu de execuție și deviația standard pentru execuții. Media acestor măsurători individuale formează o valoare pentru execuția testului care poate fi apoi comparată cu o bază de referință pentru a evalua succesul sau eșecul.
Este foarte simplu să scrieți un test de performanță: Trebuie doar să plasați codul pe care doriți să-l măsurați în închiderea measure()
.
Pentru a vedea acest lucru în acțiune, redeschideți proiectul HalfTunes și, în HalfTunesFakeTests.swift, adăugați următorul test:
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) }}
Executați testul, apoi faceți clic pe pictograma care apare lângă începutul închiderii measure()
pentru a vedea statisticile.
Faceți clic pe Set Baseline pentru a seta un timp de referință. Apoi, rulați din nou testul de performanță și vizualizați rezultatul – acesta ar putea fi mai bun sau mai rău decât linia de referință. Butonul Edit (Editare) vă permite să resetați linia de bază la acest nou rezultat.
Liniile de bază sunt stocate pentru fiecare configurație de dispozitiv, astfel încât puteți avea același test care se execută pe mai multe dispozitive diferite, iar fiecare dintre ele să mențină o linie de bază diferită în funcție de viteza procesorului, memoria etc. din configurația specifică.
De fiecare dată când faceți modificări la o aplicație care ar putea avea un impact asupra performanței metodei testate, rulați din nou testul de performanță pentru a vedea cum se compară cu linia de bază.
Code Coverage
Instrumentul de acoperire a codului vă spune ce cod al aplicației este de fapt rulat de testele dumneavoastră, astfel încât să știți ce părți din codul aplicației nu sunt (încă) testate.
Pentru a activa acoperirea codului, editați acțiunea Test din schemă și bifați caseta de selectare Gather coverage for din fila Options:
Executați toate testele (Command-U), apoi deschideți navigatorul Report (Command-9). Selectați Coverage (Acoperire) sub elementul de sus din acea listă:
Click pe triunghiul de dezvăluire pentru a vedea lista de funcții și închideri din SearchViewController.swift:
Derulați în jos până la updateSearchResults(_:)
pentru a vedea că acoperirea este de 87,9%.
Click pe butonul săgeată pentru această funcție pentru a deschide fișierul sursă al funcției. Pe măsură ce treceți cu mouse-ul peste adnotările de acoperire din bara laterală din dreapta, secțiunile de cod se evidențiază cu verde sau roșu:
Anotațiile de acoperire arată de câte ori un test atinge fiecare secțiune de cod; secțiunile care nu au fost apelate sunt evidențiate cu roșu. După cum v-ați aștepta, bucla for a rulat de 3 ori, dar nimic din căile de eroare nu a fost executat.
Pentru a crește acoperirea acestei funcții, ați putea duplica abbaData.json, apoi să-l editați astfel încât să provoace diferite erori. De exemplu, schimbați "results"
în "result"
pentru un test care lovește print("Results key not found in dictionary")
.
Coperire 100%?
Cât de mult ar trebui să vă străduiți pentru o acoperire a codului de 100%? Căutați pe Google „100% acoperire a testelor unitare” și veți găsi o serie de argumente pro și contra acestui lucru, împreună cu dezbateri privind însăși definiția „100% acoperire”. Argumentele contra spun că ultimii 10-15% nu merită efortul. Argumentele pro spun că ultimul 10-15% este cel mai important, deoarece este foarte greu de testat. Căutați pe Google „hard to unit test bad design bad design” pentru a găsi argumente convingătoare că un cod imposibil de testat este un semn al unor probleme de proiectare mai profunde.
Unde să mergem de aici?
Acum aveți câteva instrumente grozave pe care să le folosiți în scrierea testelor pentru proiectele dumneavoastră. Sper că acest tutorial iOS Unit Testing and UI Testing v-a dat încrederea necesară pentru a testa toate lucrurile!
Puteți descărca versiunea finalizată a proiectului folosind butonul Download Materials din partea de sus sau de jos a acestui tutorial. Continuați să vă dezvoltați abilitățile adăugând teste suplimentare proprii.
Iată câteva resurse pentru studiu suplimentar:
- Există mai multe videoclipuri WWDC pe tema testării. Două dintre cele bune de la WWDC17 sunt: Engineering for Testability și Testing Tips & Tricks.
- Postul următor este automatizarea: Continuous Integration și Continuous Delivery. Începeți cu Automatizarea procesului de testare cu Xcode Server și
xcodebuild
de la Apple și cu articolul de livrare continuă de la Wikipedia, care se bazează pe expertiza de la ThoughtWorks. - Dacă aveți deja o aplicație, dar nu ați scris încă teste pentru ea, ar fi bine să consultați lucrarea Working Effectively with Legacy Code de Michael Feathers, deoarece codul fără teste este cod moștenit!
- Arhivele de exemple de aplicații Quality Coding ale lui Jon Reid sunt excelente pentru a învăța mai multe despre Test Driven Development.
raywenderlich.com Weekly
Buletinul raywenderlich.com este cel mai simplu mod de a fi la curent cu tot ceea ce trebuie să știți ca dezvoltator mobil.
Obțineți un rezumat săptămânal al tutorialelor și cursurilor noastre și primiți un curs gratuit de aprofundare prin e-mail ca bonus!
Evaluare medie
4.7/5
Adaugați o evaluare pentru acest conținut
Conectați-vă pentru a adăuga o evaluare
.