iOS:n yksikkötestauksen ja käyttöliittymän testauksen opetusohjelma

Päivityshuomautus: Michael Katz päivitti tämän opetusohjelman Xcode 10.1:lle, Swift 4.2:lle ja iOS 12:lle. Audrey Tam kirjoitti alkuperäisen.

Testien kirjoittaminen ei ole hohdokasta, mutta koska testit estävät säkenöivää sovellustasi muuttumasta bugien runtelemaksi romuksi, se on välttämätöntä. Jos luet tätä ohjetta, tiedät jo, että sinun pitäisi kirjoittaa testejä koodillesi ja käyttöliittymällesi, mutta et ehkä tiedä, miten.

Sinulla saattaa olla toimiva sovellus, mutta haluat testata muutoksia, joita olet tekemässä sovelluksen laajentamiseksi. Ehkä olet jo kirjoittanut testit, mutta et ole varma, ovatko ne oikeita testejä. Tai olet aloittanut uuden sovelluksen työstämisen ja haluat testata työn edetessä.

Tämä opetusohjelma näyttää sinulle:

  • Miten Xcoden Testinavigaattoria käytetään sovelluksen mallin ja asynkronisten metodien testaamiseen
  • Miten kirjasto- tai järjestelmäobjektien kanssa tapahtuvaa vuorovaikutusta lavastetaan käyttämällä stubeja ja mockeja
  • Miten käyttöliittymää ja suorituskykyä testataan
  • Miten koodin kattavuus-työkalua käytetään

Matkan varrella, omaksut osan testauksen ninjojen käyttämästä sanastosta.

Testattavien asioiden selvittäminen

Ennen testien kirjoittamista on tärkeää tuntea perusasiat. Mitä sinun täytyy testata?

Jos tavoitteenasi on laajentaa olemassa olevaa sovellusta, sinun tulisi ensin kirjoittaa testit kaikille komponenteille, joita aiot muuttaa.

Yleisesti testien tulisi kattaa:

  • Ydintoiminnallisuus: Malliluokat ja -metodit sekä niiden vuorovaikutus kontrollerin kanssa
  • Yleisimmät käyttöliittymän työnkulut
  • Rajatilanteet
  • Virheiden korjaukset

Testauksen parhaat käytännöt

Akronyymi FIRST kuvaa ytimekästä kriteeristöä tehokkaille yksikkötesteille. Nämä kriteerit ovat:

  • Nopea: Testien tulisi toimia nopeasti.
  • Riippumaton/eristetty: Testien ei tulisi jakaa tilaa keskenään.
  • Toistettava: Sinun tulisi saada samat tulokset joka kerta, kun suoritat testin. Ulkoiset datan tarjoajat tai samanaikaisuusongelmat voivat aiheuttaa ajoittaisia epäonnistumisia.
  • Itsevarmentava: Testien tulisi olla täysin automatisoituja. Tuloksen tulisi olla joko ”hyväksytty” tai ”hylätty”, eikä luottaa ohjelmoijan tulkintaan lokitiedostosta.
  • Oikea-aikainen: Ihannetapauksessa testit tulisi kirjoittaa ennen niiden testaaman tuotantokoodin kirjoittamista (Test-Driven Development).

FIRST-periaatteiden noudattaminen pitää testit selkeinä ja hyödyllisinä sen sijaan, että ne muuttuisivat sovelluksen tiesuluiksi.

Aloittaminen

Aloita lataamalla projektin materiaalit lataamalla Materiaalien lataus -painiketta, joka on tämän ohjeen ylä- tai alaosassa. Aloitusprojekteja on kaksi erillistä: BullsEye ja HalfTunes.

  • BullsEye perustuu iOS Apprenticen esimerkkisovellukseen. Pelilogiikka on BullsEyeGame-luokassa, jota testaat tämän tutoriaalin aikana.
  • HalfTunes on päivitetty versio URLSession tutoriaalin esimerkkisovelluksesta. Käyttäjät voivat kysyä iTunesin API:sta kappaleita ja ladata ja toistaa kappaleiden pätkiä.

