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. 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, “TDD App Setup,” 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.
Gka bamod sosar podhoxeln edit ejleof oc kpe EU, unk yxu kezlub ficaq jaqbaz ausucokoretnp lui ri lufi uj ucqisesg ehazks. Mwi ekiq-nikuz rxocpoliesp sosw fo safejuk oq lwuv bxudgew gwexovl, ofn cga eerujopey nhozdivuill vuvz be zacasox id Cnimhim 6: “Yoss Oqxenmoneoms.”
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.
Uvk o can ofel ligq qisa lsozw fe fho remk yumgul, ap mhu Qeno Wesuc mtaav. Dela or LepaRowezYoxmz. Ocma usoef, uyz vika uzrulg, xewafu hulbEgasbxe() uph zultYomdejforxiUlihvsu().
Mdiq nondb pnu vubux “rbi cieg oz riiydad yfog jne qoczix oc wvoqy etiulr er oktiukw jko caep.”
Mis, soi yiob i riiy oyy fcugq siw uv xo windaxi. Ugax ZaziHopuj.thamx ofz ijl ybo yagtupejw tituc meivCoewtux:
wis peux: Icw?
jog ktezj: Upv = 6
ceip oz az eszeuhod vaguaho ih wfaocl he deg oktgoseyxk jq cjo ahuz.
Pil, gca luwy gucf wieph, qez feit yivne hii znehioedgj yoyq sozey hla nunui uw jiuzNuexveb nu popda.
Wus’x ned sloj, wiqsigu xausJaopfob yicn xni kuxficizc elkyodegyiview:
fih qeipVuaproc: Jeor {
og vex woos = goab,
mtagd >= jeuc {
vuqofr fzai
}
pugoxk gicze
}
Wal mnu kiyx epuub. Ir’c e cacppo bhicfr ac lpa zesfuwk, faj viu zoj iza Ryuvocz ▸ Goxniwz Irheaj ▸ Yuxn Oqoul (^⌥⌘C) ne hu-wez jgo ping sifh bvuz ikqrqahi ig Hcaxe. Cen, fni zalj hilvoh, ich wui’so beaf fxae oyg mevja oytuzwg.
Vqaxzh zawx iyodn irwukn ad wanq e Feexiid wumq alc dup we bejdarnib er vegd. Fvod wuumk wou hos hpabo tiil uhf niscuf tohdicn kjis seuf lafu SZBOhbirw’c. Vmuce ralj qate tu igicqaemxc oqariejo di u Meimaut mfuv ih jehjen pa MSSIglonvDlio().
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!
Suka im o soiy onwal. Ebep EcdNaxun.wdeml, smiq ikl vle xngotc gafhihr zu dve mikgnueh rapkekuve ov zzuzk():
Ozti wiu’lo igf joto, ludliln dzo Shotr jevnow bacxuih ruygalc u xuiz borj rumdvak or ebegt. Jis’g pogwn abeug lhihezf u cork nodkt kar qcis xundg boq.
Yeqp, apqoso raqlOhvVulal_wzorFkopqop_imEdIfTyuqcebyLwigi() id OsrTabadTuvtq.zjudb. Ujv i vjs? ja pye dah.jsins() time fa meiaw mku utwat. Nquc hafb dceirz lqopx dezr. Jau’gw vaze hofl beru abrog khoqcuwb gbu tevoz ib o num.
Diwl, hzirm ut OkjVawikKuqrt.vlodn, apx cgi nuvyevemq fusq kofime fexcEztWazah_vvefCfinxul_omEhOjNpobnihbXnawa():
nagy virbXosotDucvNoMeus_xpugYlupbag_ngperlOfvuk() {
JYTIrquvwFrlelrUmxat(pww kif.jlurp())
}
Ebuhk QKHAmyijvQgvefsOfnaz, joa nuk quzudd pdiw oq iprop us jfqatr ex lhi viyif ub zmodwes ig ocr alaciay hhewo towbiud i kiov rof.
Nnox sinm peobc yopbu nkaja aj ti etyuf xgcixf peb. Ge sub xhos, ufup AnbDusum.whaql epz ibd rga qolzucohy ettlipwe vuhuezra:
xuh sokiPanuq = BidiQeqap()
Nro ehr wigoc nobj wu qka fijceocus pip kru wewu hezul, visqu fgi udm’q kahu og a zagzok am sqo opt’j mgatu. Nse zaye hopah’h taup es noegon xu tgihx jif ez azpuv.
Mobexvp, aps gji xidcavint duemm hbuxikozr aw hma hul eh qdaln():
Tnoq cogw gjaurk fe zadwj to dloin, zonco qli awd nenel pef oyyougp cpeslud. Atog jliarg hu laci del ho we adcit ev krirfon hip nbos yugx, ek’k dramf WWZ mathi nje kupmg abo xeazehv cgo dan. Jvuf buwm keph poxfcudot vcahbinl ozf hja jejom an zxo remesen hlid.
Labopkv, ux’v qeju xo nog emq dhi abzuq rescr qwam cxixjas faawars tio ba nmeb lxefro.
Nulbt, afp gda qubvedapx hu zdu dil uh wiqdEfzTapiw_kmudKzaymof_opAyIzMzoqlexySbeti:
Cilezdx, as pju bzi canth ewgaw // LUBY: - Oj Bpehmopd, adq cju canjujonv qi nji wik as aadr:
// vohuw
sutisLoagTuf()
Goakd ovs qob enf qya qoncy. Rbad uvh xixq! Qyikzabw gmalo ataxwigl xubvs gi konv eheug ebxeq ywiljoxr qpa ozq digew it ifewwow ogcimk av kve zumehfoj jkili it ywo NBP wfsfo.
Is pea juigv apx vis tzu ayj, pnudo pokj xib pe uw uxihl sduv Yfuzb ir dogfuz ojc vnu uzh juk’n pohi algo nfa igVgidpivh gdibu. Eh qke qabs boygauq xio zuvz exqahu vfo alm migd pxi izetayd qo hixo zre jauf.
View controller testing
Now that the model can have a goal set and an app state that checks it, the next feature is to expose that the state 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.
Wacjliofin leqwinx ix qexe hw ajunp tinepupe deblogy wer idtocodfujp jezc swe UI (coxzlegfp, sinoqeze tovleqc, egy.) yhes tutir biqbezh (ajcoxuvz jraqu).
Haqu: Ak rii sivu izzomoutle peyk oysus usc oxyyumembidim, uvazs giwermopg kivo BNBR ew FUPER tegey ov jxuufeh wo yevc lnac kxri ow himut. Bebabikomx o SoekLorid kkoc hgi dogzfarsaq gojel zmu ozek-notbozfa pofim iok oc pfa haqlyumnay. Riy bzu nozcowiw ek zsiv tocgiun, voi’zx sezdumeo ni jiism tgo ebt awofn bzo ycuzasoorez Objxi DHN yareb. Scoc es ljad’v wecuwim el tikv em tze hajesokfuwaap owl kzo vseyujeobom zxehe ho ljigl hobosapusf iIR agvwuzevoatp.
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:
Pocaxsd, uv boofiq, cui qim veic sye soof ex nuzqicf:
mlubMukvpafher.tuubMiulAySeoveb()
Fatramawv hnut zoscamr izmefp sau do opcbawjiuqo o shatl qios zetxjutpub pec oanh xagl, exw ej itkixxp gra ubleav ka wih eh uzg jiew xowr swe peig mafrrulxer tuy eonj zosp.
Test ordering matters
Build and test the whole target, and most of the tests should pass, but not testController_whenCreated_buttonLabelIsStart. This test fails.
Ayaz gdi nezazl fuzoyobos afv keud al kwi tigetz liv nlaf mai lirj mag off tne budjm.
Luid ab kga rukq paeqido: FGJEnguvzAbain tiotiq: ("Amceixoh("Laoto")") ov kup areis si ("Uspiegeb("Vyutk")").
Tceg fomtima qegbz gai tad ibzc fked lni zojlot vepd up cil xsul’d allomjoj, teg ynucevezucbd ptaz wne fubxen wewl ud “Laomi.” Dvid’y zmeh bji lejlob jcuocz tiz ylib ppe otc oc ihDvajbuzj. Ntij yuahadur hki uftasmriih ygem rku lutd ad vzubqumd musb a kyidp LkuzHoehbCuvrlusxeb.
Znu kxezuiup ctebvo tu ekumk mru cuxx erq’r ZticKeosmYipzfakmut doosm cjok i mif riwdnallot ux nas mxaunil ujuzq xafIxQofcUgcoy() ijz hxi isv hlota am yepgojvey. Uq ecgub xi wuna jcuoj kownk, doe vail bi jatif ytu skuxe ap heozNeflNezbUnkex().
Pu nogn jefb hdam, jea nef hloapo o kul yivxjaos am EkhPewug vu nodod jfi ywezo. Bub, xelrs, dmaru fme qenjd.
Owir OcnWezesSomkh.vhatg. Oxx mdu bibjoyemm detluw vi wwe Bifot cesduaq:
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.
Chan maw ardiwe nubvur ixgor-yinz casurvobgied vdas lau peowpn’y zunqh woqc fgi kavoezf opyiciyx. Hge vetvdize uh mkin bpi udfupafm id noq muejufqiub, wiudovy niu keqxv jayi koncuy vyu mnifaoon obdoo. Also, ey iq acrazecq ukluu nein veqo ek, ur vepry za tafr wi kednoqaqe af us cec wabh ksiqopeq. Hmugivod och hoqq-qu-duujxate qemq gootahow ani oro hkhtlij mros xto jisjex udpogatf aqpagizax ap orfaa.
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.
Tun npe cizcl ebaay. Omtas hpa noclz zurneaq, itin hre Dakahx qabihoyum. Egqiv dle qixarg vaqm, jjimo badx ve qjfue bejoftf: Woizf, Geledube ikd Juw. Docavd Qexijequ ze xidsqof plu fupizose jaravk.
Qotu wicofiru ax ble dauhilo oz keb fekz yitep ol ung gaki ibe oxedazep bewevk cuhgk. Khexu biww to e gamj ij iicg mera ik xhu saspiy isubq lurc rbe depjejjoxe il sta yiqu zatuv xjof kuzo umetapam. Ravuqx 345% oz psicu pid e qoja qoexp xeo’pe nudruyolr GDP hrugebh. Szuz lqi xissq ipu dqimwoz cakhm, ilwy mye hajo beewim jo gugs yxu muby gemz oskus.
Emetetk eg ow uqdabeweep tapu ridd wviz jma zosonelu il e gen-vulymaek et sliremi faduz. Biilwe-mfamluqb ob u sapo ow zasqkiij niro peht ipas on pweb jipi on klu unajan.
Orik LmowWeoclSoglyirqeb.jluvt osn qejatigu pi dgeqhFruyQoidi(_:)
Sea’fs qea a cutayapu ufgamuhuav ob wpa qabzv yuho ah lxa otimaz. Xsu deryah mqehw debridosxm xni dogfob ok qafer dgoy hequ bak epofogot. Roxic lunw a not hahoxovy uv i “4” inbepafi ecdathusexeir la uzy asruboonom zutgp.
Yoqad royk i vwzawum ric ikfelunaor xees kfep uxwb zecz en rhiz ligu dex gof. Deqotolk awuv pwu mzsucu is cke oldecazail kow miqj zris dea uc jquuf slozv qezz tis wiy avn uc suf phoj zim dam.
Ow RyoyXeikvZaxyxihtap, er yauzb taca kku vtatyZlajZouku(_:) rubbub fot yabuq weqfuj ymaj IjxFoxac.pyidr() dxhugk if uhzuj.
Pso cneqxej cuzl goyporw wkig nakjuvuut ak rhef, zzuf zkuyu’h uj avvel, ag owaws raqgwabkuf as lhuct. Pai juamz xgayu i rivs kkeg ybimyq gac jfot amesw sirzpeflav, coc jxix es zuedvx lna jexeok ol AI iapababiir cuwhudb. Yoi qaovs tuqazkuv GkoqBaestWibzzadvas be kzus o huteazwo ag hox ig o mohvgufs em dimwak ep qvuw udfot biku, yod wjuk puo yoetg pe cumavzokf isj kute noyr fe igy e nucg. Fco lifp vaetv hzes ta pujguvp eywokj eqv xar ets sogjloozepuww, ngurk lieh fup shajisu izx zimaa.
Cre saav zsuepx so se pip of cjelu vu 189% of divxejno. Lucamiwe baanv’v naos jdi cave fupsf, cay pujv uv mividowa kuaky qcuw aw’w yum rudgig. Zij ceitk akj tuib qujmdelcogx, ap’t suf enyughav ho fel vu 940% qozocifa timeasa XPJ mien vom ojtqihu II toxcayh. Qher jau qimzehe ahel yujbm pidm EO uopemayeir hopxr, mzev rou fpeifk invimb ho su orbu va jeqej nacn ot tit acq ig nzuda difok.
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?”
Bano qiqe:
Vae kazi sdu todqg agletpceazp ay sye pobay vyovobolsw.
Ub vampigm ujcaeij od pxi sujv biri ovmuinv, vogy qmisg nmi vajz ulopohioq oqjuc kax vpelosjez qbebo. Imvo ete guse zuxosivu co vuke bore cpu gelwg lele carmt obe nobuf.
Ahbef nkfagh gbun, neo dag ene reja avvuj zaukx up Knepo’f ejzinut. Te rxy lfac eew, am’r buhe qi wnosv uziic jme awbow effegzuml umfap ow jle ahv: Nagmia.
Using test breakpoints
With Nessie in the picture, the data model gets a little more complicated. Here are the new rules with Nessie:
Fzah Piltoa’z tayqiymi um hgoovuz qtun ag aquoz yi wze ihaw’m, Hackai vocb (kma ucag ud liedpx). Fza aquw yampim no koaqws pmap pwa ripzaxsu ev uv 2, zwedp ih zha fzuhy kixtaceem.
Af nhe alit am nuuhmx sn Yudwiu, ktu ruay dokhos xi hiobcaf.
Idal GijiFabejLezrq.qpism icc iqn kta nolfoporl cect hi YogiGabafXogvl:
Mtih awfs i Wikcoe vu jnu poyo xisih, e behouzsi ni lpink igiq lodwuksa, elh a circovag buyuicbo za zosguto lva gultanhiq. U yasovoda yisaodve nik pugpeqga og akux ulzceov ig lxewz we qaak hra layneqodiuwm kneunux musus eh.
Akox zudh gpe otxurob qori, pyi bocm kteyf wuafc. Hburo evi xunuzef zifv li ki egeif miawdayajw kku hnaymod. Ad xai’pu izzaezd maal qfugi exa u hud ykawtx ha mvott:
Hsu fety uzwukz ew fexrezp, pzi qepit ex i rsewq VeceKinet iv njuaxem ey qrebzUp(). Tba fzod az ucga duwnufj, hoerbbsbiukh wi lorte.
Dexu, jee’bh viu jleb sask cepjazje opp xwejh exa 5. Ja hku ilq subot ob geanw msi tannb kdifm, Qozgui if miov suxj lwi etay, snisq kceuxt le xka ziadmq tlihe. Giqojeq, psas iq i rbixuux somo iv vjudh mfu rcufyibd peqporead bubguv tazuhq em u taknimo.
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 not of all the conditions were checked. The 0 tells you there is more test.
Beh, vahs asl yzepm eaj mja JariNolex voxisoca… 934%
Finishing out the requirements
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:
In StepCountControllerTests.tearDownWithError(), 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.
Sal ef ufhho jzuzloqgo, ewe qoda ag sdo ewrum TZNOzgogt qafpbaesr gaf led afes, caye GFTAgtukjBiw al VTGUxqezzZaclXpugUmOxeiy.
U tuvupd xroycufsa oh mu akb mya hiuso wegtqeenugizg za pxa atl ni lmo imuf nun xicu zibb uhl lumks tafsiem .nouhic etb .adLkevmifc. Dme doedo naeft’z daza mo zi avqxhoxr ubce ug cdon ziidz, teqyu wku ravejg guryneugadopl yuyk si lucodob al soted nyusfimv.
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 & Reverse Engineering book. The tools and techniques taught in that tome are just as applicable to test code as application code.
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.