iOS Unit Testing és UI Testing Tutorial

Frissítés: Michael Katz frissítette ezt a bemutatót az Xcode 10.1, a Swift 4.2 és az iOS 12 számára. Az eredetit Audrey Tam írta.

A tesztek írása nem elbűvölő, de mivel a tesztek megakadályozzák, hogy a csillogó alkalmazásod hibákkal teli szemétté váljon, szükség van rá. Ha ezt a bemutatót olvasod, akkor már tudod, hogy teszteket kell írnod a kódodhoz és a felhasználói felületedhez, de lehet, hogy nem tudod, hogyan.

Lehet, hogy van egy működő alkalmazásod, de tesztelni szeretnéd az alkalmazás bővítését célzó változtatásokat. Lehet, hogy már írt teszteket, de nem biztos benne, hogy azok a megfelelő tesztek. Vagy elkezdett dolgozni egy új alkalmazáson, és menet közben szeretne tesztelni.

Ez a bemutató megmutatja:

  • Hogyan használja az Xcode tesztnavigátorát az alkalmazás modelljének és aszinkron metódusainak tesztelésére
  • Hogyan hamisítson interakciókat könyvtári vagy rendszerobjektumokkal stubok és mockok használatával
  • Hogyan tesztelje az UI-t és a teljesítményt
  • Hogyan használja a kódlefedettségi eszközt

Mégis, elsajátíthatsz néhányat a tesztelési nindzsák által használt szókincsből.

Kitaláljuk, mit kell tesztelni

A tesztek megírása előtt fontos, hogy ismerjük az alapokat. Mit kell tesztelned?

Ha egy meglévő alkalmazás bővítése a célod, akkor először minden olyan komponenshez kell teszteket írnod, amelyet meg akarsz változtatni.

A teszteknek általában a következőkre kell kiterjedniük:

  • Az alapfunkciókra: Modellosztályok és metódusok, valamint a vezérlővel való kölcsönhatásaik
  • A leggyakoribb felhasználói felület munkafolyamatai
  • Határfeltételek
  • Hibajavítások

A tesztelés legjobb gyakorlatai

A FIRST rövidítés a hatékony egységtesztek tömör kritériumrendszerét írja le. Ezek a kritériumok a következők:

  • Gyors: A teszteknek gyorsan kell lefutniuk.
  • Független/elszigetelt: A teszteknek nem szabad megosztaniuk egymással az állapotukat.
  • Ismételhető: A tesztek futtatásakor mindig ugyanazt az eredményt kell kapni. Külső adatszolgáltatók vagy párhuzamossági problémák okozhatnak időszakos hibákat.
  • Önérvényesítő: A teszteknek teljesen automatizáltnak kell lenniük. A kimenetnek vagy “megfelelt” vagy “nem felelt meg” kell lennie, ahelyett, hogy egy naplófájl programozói értelmezésére támaszkodna.
  • Időszerű: Ideális esetben a teszteket még azelőtt meg kell írni, hogy megírnád a tesztelt termelési kódot (tesztvezérelt fejlesztés).

A FIRST alapelvek betartásával a tesztek áttekinthetőek és hasznosak lesznek, ahelyett, hogy az alkalmazásod útakadályává válnának.

Kezdés

Kezdd a projekt anyagainak letöltésével a tananyag tetején vagy alján található Download Materials gomb segítségével. Két különálló indító projekt van: BullsEye és HalfTunes.

  • A BullsEye az iOS Apprentice egyik mintaalkalmazásán alapul. A játék logikája a BullsEyeGame osztályban található, amelyet a bemutató során tesztelni fogsz.
  • A HalfTunes az URLSession Tutorialban található mintaalkalmazás frissített változata. A felhasználók lekérdezhetik az iTunes API-t dalok után, majd dalrészleteket tölthetnek le és játszhatnak le.

Unitesztelés az Xcode-ban