Yksikkötestaus Xcodessa

Testausnavigaattori tarjoaa helpoimman tavan työskennellä testien kanssa; käytät sitä luodaksesi testikohteita ja suorittaaksesi testejä sovellusta vastaan.

Yksikkötestikohteen luominen

Avaa BullsEye-projekti ja paina Command-6 avataksesi Test-navigaattorin.

Klikkaa vasemmassa alakulmassa olevaa +-painiketta ja valitse valikosta Uusi yksikkötestikohde…:

Hyväksy oletusnimi, BullsEyeTests. Kun testinippu näkyy Testinavigaattorissa, avaa nippu editorissa napsauttamalla sitä. Jos nippu ei tule automaattisesti näkyviin, etsi vikaa napsauttamalla jotakin muuta navigaattoria ja palaa sitten Test-navigaattoriin.

Oletusmalli tuo testauskehyksen, XCTestin, ja määrittelee BullsEyeTests-alaluokan XCTestCase, jossa on setUp(), tearDown() ja esimerkkitestausmetodit.

Testien suorittamiseen on kolme tapaa:

  1. Tuote ▸ Testi tai Komento-U. Molemmat näistä suorittavat kaikki testiluokat.
  2. Klikkaa nuolinäppäintä Testinavigaattorissa.
  3. Klikkaa kourussa olevaa timanttipainiketta.

Voit suorittaa yksittäisen testimenetelmän myös napsauttamalla sen timanttia joko Testinavigaattorissa tai kourussa.

Kokeile eri tapoja suorittaa testejä, jotta saat käsityksen siitä, kuinka kauan se kestää ja miltä se näyttää. Esimerkkitestit eivät tee vielä mitään, joten ne toimivat todella nopeasti!

Kun kaikki testit onnistuvat, timantit muuttuvat vihreiksi ja näyttävät valintamerkkejä. Voit napsauttaa harmaata timanttia testPerformanceExample() lopussa avataksesi Suoritustuloksen:

Et tarvitse testPerformanceExample():tä tai testExample():aa tässä opetusohjelmassa, joten poista ne.

Käyttämällä XCTAssert:ia mallien testaamiseen

Ensiksi käytät XCTAssert:n funktioita testataksesi BullsEye:n mallin keskeistä toimintoa: Laskeeko BullsEyeGame-olio oikein kierroksen pisteet?

Lisää BullsEyeTests.swift-tiedostoon tämä rivi heti import-lauseen alapuolelle:

@testable import BullsEye

Tämä antaa yksikkötesteille pääsyn BullsEyen sisäisiin tyyppeihin ja funktioihin.

Lisää BullsEyeTests-luokan yläosaan tämä ominaisuus:

var sut: BullsEyeGame!

Tämä luo sijoituspaikan BullsEyeGame:lle, joka on testattava järjestelmä (System Under Test, SUT) eli objekti, jota tämä testitapausluokka koskee.

Seuraavaksi korvaa setup():n sisältö tällä:

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

Tämä luo BullsEyeGame-olion luokkatasolla, joten kaikki tämän testiluokan testit voivat käyttää SUT-olion ominaisuuksia ja metodeja.

Tässä kutsut myös pelin startNewGame():tä, joka alustaa targetValue. Monet testit käyttävät targetValue:ää testatakseen, että peli laskee pisteet oikein.

Ennen kuin unohdat, vapauta SUT-objekti tearDown():ssä. Korvaa sen sisältö seuraavasti:

sut = nilsuper.tearDown()
Huomautus: On hyvä käytäntö luoda SUT-olio setUp():ssä ja vapauttaa se tearDown():ssä, jotta jokainen testi alkaa puhtaalta pöydältä. Lisää keskustelua löydät Jon Reidin postauksesta aiheesta.

Ensimmäisen testin kirjoittaminen

Nyt olet valmis kirjoittamaan ensimmäisen testisi!

