Kuidas testida Swiftis viskekoodi

Kõik, mida peate teadma viskefunktsioonide testimise kohta XCTesti abil ning testikoodi puhtana ja töökindlana hoidmisel.

Mitu korda olete pidanud üle võtma projekti, kus toimusid ühikatsetused, kuid neist oli raske aru saada, ebaõnnestunult ebaõnnestuda või vastasel juhul testi eesmärki isegi ei ehitataks?

Ülioluline on hoida üksuse katsekood robustsena ja hooldatavana, mitte lasta neil aja jooksul hüljata ja eirata.

Storytel püüab muuta meie ühikatsetused lühikeseks ja loetavaks. Oma olemuse tõttu kipub testi kood pikaks ja korduvaks kasvama, seetõttu on oluline hoida see puhas, siis ei muutu testidega töötamine projekti kasvades liiga tüütuks.

Kood, mida viskab, võib mõnikord olla keeruline testida ja saadud testkood võib kole olla. Selles artiklis mõtlen erinevatele stsenaariumidele ja kuidas neid kenasti ja jõuliselt lahendada.

XCTest on võimas raamistik. Xcode 8.3-s tutvustas Apple lisaks paarikümnele olemasolevale veel mõnda uut XCTAsserti funktsiooni. Ehkki nende pakutavad funktsioonid võimaldavad teha enamikke asju, mida arendaja soovib, nõuavad mõned asjad lisaks loomulikult pakutavatele funktsioonidele katteplaadi koodi.

Vaatame mõnda juhtumit ja kuidas neid lahendada.

Viskefunktsioonide tulemuste võrdlemine

See on lihtne. Kõik olemasolevad XCTAsserti funktsioonid võtavad juba vastu argumente. Kui tulemus on võrdne, teeb selline rida järgmist:

XCTAssertEqual (proovige x.calculateValue (), oodatav väärtus)

Kui kalkulatsioonVäärtus () tekitab tõrke, kuvatakse katse ebaõnnestumisega teatega “XCTAssertEqual ebaõnnestus: viskas viga…”. Kui kõnet ei antud ja kaks väärtust pole võrdsed, siis katse nurjumisega sõnumiga „XCTAssertEqual ebaõnnestus: a pole võrdne b-ga”, kus „a” ja „b” on vastavalt vasakpoolsed ja parempoolsed argumendid, mille on koostanud Keel (kirjeldab :).
Lihtsamalt öeldes kontrollivad kõik XCTAssert * väärtuse kontrollimise funktsioonid, et kõne ei anna ühtegi viga ja annab oodatud tulemuse. Väga mugav.

Funktsioonid mitte-võrdsustatavate naastetüüpidega

Sageli ei piisa XCTAssertEqualist ja selle väärtust kontrollivatest õdedest-vendadest.

Kui funktsioon ei tagasta väärtust või kui seda saab testjuhtumi kontekstis ignoreerida, võime kasutada uut funktsiooni XCTAssertNoThrow, mis sai kättesaadavaks versioonis Xcode 8.3:

XCTAssertNoThrow (proovige x.doSomething ())

Huvitaval kombel oli enne Xcode 8.3 ilmumist kohandatud funktsioon, millel oli täpselt sama allkiri, ja me ei pidanud ühtegi koodi muutma, välja arvatud kohandatud rakenduse eemaldamine.

Veel üks levinud juhtum on siis, kui tagastatud tüüp pole võrdne või kui tahame kontrollida ainult mõnda tagastatud tulemuse omadust.
Isegi kui tüüp on võrdne, on võrdõiguslikkuse testi juhtumi ebaõnnestumine peaaegu kasutu, kui objekti kirjeldus on väga pikk: on raske öelda, millise välja väärtus on vale:

Meie projektides on meil palju funktsioone, mis toodavad keerukaid tüüpe, mis ei vasta standardile Equatable. Levinud näide on andmemudeliobjektid - paljastame need sisemise rakendamise peitmiseks protokollidena ja me ei taha, et need oleksid võrdsed. Igal mudeli tüübil on viske initsiaator, mis võtab sõnastiku.

Mingil hetkel saime aru, et meie ühikatsed näevad välja kohutavad. Neil oli korduv kood, kohatised valikud ja kui need testid ebaõnnestusid, oli poiss kõik punaseks. Veelgi hullemaks muutmiseks oli palju copy-paste-d. Kood nägi välja selline:

XCTAssertNoThrow (proovige BookObject (sõnastik: sampleD Dictionary))
lase raamat = proovida? BookObject (sõnastik: sampleD Dictionary)
XCTAssertEqual (raamat? .Nimi, "...")
XCTAssertEqual (raamat? .Kirjeldus, "...")
XCTAssertEqual (raamat?. Hinne, 5)
...
// "parem" versioon:
XCTAssertNoThrow (proovige BookObject (sõnastik: sampleD Dictionary))
XCTAssertEqual (proovige BookObject (sõnastik: sampleD Dictionary) .name, "...")
XCTAssertEqual (proovige BookObject (sõnastik: pavyzdD Dictionary) .descripti, "...")
XCTAssertEqual (proovige BookObject (sõnastik: sampleD Dictionary) .rating, 5)
...
See ei ole üheksa ebaõnnestumist, see on üks ...

Lahendus osutus lihtsaks. Trikk oli XCTAssertNoThrow esimese autosulgemise tulemuse eraldamine ja täiendava valideerimise sulgemine, kuid ainult juhul, kui tulemus oleks olemas.

avalik func XCTAssertNoThrow  (_ avaldis: @autoclosure () viskab -> T, _ teade: String = "", fail: StaticString = #fail, rida: UInt = #line, ka validateResult: (T) -> Void ) {
    func executeAndAssignResult (_ avaldis: @autoclosure () viskab -> T, et: inout T?) viskab {
        proovida avaldist ()
    }
    var tulemus: T?
    XCTAssertNoThrow (proovige executeAndAssignResult (avaldis, et: & tulemus), teade, fail: fail, rida: rida)
    kui las r = tulemus {
        valideResult (r)
    }
}

Nüüd nägid samad testid palju mõistlikumad: loetavad, tugevalt kirjutatud ja segased sõnumid.

Konkreetsete vigade viskamine

Mõnel juhul soovime testida vastupidist - funktsioon viskab vea. Seda on sageli vaja mudeleid käsitleva väärtuse testimiseks.
Saime seda juba olemasoleva funktsiooniga XCTAssertThrowsError teha, ehkki kui tahtsime kontrollida, kas mõni konkreetne viga on visatud, peaksime viskevea hindamiseks pakkuma sulgemisvõimaluse.

Vaadates, milliseid kontrolle meil seal tavaliselt oli, märkasime, et neid on ainult kaks: kas võrrelda tagastatud viga eeldatavaga või kontrollida lihtsalt selle tüüpi. Nii et lõime need testid üherealisteks, lõime kaks mugavusfunktsiooni:

avalik funktsioon XCTAssertThrowsError  (_ avaldis: @autoclosure () viskab -> T, oodataError: E, _ teade: String = "", fail: StaticString = #fail, rida: UInt = #line ) {
    XCTAssertThrowsError (proovige avaldist (), teade, fail: fail, rida: rida, {(viga)
        XCTAssertNotNil (viga kui? E, "\ (viga) ei ole \ (E. oma)", fail: fail, rida: rida)
        XCTAssertEqual (viga kui? E, oodatav viga, fail: fail, rida: rida)
    })
}
avalik funktsioon XCTAssertThrowsError  (_ avaldis: @autoclosure () viskab -> T, oodatavErrorTüüp: E.Tüüp, _teade: String = "", fail: StaticString = #fail, rida: UInt = #line ) {
    XCTAssertThrowsError (proovige avaldist (), teade, fail: fail, rida: rida, {(viga)
        XCTAssertNotNil (viga kui? E, "\ (viga) ei ole \ (E. oma)", fail: fail, rida: rida)
    })
}

Isegi kui see ei viska…

Viskefunktsioonide võimsust saab kasutada tugevamate testide kirjutamiseks ka muude stsenaariumide jaoks, kus regulaarne võrdsus pole kohaldatav.

Kaaluge sellise enumi kasutamist, mida ei saa võrdsustada - näiteks kui selle juhtumite seotud väärtused pole võrdsed.
Testijuhtumite vahetamise asemel kirjutame puhtad abistajafunktsioonid, mis viskavad olulisi vigu.

Tavaline näide on tulemuse enum:

enum tulemus {
    juhtumi edu ([string: suvaline])
    juhtumi rike (viga)
}