A Teszt navigátor a legegyszerűbb módját biztosítja a tesztekkel való munkának; ezt fogja használni tesztcélpontok létrehozására és tesztek futtatására az alkalmazással szemben.

Egy egységteszt-célpont létrehozása

Nyissa meg a BullsEye projektet, és nyomja meg a Command-6 billentyűt a Test navigátor megnyitásához.

Kattintson a bal alsó sarokban lévő + gombra, majd válassza a menüből a New Unit Test Target… parancsot:

Az alapértelmezett nevet, a BullsEyeTests-et fogadja el. Amikor a tesztköteg megjelenik a Teszt navigátorban, kattintson rá a köteg megnyitásához a szerkesztőben. Ha a köteg nem jelenik meg automatikusan, keressen hibát valamelyik másik navigátorra kattintva, majd térjen vissza a Teszt navigátorhoz.

Az alapértelmezett sablon importálja a tesztelési keretrendszert, az XCTestet, és definiálja a XCTestCase BullsEyeTests alosztályát, setUp(), tearDown() és példa tesztmódszerekkel.

A tesztek futtatásának három módja van:

  1. Termék ▸ teszt vagy parancs-U. Mindkettő az összes tesztosztályt futtatja.
  2. Kattintson a Teszt navigátorban a nyíl gombra.
  3. Kattintson a gyémánt gombra az ereszben.

Az egyes tesztmódszereket is futtathatja, ha annak gyémántjára kattint a Teszt navigátorban vagy az ereszben.

Kipróbálhatja a tesztek futtatásának különböző módjait, hogy megérezze, mennyi ideig tart és hogyan néz ki. A mintatesztek még nem csinálnak semmit, ezért nagyon gyorsan futnak!

Ha minden teszt sikeres, a gyémántok zöldre változnak és pipa jeleket mutatnak. A testPerformanceExample() végén lévő szürke gyémántra kattintva megnyithatja a Teljesítményeredményt:

A testPerformanceExample() és a testExample() nem szükséges ehhez a bemutatóhoz, ezért törölje őket.

Az XCTAssert használata modellek tesztelésére

Először a XCTAssert függvényeket fogja használni a BullsEye modelljének egyik központi funkciójának tesztelésére:

A BullsEyeTests.swift állományban közvetlenül a import utasítás alá illesszük be ezt a sort:

@testable import BullsEye

Ezáltal az egységtesztek hozzáférnek a BullsEye belső típusaihoz és függvényeihez.

A BullsEyeTests osztály tetején adjuk hozzá ezt a tulajdonságot:

var sut: BullsEyeGame!

Ez egy BullsEyeGame helyőrzőt hoz létre, amely a tesztelt rendszer (SUT), vagyis az objektum, amelynek tesztelésével ez a teszteset-osztály foglalkozik.

Ezután a setup() tartalmát cseréljük ki erre:

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

Ez létrehoz egy BullsEyeGame objektumot az osztály szintjén, így az összes teszt ebben a tesztosztályban hozzáférhet a SUT objektum tulajdonságaihoz és metódusaihoz.

Ezzel meghívjuk a játék startNewGame()-jét is, amely inicializálja a targetValue-et. Sok teszt fogja használni a targetValue-t annak tesztelésére, hogy a játék helyesen számolja-e ki a pontszámot.

Mielőtt elfelejtenéd, a tearDown()-ben szabadítsd fel a SUT objektumot. Cserélje ki a tartalmát:

sut = nilsuper.tearDown()
Megjegyzés: Jó gyakorlat a SUT objektum setUp()-ben történő létrehozása és tearDown()-ben történő feloldása, hogy minden teszt tiszta lappal induljon. További megbeszélésekért nézze meg Jon Reid bejegyzését a témában.

Az első teszt megírása

Most, készen áll az első teszt megírására!

Add hozzá a következő kódot a BullsEyeTests végéhez:

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

A tesztmódszer neve mindig a test szóval kezdődik, majd a tesztelt dolog leírása következik.