Lisää seuraava koodi BullsEyeTests loppuun:

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")}

Testausmetodin nimi alkaa aina testillä, jonka jälkeen tulee kuvaus siitä, mitä se testaa.

Testi kannattaa muotoilla annettuun, kun- ja sitten -osioihin:

  1. Annettu: Tässä asetetaan kaikki tarvittavat arvot. Tässä esimerkissä luot arvon guess, jotta voit määrittää, kuinka paljon se eroaa arvosta targetValue.
  2. When: Tässä osiossa suoritat testattavan koodin: Kutsu check(guess:).
  3. Sitten: Tämä on osio, jossa vakuutat odotetun tuloksen viestillä, joka tulostuu, jos testi epäonnistuu. Tässä tapauksessa sut.scoreRound:n pitäisi olla yhtä suuri kuin 95 (100 – 5).

Suorita testi napsauttamalla timanttikuvaketta kourussa tai Testinavigaattorissa. Tämä rakentaa ja ajaa sovelluksen, ja timanttikuvake muuttuu vihreäksi valintamerkiksi!

Huomautus: Jos haluat nähdä täydellisen luettelon XCTestAssertions-lausekkeista, siirry osoitteeseen Apple’s Assertions Listed by Category.

Testin virheenkorjaus

Testiin BullsEyeGame on tarkoituksella rakennettu vika, jonka etsimistä harjoittelet nyt. Nähdäksesi bugin toiminnassa, luot testin, joka vähentää targetValue:stä 5 kyseisessä kohdassa ja jättää kaiken muun ennalleen.

Lisää seuraava testi:

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")}

Ero guess:n ja targetValue:n välillä on edelleen 5, joten pistemäärän pitäisi edelleen olla 95.

Lisää Breakpoint-navigaattoriin Test Failure Breakpoint. Tämä pysäyttää testin suorituksen, kun testimenetelmä lähettää epäonnistumisväitteen.

Ajoita testi, ja sen pitäisi pysähtyä riville XCTAssertEqual testin epäonnistumisen vuoksi.

Tarkastele sut ja guess virheenkorjauskonsolissa:

guess on targetValue - 5, mutta scoreRound on 105, ei 95!”

Käytä normaalia virheenkorjausprosessia tutkiaksesi asiaa lisää: Aseta pysäytyspiste when-lausekkeeseen ja myös yksi pysäytyspiste BullsEyeGame.swift-tiedostossa, check(guess:):n sisällä, jossa se luo difference. Suorita sitten testi uudelleen ja astu let difference-lausekkeen yli tarkastellaksesi difference:n arvoa sovelluksessa:

Ongelma on, että difference on negatiivinen, joten tulos on 100 – (-5). Tämän korjaamiseksi kannattaa käyttää difference:n absoluuttista arvoa. Poista check(guess:)-kohdasta oikea rivi kommentoimatta ja poista väärä rivi.

Poista kaksi katkaisupistettä ja suorita testi uudelleen vahvistaaksesi, että se nyt onnistuu.

Käyttämällä XCTestExpectationia asynkronisten operaatioiden testaamiseen

Nyt kun olet oppinut testaamaan malleja ja debuggaamaan testien epäonnistumisia, on aika siirtyä testaamaan epäsynkronista koodia.

Ota HalfTunes-projekti. Se käyttää URLSession iTunesin API:n kyselyyn ja laulunäytteiden lataamiseen. Oletetaan, että haluat muokata sitä käyttämään AlamoFirea verkkotoimintoihin. Nähdäksesi, rikkoutuuko jokin, sinun kannattaa kirjoittaa testit verkko-operaatioille ja ajaa ne ennen ja jälkeen koodin muuttamisen.

URLSession Metodit ovat asynkronisia: Ne palaavat heti, mutta suorituksensa päättävät vasta myöhemmin. Kun haluat testata asynkronisia metodeja, käytät XCTestExpectation, jotta testisi odottaa asynkronisen operaation valmistumista.