Kui katsetaksime funktsiooni, mis annab tulemuse otse, peaksime tagastatud väärtuse ümber lülitama ja valedel juhtudel helistama XCTFailile. Kopeeriksime ja kleepiksime lüliti iga katsejuhtumi jaoks ja uute enum-juhtude testide värskendamine oleks õudusunenägu.

Selle asemel saame luua abistajate viskamisfunktsioonid, et käsitleda enumit ühes kohas:

XCTAssert (proovige tulemust.assertIsSuccess (assertValue: {(väärtus: [string: suvaline]))
    XCTAssertEqual (väärtus.arv, 10)
}))
XCTAssert (proovige tulemust.assertIsFailure (assertError: {(väärtus: viga))
    XCTAssertEquals (väärtus, MyError.case)
}))
// MÄRK: abilised
privaatlaiend Tulemus {
    private enum Viga: Swift.Error, CustomStringConvertible {
        var sõnum: String
        var kirjeldus: String {return message}
    }
    func assertIsSuccess (assertValue: (([String: Any]) viskab -> tühine)? = null) viskab -> Bool {
        lülita ise {
        juhtum. edu (las väärtus):
            proovige assertValue? (väärtus)
            tagasta tõsi
        juhtum. rike (_):
            viske viga (teade: "Oodatav. edu, sain. \ (ise)")
        }
    }
    func assertIsFailure (assertError: ((Error) viskab -> tühine)? = null) viskab -> Bool {
        lülita ise {
        juhtum. edu (_):
            viske viga (teade: "Oodatav tõrge, sain. \ (ise)")
        juhtum. rike (lase viga):
            proovige kinnitada viga? (väärtus)
            tagasta tõsi
        }
    }
}

Sellist lähenemisviisi saab kasutada mitmesuguste stsenaariumide korral, näiteks valikuliselt graatsiliselt kontrollides (mis on ka enumad: troll :).

Märkus kohandatud kinnitusfunktsioonide loomise kohta

Kohandatud testfunktsioonide kirjutamisel tuleb meeles pidada vähe asju.

  1. Hea tava on lisada rea- ja failiargumente, viies need täielikult edasi XCTAsserti standardfunktsioonidesse. Sel viisil teatatakse testjuhtumi tõrgetest kohas, kus teie kohandatud väidet nimetatakse, ja mitte funktsiooni enda sises.
  2. Hea on lisada sõnumi parameeter, nii et helistaja saab testile konteksti anda. Neid on hea kasutada ka katsejuhtumite kirjutamisel :)
  3. XCTFail (teade :) annab võimaluse testi tingimusteta läbikukkumiseks, mis võib olla väga kasulik nt. kui võrrelda katseid, mis pole võrdsed, ja sattuda ootamatusse olukorda.

*

XCTesti raamistik on viimase aasta jooksul kasvanud väga võimsaks ja püüame jääda olemasolevate funktsioonide taaskasutamisse nii palju kui võimalik, selle asemel, et nende käitumist korrata.

Väärib märkimist, et NSExceptions jaoks pakub XCTesti raamistik rikkamat API-t, mis on kahjuks saadaval ainult Objective-C-s: https://developer.apple.com/documentation/xctest/nsexception_assertions?language=objc

Kõigi kehtivate funktsioonide täieliku dokumentatsiooni leiate siit: https://developer.apple.com/documentation/xctest

Algselt sellest inspireeritud: http://ericasadun.com/2017/05/27/tests-that-dont-crash/

*

Xcode 9 värskendus: uusi nõutavaid API-sid pole veel lisatud. XCTesti Swift-versioonis NSE-erandite toe toetades!

*

Xcode 10.2 värskendus: uusi väita API-sid pole veel lisatud.

Olen hakanud uurima, kuidas saaksin aidata kaasa avatud lähtekoodiga rakenduste loomisele, ja loonud Swifti foorumites pigi. Kas soovite mind selles aidata? Uurige mind siin või Twitteris, teeme koos XCTesti paremaks!

Täname, et lugesite! Kui teile artikkel meeldis, siis palun jagage seda, klõpsates artikli all oleval nupul Jaga - rohkem inimesi saab sellest kasu.

Võite mind jälgida ka Twitteris, kus enamasti kirjutan iOS-i arendamisest.

Kas soovite minuga koostööd teha? Storytel võtab tööle - kandideeri siia