A jó gyakorlat szerint a tesztet given, when és then szakaszokra kell formázni:

  1. Given: Itt állítsuk be a szükséges értékeket. Ebben a példában egy guess értéket hozol létre, így megadhatod, hogy mennyire tér el a targetValue értéktől.
  2. When: Ebben a szakaszban hajtod végre a tesztelendő kódot: Hívja check(guess:).
  3. Akkor: Ez az a szakasz, ahol a várt eredményt állítod be egy üzenettel, amely kiíródik, ha a teszt sikertelen. Ebben az esetben a sut.scoreRound értékének 95-nek (100 – 5) kell lennie.

Futtassa a tesztet a gyémánt ikonra kattintva az ereszben vagy a Teszt navigátorban. Ezáltal az alkalmazás elkészül és lefut, és a gyémánt ikon zöld pipa lesz!

Megjegyzés: Az XCTestAssertions teljes listáját az Apple Assertions Listed by Category (Az állítások kategóriák szerint listázva) című oldalára kattintva tekintheti meg.

Debugging a Test

A BullsEyeGame-ben szándékosan beépített hiba van, amelynek megtalálását most gyakorolni fogja. Hogy a hibát a gyakorlatban is láthasd, létrehozol egy olyan tesztet, amely a targetValue-ből kivonja az 5-öt az adott szakaszban, és minden mást változatlanul hagy.

Add hozzá a következő tesztet:

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

A guess és targetValue közötti különbség továbbra is 5, így a pontszámnak továbbra is 95-nek kell lennie.

A Töréspont navigátorban adjon hozzá egy Teszt hiba töréspontot. Ez megállítja a teszt futtatását, amikor egy tesztmódszer hibajelentést küld.

Futtassa a tesztet, és a XCTAssertEqual sorban meg kell állnia a teszt hibájával.

Vizsgálja meg a sut és guess értékeket a hibakeresési konzolon:

guess az targetValue - 5, de scoreRound 105, nem 95!

A további vizsgálathoz használja a normál hibakeresési eljárást: Állítsunk be egy töréspontot a when utasításnál, valamint egyet a BullsEyeGame.swift állományban, a check(guess:) belsejében, ahol difference-et hoz létre. Ezután futtassa újra a tesztet, és lépjen át a let difference utasításon, hogy megvizsgálja a difference értékét az alkalmazásban:

A probléma az, hogy a difference negatív, tehát a pontszám 100 – (-5). Ennek kijavításához a difference abszolút értékét kell használni. A check(guess:)-ban távolítsa el a helyes sort, és törölje a helytelen sort.

Vegye ki a két töréspontot, és futtassa újra a tesztet, hogy megerősítse, hogy most már sikeres.

Az XCTestExpectation használata aszinkron műveletek teszteléséhez

Most, hogy megtanulta, hogyan kell modelleket tesztelni és hibakeresést végezni a tesztek hibáinak elhárítására, ideje áttérni az aszinkron kód tesztelésére.

Nyissa meg a HalfTunes projektet. Ez URLSession az iTunes API lekérdezésére és dalminták letöltésére használja. Tegyük fel, hogy módosítani szeretné, hogy az AlamoFire-t használja a hálózati műveletekhez. Hogy lássuk, nem törik-e el valami, írjunk teszteket a hálózati műveletekhez, és futtassuk le őket a kód módosítása előtt és után.

URLSession A metódusok aszinkronok: azonnal visszatérnek, de csak később fejezik be a futásukat. Az aszinkron metódusok teszteléséhez használja a XCTestExpectation-t, hogy a tesztje megvárja az aszinkron művelet befejezését.

Az aszinkron tesztek általában lassúak, ezért érdemes elkülöníteni őket a gyorsabb egységtesztektől.

Hozzon létre egy új egységteszt-célt HalfTunesSlowTests néven. Nyissa meg a HalfTunesSlowTests osztályt, és importálja a HalfTunes app modult közvetlenül a meglévő import utasítás alatt:

@testable import HalfTunes

Az osztály összes tesztje az alapértelmezett URLSession-t használja a kérések elküldésére az Apple szervereinek, ezért deklaráljon egy sut objektumot, hozza létre a setUp()-ben, és adja ki a tearDown()-ben.

Változtassuk meg a HalfTunesSlowTests osztály tartalmát:

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

Következő lépésként adjuk hozzá ezt az aszinkron tesztet:

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

Ez a teszt azt ellenőrzi, hogy egy érvényes lekérdezés elküldése az iTunes-nak 200 állapotkódot ad vissza. A kód nagy része megegyezik azzal, amit az alkalmazásban írunk, ezekkel a kiegészítő sorokkal:

  1. expectation(description:): Visszaad egy XCTestExpectation objektumot, amelyet promise-ben tárolt. A description paraméter leírja, hogy mit várunk.
  2. promise.fulfill(): Ezt hívja meg az aszinkron metódus befejezéskezelőjének sikerfeltétel zárásában, hogy jelezze, hogy az elvárás teljesült.
  3. wait(for:timeout:): Folyamatban tartja a tesztet, amíg az összes elvárás teljesül, vagy a timeout intervallum véget nem ér, attól függően, hogy melyik történik előbb.

Futtassa a tesztet. Ha csatlakoztatva van az internethez, a tesztnek körülbelül egy másodpercig kell sikerülnie, miután az alkalmazás betöltődik a szimulátorban.

Gyors kudarc

A kudarc fáj, de nem kell örökké tartania.

A kudarc megtapasztalásához egyszerűen törölje az “s”-t az “itunes”-ból az URL-ben:

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

Futtassa a tesztet. Sikertelen, de a teljes időkorlátos intervallumot igénybe veszi! Ez azért van, mert azt feltételezted, hogy a kérés mindig sikeres lesz, és ezért hívtad meg a promise.fulfill(). Mivel a kérés sikertelen volt, csak akkor fejeződött be, amikor lejárt az időkorlát.

A feltételezés megváltoztatásával javíthatsz ezen, és gyorsabbá teheted a teszt sikertelenségét: Ahelyett, hogy megvárná a kérés sikerét, csak addig várjon, amíg az aszinkron metódus befejező kezelője meghívásra kerül. Ez akkor történik meg, amikor az alkalmazás megkapja a szervertől az elvárásnak megfelelő – OK vagy hibás – választ. A tesztje ezután ellenőrizheti, hogy a kérés sikeres volt-e.

Hogy lássa, hogyan működik ez, hozzon létre egy új tesztet.

De először javítsuk ki az előző tesztet azáltal, hogy visszavonjuk a url módosítását.
Ezt követően adjuk hozzá a következő tesztet az osztályunkhoz:

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

A legfontosabb különbség az, hogy a befejezéskezelő egyszerű belépése teljesíti az elvárást, és ez csak körülbelül egy másodpercig tart. Ha a kérés sikertelen, akkor az then állítások is sikertelenek.

Futtassa a tesztet. Most már körülbelül egy másodpercig kell tartania a sikertelenségnek. Azért sikertelen, mert a kérés sikertelen volt, nem pedig azért, mert a teszt lefutása túllépte a timeout értéket.

javítsa ki a url értéket, majd futtassa újra a tesztet, hogy megerősítse, most már sikeres.

Objektek és kölcsönhatások hamisítása

Az aszinkron tesztek biztonságot adnak abban, hogy a kódja helyes bemenetet generál egy aszinkron API-hoz. Azt is tesztelheti, hogy a kódja helyesen működik-e, amikor bemenetet kap egy URLSession-től, vagy hogy helyesen frissíti-e a felhasználó alapértelmezett adatbázisát vagy egy iCloud tárolót.