Asynkroniset testit ovat yleensä hitaita, joten ne kannattaa pitää erillään nopeammista yksikkötesteistäsi.

Luo uusi yksikkötestikohde nimeltä HalfTunesSlowTests. Avaa HalfTunesSlowTests-luokka ja tuo HalfTunes-sovellusmoduuli heti olemassa olevan import-lausekkeen alapuolelle:

@testable import HalfTunes

Kaikki tämän luokan testit käyttävät oletusarvoisesti URLSession pyyntöjen lähettämiseen Applen palvelimille, joten ilmoita sut-olio, luo se setUp()-kohdassa setUp() ja vapauta se tearDown().

Korvaa HalfTunesSlowTests-luokan sisältö seuraavasti:

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

Lisää seuraavaksi tämä asynkroninen testi:

// 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)}

Tällä testillä tarkistetaan, että kelvollisen kyselyn lähettäminen iTunesiin palauttaa tilakoodin 200. Suurin osa koodista on samaa kuin mitä kirjoittaisit sovelluksessa, ja siihen lisätään nämä rivit:

  1. expectation(description:): Palauttaa XCTestExpectation objektin, joka on tallennettu promise. Parametri description kuvaa, mitä odotat tapahtuvan.
  2. promise.fulfill(): Kutsu tätä asynkronisen metodin valmistumisen käsittelijän onnistumisehdon sulkemisessa merkitäksesi, että odotus on täyttynyt.
  3. wait(for:timeout:): Pitää testin käynnissä, kunnes kaikki odotukset täyttyvät tai timeout aikaväli päättyy, riippuen siitä, kumpi tapahtuu ensin.

Aja testi. Jos olet yhteydessä internetiin, testin pitäisi onnistua noin sekunnin kuluttua siitä, kun sovellus on latautunut simulaattoriin.

Epäonnistuminen nopeasti

Epäonnistuminen sattuu, mutta sen ei tarvitse kestää ikuisesti.

Kokeaksesi epäonnistumista poista URL-osoitteesta ’s’ sanasta ”itunes”:

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

Ajoita testi. Se epäonnistuu, mutta kestää koko aikakatkaisuvälin! Tämä johtuu siitä, että oletit pyynnön aina onnistuvan, ja siksi kutsuit promise.fulfill(). Koska pyyntö epäonnistui, se päättyi vasta, kun aikakatkaisu umpeutui.

Voit parantaa tätä ja saada testin epäonnistumaan nopeammin muuttamalla oletusta: Sen sijaan, että odottaisit pyynnön onnistumista, odota vain siihen asti, että asynkronisen metodin valmistumisen käsittelijää kutsutaan. Tämä tapahtuu heti, kun sovellus saa palvelimelta vastauksen – joko OK tai virhe – joka täyttää odotuksen. Tämän jälkeen testisi voi tarkistaa, onnistuiko pyyntö.

Luo uusi testi, jotta näet, miten tämä toimii.

Mutta korjaa ensin edellinen testi peruuttamalla muutos, jonka teit kohtaan url.
Lisää sitten seuraava testi luokkaasi:

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)}

Keskeinen ero on se, että pelkkä loppuunsaattamisen käsittelijän syöttäminen täyttää odotuksen, ja tämä kestää vain noin sekunnin. Jos pyyntö epäonnistuu, then väitteet epäonnistuvat.

Suorita testi. Sen pitäisi nyt kestää noin sekunnin ennen epäonnistumista. Se epäonnistuu, koska pyyntö epäonnistui, ei siksi, että testin suoritus ylitti timeout.

Korjaa url ja suorita testi uudelleen varmistaaksesi, että se nyt onnistuu.

Objektien ja vuorovaikutusten väärentäminen

Asynkroniset testit antavat sinulle varmuuden siitä, että koodisi tuottaa oikean syötteen asynkroniselle sovellusrajapinnalle. Saatat myös haluta testata, että koodisi toimii oikein, kun se vastaanottaa syötteen URLSession:ltä, tai että se päivittää oikein käyttäjän oletustietokannan tai iCloud-säiliön.

