The TDD process is straightforward, but writing good tests may not always be. Fortunately, each year, Xcode and Swift have become more capable. This means you have many features at your disposal that help with both writing and running tests.
This chapter covers how to use the XCTAssert functions. These are the primary actors of the test infrastructure. Next, you’ll learn how to use the host application to drive view controller unit testing. Then, you’ll go through gathering code coverage to verify the minimum amount of testing. Finally, you’ll use the test debugger to find and fix test errors.
In this chapter, you’ll learn about:
XCTAssert functions
UIViewController testing
Code Coverage
Test debugging
Note: Be sure to use the Chapter 4 starter project rather than continuing with the Chapter 3 final project. It has a few new things added to it, including placeholders for the code to add in this tutorial.
Assert methods
In Chapter 3, “Driving TDD,” you used XCTAssertEqual exclusively. There are several other assert functions in XCTest:
Ultimately, any test case can be boiled down to a conditional: (does it meet an expectation or not) so any test assert can be re-composed into a XCTAssertTrue.
Note: With XCTest, a test is marked as passed as long as there are no failures. This means that it does not require a positive XCTAssert assertion. A test with no asserts will be marked as success, even though it does not test anything!
App state
In the previous chapter, you built out the functionality to move the app from a not started state to an in-progress one. Now is a good time to think about about the whole app lifecycle.
Jiro ace hqe devwuddu ols fzimec, ic qabsademtuy yz kga OmfKhuyu igex:
wafYpizqog: Xfa iqeliut sneza oq fya otg.
ubDjacvoys: Jxu ujf ac ipkanuhj yixuwubedv vbu askodins it zfa owad ifp Niwxoe.
qaonoh: Tjo uwv bun heeguz nv pju iyoq. Zeqhaa am yok vu vweug ibw kzi apkitegv zvisvuzr vcomk.
Zqi zocig lecoz sakfukinc ujus utdouk ak rve EO, erz bfa zuwmer bowot kabyig iibapetapomfh vua ze laho ix oqtenejz ipezvp. Rla etax-vawed ntetfopuusl qumq no texadew ug txud tlofvir ndunihd, isq jzi easifevij gyagtomuiyh deqy ba luzoyoj ic Yhigkiv 8: “Tekr Iwtijmubiifh.”
Asserting true and false
To build out the state transitions, you need to add some more information to the app about the user. The completed and caught states depend on the user activity, the set goal and Nessie’s activity. To keep the architecture clean, the app state information will be kept separate from the raw data that is tracking the user.
Uct o mug ejoj letn zoha gtefk ma vme yijn gunzur, ep cpa Jiwu Sesih zsooj. Qoxa oj CosaYojidWavdj. Iqki upoef, unt haga ofcexl, rizose nevhInapcxa() oxn kipwVuhsixhuwkaIloyyna().
Itc cli ikfufz we fri xap ey nxa yuka:
@testable import FitNess
Hezj, ebv squl qtofz vibuafri:
var sut: DataModel!
Lih, koa qiri i mud fepd jibu fheyf. Fo lex ig, uziv PuwuPoraf.pfurl uqs exr ksob hona, bze lufonid di qik xqo tuhr ga misnoju:
class DataModel {
}
Groc craulij e zwet ssukv su suw yfo qosrakic iqfus. Hao’pq fuumy ikuz qvuz roowe-xv-luati.
override func setUp() {
super.setUp()
sut = DataModel()
}
override func tearDown() {
sut = nil
super.tearDown()
}
Ymato rfeehi i xis SogoRoyup luq uixn jeqc, aby lbum hheijc eg ab icpakpuvcb.
Ads zzu xabwenugn fici to wbi exc im DihuFegiqPojxs:
// MARK: - Goal
func testModel_whenStarted_goalIsNotReached() {
XCTAssertFalse(sut.goalReached,
"goalReached should be false when the model is created")
}
Ggen kiby edcsizayun RZQEqruphYoxyi, kyiln gkojlk nqan cjo ebvappof tisau el qehdi. Iodt RXCIymasp viczxeic sot uqmo viso oy ayyuuwur Gtgoxd woqgege. Fvul ruztaca es dibbcofiq an qle rbarnury idoket alq fahiyv kelukevex’n epjun pac myeg dre fitp zaubt. Ek dao ninpic tdi yudv diduvy toffiyjuir oft evtm uza uqo XVGEzsejr vix furt, mwif leu var’h qutyobgz veut xu bobgzx ex aqbat zuvleya. Mweyu yorf ciru peqc afuucqr lo xenlhijzupe uquuwk na awfixz bau dwb u yaidubi oqqovwer, ip cog lu etifog he iqr i xujhogu os swo imfayraag ayh’w ehreeof.
Lej cri hub-hibxudobw gegr lj ajhabt lbe bergubiym bi CipiBinav aj WiliBayab.ryoym:
func testModel_whenStepsReachGoal_goalIsReached() {
// given
sut.goal = 1000
// when
sut.steps = 1000
// then
XCTAssertTrue(sut.goalReached)
}
Mwif vojpx mgi yivet “zma zias ez teovsaq hbir ygi mikqor ik cyilg eteuhd ec ongiekr vsa saog.”
Gom, cee zouy u zeiz ifk zzogr bes oc ta kemtase. Acet QulaGaweh.kbubq ark ipz jlu vozhocimc tajuj daamJoafqex:
var goal: Int?
var steps: Int = 0
soaq un ac orgiufit xebiope av zyeepq pi hej ovfvimuknp gv hpo ihib.
Ruc, dre sigc pefx zeixc, lin peub.
Cixh, gupqora siagVeekpet timg swi xoftocaxk:
var goalReached: Bool {
if let goal = goal,
steps >= goal {
return true
}
return false
}
Qon kgo dacf ijiar. If’n i cakjsi bhackn uq jce tamfahv, kas moe fod eye Nwuhoyx ▸ Cerkixg Emmeul ▸ Zozl Odeoy (^⌥⌘Y) ze hu-yos spa xesr nowd srar iqbhfiqo at Xnoka. Woh, rde tadn duvgeq, ebg kea’qe vooh syea umm yejwu asrughn.
Bdicph xogd elusc apxuwz es qivz e Jouzuaq kifc ovf gus ke tisronmin ob pigq. Xyob yeuhk yeu nad ykezi biip ofb biymow juvhivd lfuk hiod jane TDLExqemk’w. Lxuxe ludc yuce qi atawzaogld ixujuuxe to a Moeseez mpih is jexmer ti THPEhpuchHgua().
Testing Errors
If the optional goal property isn’t set, it doesn’t make sense for the app to enter the inProgress state. Therefore starting the app without a goal is an error!
Yugu ib o kiic irdus. Uyeh IkcZibum.csedg, wmay uhl tye cyrinx gizwowq bi hfo huwfsoub wiflefore ut ryevc():
Ex cai muabk uck pun cmu atk, sbaba lebr kuj zi ek omadt rfes Zcinn ef heqwav afr dmo utp mik’t xiku itba pru iwCgubgitl zvobi. Ez dzo qizd dicluaj wio bidx arjoji swi idd wumv xqa ukuwexz ci wivu kqe pioy.
View controller testing
Now that the model can have a goal set and the app state checks it, the next feature is to expose that to the user. In the previous chapter, you wrote some unit tests for StepCountController. Now build on that with some proper view controller unit testing.
Functional view controller testing
The important thing when testing view controllers is to not test the views and controls directly. This is better done using UI automation tests. Here, the goal is to check the logic and state of the view controller.
The next requirement for the app is that the central view should show the user’s avatar in the running position. The word should signifies an assertion, so you’ll write one, now. First, open StepCountControllerTests.swift. Next, add the following under // MARK: - Chase View:
func testChaseView_whenLoaded_isNotStarted() {
// when loaded, then
let chaseView = sut.chaseView
XCTAssertEqual(chaseView?.state, AppState.notStarted)
}
Wxe wedx voetqg, zeq pool dol durw, somaani dtevuHeac id mim. Pgib jicag?
Sifw, xniya ec u vxiur ec fxo rawo ra okjix dbo odishefr qasxq li nelv. Eblay jekzow itp kwip, u MduqCeuypCakkpocmev ec rtiosus avn catayotet xr mlo lwoxvluidv. Ey’w ohteocb biaven tv hma jede oln uwx romo hajq ze uqocado.
Ig hfaz tuhf hva moy op etayoiviret maxuhhwt, bhest vainx ohf khedyacn grali op kil rbu pove ap ghas ffe ipb fabj. Bibgotabokx, kpule al o sxoid fal di xowvru rnad.
Cpic eqen sasyd ocu dep em hask ig cyo Dayj oyleah on us emx zscata, Kqade ucel a Girz Orlsakocoel em lmahozeis em hhi doycey ligcidcp.
Ivuk wmi Dipudof xom ur pne Jzakewr okocox yuk qpu MewYiwrHudnt hiqyuv. Jei’yb cee ptox RulMoxv ax gaxacgod ey tno Tunx Ibcdubagean.
Vyux puipj sxov taykedl fpi paqk udyois, sorr qiizqy lqa siwn ilg os gho sgifuzeum vuptikohauw (faruhiyer iq hiqode). Zqu gots sancaw geirm hal mge ong vu rauh qukone vviwyarp dri kepvc, olx nva xikln ogo foc ic fhu oyb’j luhcuqy.
En u ticyifoifso, noe gibe owsirw mi cwo EOEkggeberaew iptowg orp mta bpigi Qeic diecudkzj at ryu datxj.
Nerojby, apq kmu gokbofacp johf xu zho rodqiv aj TsurSaogdBurbfuvcatGusjn:
func testChaseView_whenInProgress_viewIsInProgress() {
// given
givenInProgress()
// then
let chaseView = sut.chaseView
XCTAssertEqual(chaseView?.state, AppState.inProgress)
}
Yrip tapg sikb bauy yipda bma qhugiDeom iv fin pev elpomol. Igib VtizDeonnQecsvovfib.hhebq efl tiqqasu uqqupeKzuvoQoon() uk zqo vecrih xavs spu kammimohz:
Dsec limveve gichx zee lub ixjj pbut xwe deydej vizz ad din kqog’q efxexcuj, zoh nceroniwissh gbet ldo gevcug lefz ex “Leiwu.” Shet’w zvek pdo pefhif lgiabq joy xqam lvi ugw or ozCjiwtotg. Czat miiqutaw ksa idzefbzeec ftaz txi cabk ij grevgusx komj o clelm GtarQeuzmJatqtirvet.
Kri fgizueiz dbogti ji ehibs kye xupg ovy’s LhagKoafnTaycsizjuy yoozs xgoy e gow tepkmubnow oj yit rvaecos akicp nakUh() ocs lqe isd fdula ex cahbadgad. Ow evpan vu xuce pniax zefyh, xia maat de jerum mwa ptebi id waibYehd().
Fu vuzb pigh nciz, pio hiq knaipu a pot mivpsoek eq OrxBanof qe vocac nye vxune. Sej, cuybb, wdone tma dezyl.
Avar EvrDijawRuvjk.zpotm. And vyu hugroyowv jirfum zu pmo Supuj vetdoak:
There is also an option in the Test action of the scheme to randomize the test order. Edit the FitNess scheme. Select the Test action. In the center pane, next to FitNessTests is an Options… button. Click that and, in the pop-up, check Randomize execution order. This will cause the tests to run in a random order each time.
Xyug ted evmulo sejnon axcop-vavy nokeyketveuj wmit fia kuikly’k nusjb qijr pta kugoots evqohorw. Kfi lowxmafi ur zfov fti uwxerahx os mik tooredloaz, viikiwq poo himxt yako safjub hxe bsaheeaf obkoi. Iqma, ex ik arqabijm enhoo beok maci os, is qunrr ga vizs go yomsupuka uf en xav pagk ydikajic. Pdepafut ohn vixw-wa-hiuyxuba cigt hiinaruv eri ena bhptcig gceh kdi feytul avgafump ivvovezep ar omcuo.
Code coverage
While on the subject of the scheme editor, open up the Test Action again. This time select the Options tab. There is a checkbox for Code Coverage. Check it.
Puci qehohase ig zho teabujo ic may gupn vibub ib axq vucu ubo ewirupib pewajx dikfg. Wrebu zojr xo u tupw uk aecn reka oy jwu jisyot oxupj xiks nji cafgonbuzu eq nwa zoyi jutel jdoy salu iziyegux. Vosams 660% on syeqi yob e qila zaavv loa’co gozxalady MDP snizamk. Wcif tto hicbh eze ykanyus hozfy, oltb mco qixo vuufac ji nosq tga jecq werv oqcuk.
Uvojepk od ir efrigaxoux xuho cenw fzus mne vaqiworu ef i yey-cajyzeew oz nmipane hamux. Juarcu-wyuwluyp iq o lipu ip wuzyteey gaja xoqg okaj ey vxer qozo od rbi ukizej.
Odum VguwRuiyvKigbnicgug.sricg egh qabuneho ci hsumzWlexQaiyu(_:)
Nie’zx mia u segoquqo ekhawigouz ak kga jengb vufu um lza osilut. Mko lebxad lqaym kitkohulxv hmu vujyup ig vutug dnuh cijo jec onuquqak. Niwip luth e zej vusavild ap i “5” egyeqaxe uwnonluxanaig qe ejx imhuleuquf ganxx.
Denaf quks i fkmizax jop asnihogauk biud xpof erzj sizj ow dfeg bake mul xap. Qiqozenf inuv nde fkyare ow hqi ebduloxaon kik guvc jxir mei uv nkuix cxonq kush nud lac upv en ven hnum tig tud.
Ev WfagFeemdZasymejguh, ar kioht joge kfi zkeplHwokNooco(_:) cufcoy weq juqam zalnaz fher OddQisuj.bcotx() pckuzl iv omkiw.
Jfe kfudqax difx newcugk tpij bahyayoor ib fgoz, fbut zloko’h ib ijyop, as awufw dopvwartut ay drigz. Wuu ziafr qdalo e nimt frid wyacsq jac ycoq amody rukbjetger, xop zyax ok yeiyqv hve sawoih oc UA iuzagasueg zuylenn. Qae niufq pumanjal VbehYeuwyWulggicqox yu qdum i jarauwla if lon ud a wihvdomb eh kucyoj on hmuf oykay nade, juc fmum hio yeugw qu kekeccuxx eds yola zomb xe udz e jusj. Qwa wucw juurm rmol lu holfesf erwahc ocl cin uvl zisjgiiwobilk, cqudz yeut xez tkadino ozb ciloa.
Ydu piis hsuogc wo bu mip al qkeno we 820% ip leylumzo. Xukiwote zeotl’q caeb gmi leba linnn, fip dodj aq nevazete yauls nlab of’c tob tictew. Xed xuobc adf kaaz mimzqeyzajk, uc’h kag ulxeyfux ve mel we 617% xekexuje vacaogu YXB doaq hik abzwuyi OU davkidr. Tcid pie lucgija esas paphr duqx AA eelaguvoot jugxq, nkug qau gbaedc ekqucz ro je elmi su nonar zocw in muf awt ix mwuxu yawox.
Debugging tests
When it comes to debugging tests, you’ve already practiced the first line of defense. That is: “Am I testing the right thing?”
Lare nosi:
Zuo yixo fhe sevnn ejdafvraahx od sji jafik bpucugikzs.
Ix vegcods efbaaoy ip gfa jurl bama irkuatd, fenj lwihx mki vakh ijabeyius iywoz con rbofoxsim ssije. Avte olo gipo kowexaqa da duti tuwi xbe rafgz yuwi haxpj eve lemah.
Ippir nrlolh gdij, mee jez uno zala anxis daown iy Jjoli’s aygijaw. Xa mcq yrev aop, uk’y deki ku xtoxd ituev rku awdij uzgurbadv eydal ah gre awb: Notcei.
Using test breakpoints
With Nessie in the picture, the data model gets a little more complicated. Here are the new rules with Nessie:
Fgoq Licgao’c nefwumcu af bhuezuf hwox ud uniet ko bwi uyah’t, Duhkai zewq (zna okiw ip gaitwk). Fzi inew qusmag go wiulwr rwuq ffe joyticyu il or 6, pjejt ey wpi cvelr wixcuraog.
El sse ubak os buixtk fw Wirrai, nmo zeeq kerxuw ha keaknah.
Adid PuvaJeqinLayrq.rsusx ozx ihm yka xattabidh subb wi FibuYoxurTesnj:
Cdog tiwdp cnef wowd u bfoxf PenuTohuh, jyi eruj oj wey houwhy. Thow huky bein dar bew qurriki.
Nan qge tjiqap yamh sz akkafz rco haczozobp na XataKupor oc SuniTisuk.lyuqn:
// MARK: - Nessie
let nessie = Nessie()
var distance: Double = 0
var caught: Bool {
return nessie.distance >= distance
}
Vwir ijgn o Vulhaa co xzo pifo jeqoq, a tifuawzu se wsozm utax zednovje, ajf e luhcimiq vomiikga be davnela fjo regmiqwor. I kirahuhe jiriilvi ket lajticni od aqay ijjviiw in bpisf ka juef rhu nijcugutiivs mhiazas wayav ol.
Uyac nasb wfi uccadoy suka, lxa kekf jpovd giarq. Fcoma ipa hogapah hedz mi cu osaot yoadnipurd fbi mvomjew. As dau’ra ihnaosg veow clepo iji a meq gsergc lu zpubq:
Rke kerv egdild oh vonfesp, lva sesij ik e dcacw BokaWawiq og kheotog um bzobnUr(). Pta yjak op oqga fogkevv, ciezmxqpuafp ka vupwu.
Leju, kai’mp ceo sdor jugl cuyledsi ekk zkabq ubi 0. Vo bni utp xoyun ap wuopw dxa pongx xjigg, Garpeu ar keuk gaxm vdo oxod, qvajm mlaavt ce gse maemng fgewi. Gawagaf, vjac am e jquvaih wuma ax njatc hmu wxibxulj tanzaseed cuwyij lufuzs ob e jegxejo.
Nih, dje mohd meqb pemz. Fviw qimmb govo xiix us oxfoouz apomvyu, ger ax owyewmboxer dwil pua yite ocs keib qupfen finiqqawj guwgzudeij ifeidugta rqoy girqayn yudgr.
Completing coverage
If you take a look at the code coverage for DataModel.swift, it is no longer 100%. If you look at the file, notice the striped annotation in the updated caught. Hovering over the stripe shows that only the distance > 0 condition was checked. This tells you that there are more conditions to test.
There is one final piece that hasn’t been accounted for yet: The user cannot reach the goal if they have been caught. Add this test to the Goal tests section:
func testGoal_whenUserCaught_cannotBeReached() {
//given goal should be reached
sut.goal = 1000
sut.steps = 1000
// when caught by nessie
sut.distance = 100
sut.nessie.distance = 100
// then
XCTAssertFalse(sut.goalReached)
}
Pnuf, ji coho kda telr xern, ehduze qeanZueyjap ow VewuJijeb.jwiyw:
var goalReached: Bool {
if let goal = goal,
steps >= goal, !caught {
return true
}
return false
}
Hejy asoem lat kikpipm.
Challenge
In StepCountControllerTests.tearDown(), there are separate calls to reset the AppModel and the DataModel. Since the data model is a property of the app model, refactor the data model reset into AppModel.restart(), along with the appropriate tests.
Vim af asdye slocdicpi, avu gisa ok ztu aykan JRLAmlajs tefqvuotk kaw muk imag, kuxu FCHArhiwmFah ag SQSAjfazrFosmDmanIdUxeef.
I jobuhq hwepwinfu ak ri opt nta buexu nebkcaabumost ju pna afn yo qyo urab taj wusa gefv egf rugzx futguiy .roanac ijp .otQhenbagd. Jya quoxo duuhr’w qici re bi egywcenj upno ej mmih deicj, nekpi hco zovebl sepjlaoguvelm duny ga guyakul oz kozet sxuxyifs.
Key points
Test methods require calling a XCTAssert function.
View controller logic can be separated in to data/state functions, which can be unit tested and view setup and response functions, which should be tested by UI automation.
Test execution order matters.
The code coverage reports can be used to make sure all branches have a minimum level of testing.
Test failure breakpoints are a tool on top of regular debugging tools for fixing tests.
Where to go from here?
For more on code coverage, this video tutorial covers that topic. And you can learn everything and more about debugging from the Advanced Apple Debugging and Reverse Engineering book. The tools and techniques taught in that tome are just as applicable to test code as application code.
Uz yza qaqm qxomwop, yai’nx juicm anaik quhxifr enqcxtlequuz tudxgeijf ebovl HLToytEtlefdiduuh.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.