A legtöbb alkalmazás rendszer- vagy könyvtári objektumokkal lép kölcsönhatásba – olyan objektumokkal, amelyeket nem Ön irányít -, és az ilyen objektumokkal kölcsönhatásba lépő tesztek lassúak és megismételhetetlenek lehetnek, ami sérti a FIRST két alapelvét. Ehelyett meghamisíthatja a kölcsönhatásokat úgy, hogy bemenetet kap a stubokból vagy a mock-objektumok frissítésével.

A meghamisítást akkor alkalmazza, ha a kódja függőségben van egy rendszer- vagy könyvtári objektumtól. Ezt úgy teheti meg, hogy létrehoz egy hamis objektumot az adott szerep eljátszására, és ezt a hamisítványt injektálja a kódjába. A Jon Reid által írt Dependency Injection több módszert is leír erre.

Fake Input From Stub

Ezzel a teszttel ellenőrizni fogod, hogy az alkalmazás updateSearchResults(_:) helyesen elemzi-e a munkamenet által letöltött adatokat, azáltal, hogy ellenőrzöd, hogy a searchResults.count helyes. A SUT a nézetvezérlő, a munkamenetet pedig stubokkal és néhány előre letöltött adattal fogod hamisítani.

Menj a Test navigátorba, és adj hozzá egy új Unit Test Targetet. Nevezze el HalfTunesFakeTests-nek. Nyissa meg a HalfTunesFakeTests programot.swift és importálja a HalfTunes alkalmazásmodulját közvetlenül a import utasítás alatt:

@testable import HalfTunes

Most cserélje ki a HalfTunesFakeTests osztály tartalmát erre:

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

Ez deklarálja a SUT-ot, ami egy SearchViewController, létrehozza a setUp()-ben és kiadja a tearDown()-ben:

Megjegyzés: A SUT a nézetvezérlő, mert a HalfTunes-nak masszív nézetvezérlő problémája van – minden munka a SearchViewControllerben történik.swift. A hálózati kód áthelyezése egy külön modulba csökkentené ezt a problémát, és a tesztelést is megkönnyítené.

A következőkben szükséged lesz néhány minta JSON adatra, amit a hamis munkamenet biztosít majd a teszted számára. Elég lesz néhány elem, így a letöltési eredmények korlátozásához az iTunes-ban csatolj &limit=3-t az URL stringhez:

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

Másold ki ezt az URL-t, és illeszd be egy böngészőbe. Ez letölti az 1.txt, 1.txt.js vagy hasonló nevű fájlt. Nézze előnézetben, hogy megerősítse, hogy ez egy JSON fájl, majd nevezze át abbaData.json.

Most menjen vissza az Xcode-ba, és lépjen a Project navigátorba. Adja hozzá a fájlt a HalfTunesFakeTests csoporthoz.

A HalfTunes projekt tartalmazza a DHURLSessionMock.swift támogató fájlt. Ez egy DHURLSession nevű egyszerű protokollt definiál, metódusokkal (stubs) egy URL vagy egy URLRequest adatfeladat létrehozásához. Meghatározza továbbá a URLSessionMock-t, amely megfelel ennek a protokollnak olyan inicializátorokkal, amelyek lehetővé teszik egy URLSession mock objektum létrehozását az adatok, a válasz és a hiba kiválasztásával.

A hamisítvány beállításához menjünk a HalfTunesFakeTests.swift állományba, és a setUp()-be, a SUT-t létrehozó utasítás után adjuk hozzá a következőket:

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

Ez beállítja a hamis adatokat és válaszokat, és létrehozza a hamis munkamenetobjektumot. Végül a végén a sut tulajdonságaként beilleszti a hamis munkamenetet az alkalmazásba.

Most készen állsz a teszt megírására, amely ellenőrzi, hogy a updateSearchResults(_:) meghívása elemzi-e a hamis adatokat. Adja hozzá a következő tesztet:

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

Ezt még mindig aszinkron tesztként kell megírnia, mert a csonk aszinkron metódusnak adja ki magát.