Useimmat sovellukset ovat vuorovaikutuksessa järjestelmä- tai kirjasto-objektien – objektien, joita et hallitse – kanssa, ja testit, jotka ovat vuorovaikutuksessa näiden objektien kanssa, voivat olla hitaita ja toistamattomia, jolloin ne rikkovat kahta FIRST-periaatetta. Sen sijaan voit teeskennellä vuorovaikutusta saamalla syötteen stubeista tai päivittämällä mock-objekteja.

Käytä teeskentelyä, kun koodisi on riippuvainen järjestelmä- tai kirjasto-objektista. Voit tehdä tämän luomalla väärennetyn objektin, joka esittää kyseistä osaa, ja injektoimalla tämän väärennöksen koodiisi. Jon Reidin teoksessa Dependency Injection kuvataan useita tapoja tehdä tämä.

Fake Input From Stub

Tässä testissä tarkistat, että sovelluksen updateSearchResults(_:) jäsentää oikein istunnon lataamaa dataa tarkistamalla, että searchResults.count on oikein. SUT on näkymäohjain, ja teeskentelet istuntoa stubeilla ja joillakin valmiiksi ladatuilla tiedoilla.

Mene Test-navigaattoriin ja lisää uusi Unit Test Target. Nimeä se HalfTunesFakeTests. Avaa HalfTunesFakeTests.swift ja tuo HalfTunes-sovellusmoduuli heti import-lausekkeen alapuolella:

@testable import HalfTunes

Vaihda nyt HalfTunesFakeTests-luokan sisältö tähän:

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

Tässä julistetaan SUT, joka on SearchViewController, luodaan se setUp():ssä ja vapautetaan se tearDown():ssä:

Huomaa: SUT on näkymäohjain, koska HalfTunesilla on massiivinen näkymäohjainongelma – kaikki työ tehdään SearchViewControllerissa.swift. Verkkokoodin siirtäminen erilliseen moduuliin vähentäisi tätä ongelmaa ja myös helpottaisi testausta.

Seuraavaksi tarvitset esimerkin JSON-datasta, jonka valesessiosi antaa testillesi. Vain muutama kohde riittää, joten rajoittaaksesi lataustuloksia iTunesissa liitä URL-merkkijonoon &limit=3:

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

Kopioi tämä URL-osoite ja liitä se selaimeen. Tämä lataa tiedoston nimeltä 1.txt, 1.txt.js tai vastaava. Esikatsele sitä varmistaaksesi, että se on JSON-tiedosto, ja nimeä se sitten uudelleen nimellä abbaData.json.

Mene nyt takaisin Xcodeen ja siirry Project navigatoriin. Lisää tiedosto HalfTunesFakeTests-ryhmään.

HalfTunes-projekti sisältää tukitiedoston DHURLSessionMock.swift. Tämä määrittelee yksinkertaisen protokollan nimeltä DHURLSession, jossa on metodeja (stubs) datatehtävän luomiseksi joko URL:lla tai URLRequest:llä. Se määrittelee myös URLSessionMock:n, joka noudattaa tätä protokollaa alustajilla, joiden avulla voit luoda mock URLSession-olion, jossa on valitsemasi data, vastaus ja virhe.

Jotta voit asettaa väärennöksen, siirry HalfTunesFakeTests.swift-tiedostoon ja lisää setUp()-kohtaan setUp(), SUT:n luoneen lausekkeen jälkeen, seuraava:

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

Tämä asettaa väärennetyt tiedot ja vastauksen ja luo väärennetyn istuntoobjektin. Lopuksi lopussa se injektoi valesession sovellukseen sut:n ominaisuutena.

Nyt olet valmis kirjoittamaan testin, joka tarkistaa, parsitaanko kutsumalla updateSearchResults(_:) valedataa. Lisää seuraava testi:

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")}

Tämä pitää silti kirjoittaa asynkronisena testinä, koska tynkä teeskentelee olevansa asynkroninen metodi.

Väite when on, että searchResults on tyhjä ennen datatehtävän suorittamista. Tämän pitäisi olla totta, koska olet luonut täysin uuden SUT:n setUp().

Väärennetty data sisältää kolmen Track-objektin JSON:n, joten then-väittämä on, että näkymäohjaimen searchResults-matriisissa on kolme elementtiä.

Aja testi. Sen pitäisi onnistua melko nopeasti, koska todellista verkkoyhteyttä ei ole!

Väärennetty päivitys Mock-objektiin

Edellisessä testissä käytettiin tynkää antamaan syötettä väärennetystä objektista. Seuraavaksi käytät mock-objektia testataksesi, että koodisi päivittää oikein UserDefaults.

Avaa BullsEye-projekti uudelleen. Sovelluksessa on kaksi pelityyliä: Käyttäjä joko siirtää liukusäädintä vastaamaan tavoitearvoa tai arvaa tavoitearvon liukusäätimen asennosta. Oikeassa alakulmassa oleva segmentoitu ohjain vaihtaa pelityylin ja tallentaa sen käyttäjän oletusasetuksiin.

Seuraavalla testillä tarkistat, että sovellus tallentaa gameStyle-ominaisuuden oikein.

Napsauta Testinavigaattorissa New Unit Test Class (Uusi yksikkötestiluokka) -painiketta ja nimeä se BullsEyeMockTests. Lisää import-lauseen alle seuraava:

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

MockUserDefaults ohittaa set(_:forKey:) lisäämällä gameStyleChanged-lippua. Usein näet vastaavia testejä, jotka asettavat Bool-muuttujan, mutta Int:n inkrementointi antaa sinulle enemmän joustavuutta – testisi voisi esimerkiksi tarkistaa, että metodia kutsutaan vain kerran.

Ilmoita SUT ja mock-olio BullsEyeMockTests:

var sut: ViewController!var mockUserDefaults: MockUserDefaults!

Seuraavaksi korvaa oletusarvot setUp() ja tearDown() tällä:

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()}

Tämä luo SUT:n ja mock-olion ja injektoi mock-olion SUT:n ominaisuudeksi.

Korvaa nyt mallin kaksi oletusarvoista testimenetelmää tällä:

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")}

Väite when on, että gameStyleChanged-lippu on 0 ennen kuin testimenetelmä muuttaa segmentoitua ohjausta. Jos siis myös then-väite on tosi, se tarkoittaa, että set(_:forKey:) kutsuttiin tasan kerran.

Aja testi; sen pitäisi onnistua.

UI-testaus Xcodessa

UI-testin avulla voit testata vuorovaikutusta käyttöliittymän kanssa. Käyttöliittymätestaus toimii etsimällä sovelluksen käyttöliittymäobjekteja kyselyillä, syntetisoimalla tapahtumia ja lähettämällä sitten tapahtumat näille objekteille. API:n avulla voit tutkia käyttöliittymäobjektin ominaisuuksia ja tilaa verrataksesi niitä odotettuun tilaan.

Lisää BullsEye-projektin Test-navigaattoriin uusi UI-testauksen kohde. Tarkista, että testattava kohde on BullsEye, ja hyväksy sitten oletusnimi BullsEyeUITests.

Avaa BullsEyeUITests.swift ja lisää tämä ominaisuus BullsEyeUITests-luokan yläosaan:

var app: XCUIApplication!

Kohdassa setUp() korvaa lauseke XCUIApplication().launch() seuraavalla:

app = XCUIApplication()app.launch()

Vaihda testExample():n nimi testGameStyleSwitch():ksi.

Avaa uusi rivi kohtaan testGameStyleSwitch() ja napsauta editori-ikkunan alareunassa olevaa punaista Tallenna-painiketta:

Tällöin sovellus avautuu simulaattorissa tilaan, jossa vuorovaikutuksesi tallentuu testikomentoina. Kun sovellus on latautunut, napauta pelityylikytkimen Slide-segmenttiä ja ylätunnistetta. Lopeta sitten tallennus napsauttamalla Xcoden Record-painiketta.