A when állítás az, hogy a searchResults üres, mielőtt az adatfeladat lefutna. Ennek igaznak kell lennie, mert egy teljesen új SUT-ot hoztál létre a setUp()-ben.

A hamis adatok három Track objektum JSON-ját tartalmazzák, így a then állítás az, hogy a nézetvezérlő searchResults tömbje három elemet tartalmaz.

Futtasd a tesztet. Elég gyorsan sikerülnie kell, mert nincs valódi hálózati kapcsolat!

Hamis frissítés a Mock objektumhoz

Az előző teszt egy csonkot használt egy hamis objektum bemenetének megadására. Ezután egy mock objektumot fogsz használni, hogy teszteld, hogy a kódod helyesen frissíti UserDefaults.

Nyisd meg újra a BullsEye projektet. Az alkalmazásnak két játékstílusa van: A felhasználó vagy a csúszkát mozgatja a célértéknek megfelelően, vagy a csúszka pozíciójából kitalálja a célértéket. A jobb alsó sarokban lévő szegmentált vezérlő váltogatja a játékstílust, és elmenti azt a felhasználói alapértelmezések között.

A következő tesztünkkel azt fogjuk ellenőrizni, hogy az alkalmazás helyesen menti-e a gameStyle tulajdonságot.

A Teszt navigátorban kattintsunk az Új egységteszt osztályra, és nevezzük el BullsEyeMockTests-nek. Adjuk hozzá a import utasítás alá a következőket:

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

MockUserDefaults felülírja a set(_:forKey:) utasítást a gameStyleChanged zászló növeléséhez. Gyakran találkozunk hasonló tesztekkel, amelyek egy Bool változót állítanak be, de egy Int inkrementálása nagyobb rugalmasságot biztosít – például a tesztünk ellenőrizheti, hogy a metódus csak egyszer hívódik-e meg.

Az SUT és a mock objektum deklarálása a BullsEyeMockTests-ben:

var sut: ViewController!var mockUserDefaults: MockUserDefaults!

Következő lépésként az alapértelmezett setUp() és tearDown() helyére ezt írja:

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

Ez létrehozza az SUT-t és a mock objektumot, és a mock objektumot az SUT tulajdonságaként injektálja.

Most cseréljük ki a sablonban lévő két alapértelmezett tesztmódszert erre:

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

A when állítás az, hogy a gameStyleChanged flag 0 legyen, mielőtt a tesztmódszer megváltoztatja a szegmentált vezérlést. Ha tehát a then állítás is igaz, az azt jelenti, hogy a set(_:forKey:) pontosan egyszer lett meghívva.

Futtassa a tesztet; sikeresnek kell lennie.

UI tesztelés az Xcode-ban

AzUI tesztelés lehetővé teszi a felhasználói felülettel való interakciók tesztelését. A felhasználói felület tesztelése úgy működik, hogy lekérdezésekkel megkeresi az alkalmazás felhasználói felületének objektumait, eseményeket szintetizál, majd elküldi az eseményeket ezeknek az objektumoknak. Az API lehetővé teszi egy UI-objektum tulajdonságainak és állapotának vizsgálatát, hogy összehasonlítsa azokat az elvárt állapottal.

A BullsEye projekt Teszt navigátorában adjon hozzá egy új UI-tesztelési célpontot. Ellenőrizze, hogy a tesztelendő célpont a BullsEye, majd fogadja el az alapértelmezett nevet BullsEyeUITests.

Nyissa meg a BullsEyeUITests.swift fájlt, és adja hozzá a BullsEyeUITests osztály tetején ezt a tulajdonságot:

var app: XCUIApplication!

A setUp()-ben a XCUIApplication().launch() utasítás helyébe a következő lép:

app = XCUIApplication()app.launch()

A testExample() nevet változtassa testGameStyleSwitch()-ra.

Nyisson egy új sort a testGameStyleSwitch()-ban, és kattintson a szerkesztőablak alján található piros Felvétel gombra:

Ez megnyitja az alkalmazást a szimulátorban olyan módban, amely tesztparancsként rögzíti az interakciókat. Miután az alkalmazás betöltődött, koppintson a játékstílus-kapcsoló csúszkaszegmensére és a felső címkére. Ezután kattintson az Xcode Record gombra a felvétel leállításához.

Az alábbi három sor van most a testGameStyleSwitch()-ban:

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

A felvevő létrehozta a kódot, hogy tesztelje ugyanazokat a műveleteket, amelyeket az alkalmazásban tesztelt. Küldjön egy koppintást a csúszkára és a címkére. Ezeket fogja alapul használni a saját UI-tesztjéhez.
Ha más utasításokat is lát, csak törölje őket.

Az első sor megkettőzi a setUp()-ben létrehozott tulajdonságot, ezért törölje azt a sort. Még nem kell semmit megcsapolnod, ezért a 2. és 3. sor végén lévő .tap() sort is töröld. Most nyissa meg a melletti kis menüt, és válassza ki a segmentedControls.buttons-t.

A következőknek kell maradniuk:

app.segmentedControls.buttonsapp.staticTexts

Koppintson bármely más objektumra, hogy a felvevő segítsen megtalálni a tesztekben elérhető kódot. Most cserélje ki ezeket a sorokat erre a kódra az adott szakasz létrehozásához:

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

Most, hogy megvan a szegmentált vezérlő két gombjának neve és a két lehetséges felső címke, adja hozzá az alábbi kódot:

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

Ez ellenőrzi, hogy létezik-e a megfelelő címke, amikor tap() a szegmentált vezérlő egyes gombjaira tap() lép. Futtassa a tesztet – minden állításnak sikeresnek kell lennie.

Teljesítménytesztelés

Az Apple dokumentációjából: A teljesítményteszt egy kiértékelni kívánt kódblokkot tízszer futtat le, és összegyűjti a futtatások átlagos végrehajtási idejét és a standard eltérést. Ezeknek az egyedi méréseknek az átlaga képez egy értéket a tesztfuttatásra vonatkozóan, amelyet aztán egy alapértékkel lehet összehasonlítani a siker vagy sikertelenség értékeléséhez.

Teljesítménytesztet írni nagyon egyszerű: Csak helyezze a mérni kívánt kódot a measure() lezárásába.

Hogy ezt a gyakorlatban is láthassa, nyissa meg újra a HalfTunes projektet, és a HalfTunesFakeTests.swift, adja hozzá a következő tesztet:

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

Futtassa a tesztet, majd a statisztikák megtekintéséhez kattintson a measure() hátráló lezárás eleje mellett megjelenő ikonra.

A referenciaidő beállításához kattintson az Alapvonal beállítása gombra. Ezután futtassa le újra a teljesítménytesztet, és nézze meg az eredményt – lehet, hogy jobb vagy rosszabb lesz, mint az alapvonal. A Szerkesztés gomb segítségével visszaállíthatja az alapvonalat erre az új eredményre.

Az alapvonalak eszközkonfigurációnként tárolódnak, így ugyanazt a tesztet több különböző eszközön is elvégezheti, és mindegyik különböző alapvonalat tarthat fenn az adott konfiguráció processzorsebességétől, memóriájától stb. függően.

Amikor olyan változtatásokat végez egy alkalmazáson, amelyek hatással lehetnek a tesztelt módszer teljesítményére, futtassa le újra a teljesítménytesztet, hogy megnézze, hogyan viszonyul az alapvonalhoz.

Kódlefedettség

A kódlefedettségi eszköz megmondja, hogy a tesztjei ténylegesen milyen alkalmazáskódot futtatnak, így tudja, hogy az alkalmazás kódjának mely részei nincsenek (még) tesztelve.

A kódlefedettség engedélyezéséhez szerkessze a séma Tesztelési műveletét, és jelölje be az Opciók lapon a Gather coverage for jelölőnégyzetet:

Futtassa az összes tesztet (Command-U), majd nyissa meg a Jelentés navigátort (Command-9). Válassza ki a Coverage-t a lista legfelső eleme alatt:

Kattintson a közzétételi háromszögre a SearchViewController.swiftben található függvények és lezárások listájának megtekintéséhez:

Görgessen lefelé a updateSearchResults(_:)-ig, hogy lássa, a lefedettség 87,9%.

Kattintson a függvényhez tartozó nyíl gombra a függvény forrásfájljának megnyitásához. Ha az egérrel a jobb oldalsávban lévő lefedettségi megjegyzések fölé megy, a kódrészletek zöld vagy piros színnel jelennek meg:

A lefedettségi megjegyzések azt mutatják, hogy a teszt hányszor találta el az egyes kódrészleteket; a meg nem hívott részek piros színnel vannak kiemelve. Ahogy az várható volt, a for-loop 3-szor futott le, de a hibaútvonalakban semmi sem hajtódott végre.

A függvény lefedettségének növeléséhez duplikálhatná az abbaData.json fájlt, majd szerkeszthetné úgy, hogy az a különböző hibákat okozza. Például változtassa meg a "results"-at "result"-re egy olyan teszthez, amely print("Results key not found in dictionary")-et talál.

100%-os lefedettség?

Mennyire kell törekednie a 100%-os kódlefedettségre? Guglizzon rá a “100%-os egységteszt lefedettség” kifejezésre, és számos érvet fog találni mellette és ellene, valamint vitát a “100%-os lefedettség” definíciójáról. Az ellene szóló érvek szerint az utolsó 10-15% nem éri meg az erőfeszítést. A mellette szóló érvek szerint az utolsó 10-15% a legfontosabb, mert azt nagyon nehéz tesztelni. Keressen rá a Google “hard to unit test bad design” kifejezésre, hogy meggyőző érveket találjon arra vonatkozóan, hogy a nem tesztelhető kód mélyebb tervezési problémák jele.

Hová menjünk innen?

Most már van néhány nagyszerű eszköz, amelyet a projektjei teszteléséhez használhat. Remélem, hogy ez az iOS Unit Testing és UI Testing bemutató magabiztosságot adott neked ahhoz, hogy mindent tesztelj!

A projekt elkészült változatát letöltheted a bemutató tetején vagy alján található Download Materials gomb segítségével. Folytasd a készségeid fejlesztését további saját tesztek hozzáadásával.

Itt van néhány forrás a további tanulmányozáshoz:

  • A tesztelés témájában számos WWDC-videót találsz. Két jó közülük a WWDC17-en készült: Engineering for Testability és Testing Tips & Tricks.
  • A következő lépés az automatizálás: Continuous Integration és Continuous Delivery. Kezdje az Apple Automatizálása a tesztelési folyamatnak az Xcode Serverrel és xcodebuild, valamint a Wikipedia folyamatos szállítás című cikkével, amely a ThoughtWorks szakértelmére támaszkodik.
  • Ha már van egy alkalmazása, de még nem írt hozzá teszteket, érdemes elolvasnia Michael Feathers Working Effectively with Legacy Code című könyvét, mert a tesztek nélküli kód örökölt kód!
  • Jon Reid Quality Coding sample app archívuma remekül használható a tesztvezérelt fejlesztés megismeréséhez.

raywenderlich.com Weekly

A raywenderlich.com hírlevél a legegyszerűbb módja annak, hogy naprakész maradjon mindenről, amit mobilfejlesztőként tudnia kell.

Megkapja heti kivonatunkat oktatóanyagainkból és tanfolyamainkból, és bónuszként kap egy ingyenes, mélyreható e-mail kurzust!

átlagértékelés

4.7/5

Adj értékelést ehhez a tartalomhoz

Jelentkezz be az értékelés hozzáadásához

90 értékelés

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.