Sinulla on nyt seuraavat kolme riviä testGameStyleSwitch():

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

Tallennin on luonut koodia, jolla testataan samoja toimintoja, joita testasit sovelluksessa. Lähetä napautus liukusäätimelle ja tarralle. Käytät niitä pohjana luodessasi oman UI-testisi.
Jos näet muita lausekkeita, poista ne.

Ensimmäinen rivi kopioi kohdassa setUp() luomasi ominaisuuden, joten poista tämä rivi. Sinun ei tarvitse napauttaa vielä mitään, joten poista myös .tap() rivien 2 ja 3 lopusta. Avaa nyt :n vieressä oleva pieni valikko ja valitse segmentedControls.buttons.

Mitä sinulle jää jäljelle, pitäisi olla seuraava:

app.segmentedControls.buttonsapp.staticTexts

Napauta muita objekteja, jotta tallennin auttaa sinua löytämään koodin, jota voit käyttää testeissäsi. Korvaa nyt nämä rivit tällä koodilla luodaksesi kyseisen osion:

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

Nyt kun sinulla on nimet segmentoidun ohjauksen kahdelle painikkeelle ja kahdelle mahdolliselle ylätunnisteelle, lisää alla oleva koodi:

// 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)}

Tällä tarkistetaan, onko oikea tunniste olemassa, kun tap() painat kutakin painiketta segmentoidussa ohjauksessa. Suorita testi – kaikkien väitteiden pitäisi onnistua.

suorituskyvyn testaus

Applen dokumentaatiosta: Suorituskykytesti ottaa koodilohkon, jonka haluat arvioida, ja ajaa sen kymmenen kertaa keräten ajojen keskimääräisen suoritusajan ja keskihajonnan. Näiden yksittäisten mittausten keskiarvo muodostaa testiajon arvon, jota voidaan sitten verrata perustasoon onnistumisen tai epäonnistumisen arvioimiseksi.

Suorituskykytestin kirjoittaminen on hyvin yksinkertaista: Asetat vain koodin, jota haluat mitata, measure():n sulkemiseen.

Voidaksesi nähdä tämän toiminnassa, avaa HalfTunes-projekti uudelleen ja kohdassa HalfTunesFakeTests.swift, lisää seuraava testi:

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) }}

Suorita testi ja napsauta sitten kuvaketta, joka tulee näkyviin measure() perässä olevan sulkutunnisteen alun viereen nähdäksesi tilastot.

Klikkaa Set Baseline (Aseta lähtötaso) asettaaksesi vertailuaika. Suorita sitten suorituskykytesti uudelleen ja tarkastele tulosta – se saattaa olla parempi tai huonompi kuin perustaso. Muokkaa-painikkeella voit nollata perustason tähän uuteen tulokseen.

Tasotiedot tallennetaan laitekonfiguraatiokohtaisesti, joten voit suorittaa saman testin useilla eri laitteilla, ja jokainen laite voi ylläpitää erilaista perustasoa, joka riippuu tietyn konfiguraation prosessorin nopeudesta, muistista jne.

Aina kun teet sovellukseen muutoksia, jotka saattavat vaikuttaa testattavan menetelmän suorituskykyyn, suorita suorituskykytesti uudelleen nähdäksesi, miten se vertautuu perustasoon.

Koodin kattavuus

Koodin kattavuus -työkalu kertoo, mitä sovelluksen koodia testit todella suorittavat, joten tiedät, mitä osia sovelluksen koodista ei (vielä) testata.

Koodin kattavuuden ottamiseksi käyttöön muokkaa skeeman Test-toimintoa ja merkitse valintaruutu Gather coverage for (Kerää kattavuus) Options (Asetukset) -välilehdellä:

Suorita kaikki testit (Komento-U) ja avaa sitten Report navigator (Komento-9). Valitse Coverage tuon luettelon ylimmän kohteen alta:

Klikkaa paljastuskolmiota nähdäksesi SearchViewController.swift-tiedoston funktioiden ja sulkemisten luettelon:

Rullaa alaspäin kohtaan updateSearchResults(_:) nähdäksesi, että kattavuus on 87,9 %.

Klikkaa tätä funktiota osoittavaa nuolipainiketta avataksesi funktiota koskevan lähdetiedoston. Kun viet hiiren oikeassa sivupalkissa olevien kattavuusmerkintöjen päälle, koodin osat korostuvat vihreällä tai punaisella:

Kattavuusmerkinnät osoittavat, kuinka monta kertaa testi osuu kuhunkin koodin osaan; osat, joita ei kutsuttu, on korostettu punaisella. Kuten arvata saattaa, for-silmukka suoritettiin kolme kertaa, mutta mitään virhepoluissa ei suoritettu.

Tämän funktion kattavuuden lisäämiseksi voisit kopioida abbaData.json-tiedoston ja muokata sitä niin, että se aiheuttaa eri virheitä. Vaihda esimerkiksi "results" "result":ksi testissä, joka osuu print("Results key not found in dictionary").

100 % kattavuus?

Miten kovasti pitäisi pyrkiä 100 % koodin kattavuuteen? Googlaa ”100% unit test coverage” ja löydät erilaisia argumentteja puolesta ja vastaan, sekä keskustelua itse ”100% kattavuuden” määritelmästä. Argumentit sitä vastaan sanovat, että viimeiset 10-15% eivät ole vaivan arvoisia. Puolesta sanotaan, että viimeiset 10-15 prosenttia on kaikkein tärkeimpiä, koska niitä on niin vaikea testata. Googlaa ”hard to unit test bad design” löytääksesi vakuuttavia argumentteja siitä, että testaamaton koodi on merkki syvemmistä suunnitteluongelmista.

Where to Go From Here?

Sinulla on nyt joitain loistavia työkaluja, joita voit käyttää kirjoittaessasi testejä projekteihisi. Toivottavasti tämä iOS:n yksikkötestauksen ja käyttöliittymän testauksen opetusohjelma on antanut sinulle itseluottamusta testata kaikkea!

Voit ladata projektin valmiin version tämän opetusohjelman ylä- tai alaosassa olevasta Lataa materiaalit -painikkeesta. Jatka taitojesi kehittämistä lisäämällä lisää omia testejäsi.

Tässä on joitain resursseja jatko-opiskelua varten:

  • Testauksesta on useita WWDC-videoita. Kaksi hyvää niistä WWDC17:stä ovat: Engineering for Testability ja Testing Tips & Tricks.
  • Seuraava askel on automaatio: Continuous Integration ja Continuous Delivery. Aloita Applen kirjasta Automating the Test Process with Xcode Server ja xcodebuild sekä Wikipedian artikkelista Continuous Delivery, jossa hyödynnetään ThoughtWorksin asiantuntemusta.
  • Jos sinulla on jo sovellus, mutta et ole vielä kirjoittanut sille testejä, kannattaa tutustua Michael Feathersin Working Effectively with Legacy Code -teokseen, sillä koodi, jossa ei ole testejä, on vanhentunutta koodia.
  • Jon Reid’s Quality Coding -sarjan esimerkkisovellusarkistoista voi oppia lisää testivetoisesta kehitystyöstä.

raywenderlich.com Weekly

Raywenderlich.comin uutiskirje on helpoin tapa pysyä ajan tasalla kaikesta, mitä sinun pitää tietää mobiilikehittäjänä.

Saa viikoittainen kooste opetusohjelmistamme ja kursseistamme ja saat bonuksena ilmaisen syventävän sähköpostikurssin!

Keskimääräinen arvostelu

4.7/5

Lisää arvostelu tälle sisällölle

Kirjaudu sisään lisätäksesi arvostelun

90 arvostelua

Vastaa

Sähköpostiosoitettasi ei julkaista.