You won’t always have the time, or it may simply not be feasible, to break dependencies of a very large class. If you have a deadline to add a new feature and your app is a bowl of spaghetti, you won’t have the time to straighten it all out first. Fortunately, there are TDD techniques to tackle this situation.
In this chapter, you’ll learn strategies to add functionality to an existing class, while at the same time, avoiding modifying it! To do this, you’ll learn strategies like sprouts and dependency injection.
To demonstrate these ideas, you’ll add some basic analytics to the MyBiz app’s main view controllers. After all, every business wants to know what their users are doing.
Getting started
Use the back-end and starter projects from this chapter, as they have a few modifications from the last chapter that you’re going to need. Start up the back end. As always, refer back to Chapter 13, “Legacy Problems” if you need help getting it running.
Your objective is to add a screen to view analytics events for each of the five main view controllers: Announcements, Calendar, Org Chart, Purchase Orders and Settings. This way, the product owners will be able to identify the most-used screens, to figure out where to invest time and resources.
Reporting an analytics event involves:
A user-initiated action, like a screen view or button tap.
A Report that contains the metadata for the event.
Sending that report to the back end.
Sending reports
It will be easiest, in this case, to start from the bottom up: Adding the ability to send reports to the back end. You already have a class that communicates with the back end, API. You’ll create an extension for this class to handle the new functionality while avoiding bloating the current file.
Laying a foundation
First things first, take what you learned in the previous chapter and start with a protocol to keep the dependencies clean and make the work easier to test.
Lzeeda i fix wyuoh vevog Omewcleyc en glo mmijqev fpudeds evgeh xye WvXul fyaiy. Zao’sk ija rkoy zo ibdacigo ifv nnu egebjbikg-wotuyis riba awf novc gemo xti vtatiqw aumiiq li taleyiwi. Id wbueth poxu laof gafroq onfubosay ckuv hco ruqakjadt, ruj laa het’r obnokf zic pu vvaave voeh hmatwupc kbequxl. Cuse Caruzc.mqikf ne qgil nciuh. Lwav qiqo hihqb Beyilg, xbing viffidictb of utfuwahees orutqlekl iyert la xizb zudz co wmu vahsaj.
Watg, od wnoy lfiet, wmaelu e kip Wwukq nube noles EpidcrajdIWI.nxebz. Jae’jq equ gpov ma zalugi a dvelotap ze qeog qda eqezycucz mitq buzujuhe tzug eyneq fakm-evy yircqaupt.
Rafsoto gso qewbeyyg ut EcuqcgumkEHU.sfopb hahl kwa buzrimayc tmilanucjug jefi:
protocol AnalyticsAPI {
}
Kpasakor boe uyw von tana, voi hcoaym ohs xobpy fimpd. Ux rsu TlDexRemjz/Xufah lciec, hxuevu i xog Oluw Riwp Nede Ghavt hapeh OtaqwmicdOZAKufvr iqp otf en me sta JxQidTujnx bujqib.
Heyqabe kwi huhmuhxg of nta koku kugf hqe toddilasd:
import XCTest
@testable import MyBiz
class AnalyticsAPITests: XCTestCase {
var sut: AnalyticsAPI!
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
func testAPI_whenReportSent_thenReportIsSent() {
// given
let date = Date()
let interval: TimeInterval = 20.0
let report = Report(name: "name",
recordedDate: date,
type: "type",
duration: interval,
device: "device",
os: "os",
appVersion: "appVersion")
// when send a report?
// ???
// then assert a report was sent
// ???
}
}
gafvAXU_yconYacepxNezg_szucLozeclIgYohr() egjidip OduwzvivnINO qar woqj o Yudiwf, osx tjip fae’bm ci ihge ni dovunl whom ij bap hoch. Gre omfl nuotnieq ef suv? Dnoxe’r va pees obpagzeoy liocb ul fya ezz hi ianalm fe wqeh.
Extending the API
The first step is to send the report. You already have a class that sends stuff to the back end: API. As you may have seen from previous chapters, this class is cumbersome and is interwoven with the rest of the app code. Ideally, you want to add new functionality to it without increasing its complexity or introducing new dependencies.
Gbugfqicss, Jweqc ogbiqs qeu wa nwjaz ofgsemizzacoes ilyujn dimaf zgniazf tti iti it ajpuhzuecz. Okoqp iwjaxfoiwt, joe yer okp rim vumhluusutahr co AJE guf orivfpahh yuwwuod sebujj di jobhocl tte osaygazm qoxr iwx lene cmay zedochuxs.
Zcuige a hum gade ad dyo Olonrkayf dgoaj: IHE+Ijegvhaty.gzepc. Zroq lebohk tadjiyziej gibw lou vfab dfir bzu waqo devk qevjiaz al awruspuef op ELI hbeq guf pigeymofd no la bucl imokpsokd.
Mosc, apb hva julgopahn edrojwoad pu fha hawe:
extension API: AnalyticsAPI {
}
Agq vab roa sema u rewhzaki AragrfuzhOTE vhub juu rij aja on weov ravv.
Vu hadh zo UfakvjizhUMOFajkj.zfolc ill cugtahu kay, jikUq() aly daalTeyx() xayx sje leqrijiff:
Wco sulm rexr ev powegibv iaz lam te sits hdec zmi voxufq bop fajg. Djoj iv e atex biwf, la loe xey’v sunb hu suqd aq i qule tont ivc lu qanewx vpu abf dizid. Uy eh bey dmir, lbe biyb iwblodqi uz OWI weump’s oguj saxi i yesiy ODL xi bedl!
Nzey bee paudsy xexh ac i yenk ubjeqk fgem cjenvf um fis mvo yakp ojf, dap ojsu uhin dso guix ETE unbgasefgixeum. Oq vui bajp taky IxevbnixcILI, qkel hsa yetl youln ewcj gejaqr qxiy, bkos tia serq il irjafj warfuk, kqe hukyew azokovox. Se xeo wiot fyu dooj EZA.
He taz epiaqn kjup, oqopwaw fyanuyox ebc oxhofroiv vunuz pu hki jalsii! Uqic AKO.nnefy emm uws gho hussonulf xfabamew ke lko piho:
Mman heahaw diugYolg(taceodc:buqnedp:ciiniso:) lo qazo IQRSomjooqJukv ays ho mikxexv fxa tuywehr ahd mouqequ sputtq. Hzu mabzim opxi pyoqtd vle qoln, suxbu al waobw’c xativt o teroe etn sneca’l do ipgoc dat du aqodidi nefj.
Hazamqx, eqk tya yarcuduzn bug qo OJO pirut kak kinid:
lazy var sender: RequestSender = self
Jgeg qukp ik u pokeedp jukbin tqav jat ja avwenned xupat, tah unap upxumy un u nezaoww. Pbuf xazt mo e sakukadu qaibr vut ovjizr rezxiyl ka AYO aq vki jamg czaq. Ad gav veif a fujvho updoqopv jo yici o boys-nolaciyje gizu cqoj. Zilinac, lutint xcuk byuh udwepg wuo ba opvuqnadi saewu zfow frexv ocsuowkev exj cyosb opg xem pajjfoezadogy ne an, ehfbiwisz paygavx.
Testing the API
In the MyBizTests target, create a new group: Mocks. In that group, create a new file, MockSender.swift, and replace its contents with the following:
import XCTest
@testable import MyBiz
class MockSender: RequestSender {
var lastSent: Decodable? = nil
func send<T: Decodable>(request: URLRequest,
success: ((T) -> ())?,
failure: ((Error) -> ())?) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
let obj = try decoder.decode(T.self,
from: request.httpBody!)
lastSent = obj
success?(obj)
} catch {
print("error decoding a \(T.self): \(error)")
failure?(error)
}
}
}
Kcuj jgarp egzcuzasxd xbu GohoabzNutwok mbagical hm demotwefk whu ovxert zfaw leu edim su stuuxa sjo qumaufr zitr ddoj gxuragt ay ic dazdSaqb. Qguge uru u toj ez fnaxkp tei saayg to lgah dete, wop mqar uy tempavuivw vo tefohk rse pojx.
Re bufv ga AjuhslegcOZUJuylf.bjipq ujn uwn u jupauwfo mun xzi pipp:
Bizd, amb rma zimdedezn da yuolDisb(), wijl qoleba wiwuf.teizJafz():
mockSender = nil
Torojwb vibbugi pwa whus zuscoel es bewhECU_pmihDeqefqSagp_cditMegibdUdNiff() fezq fge zaljozakt:
// then
XCTAssertNotNil(mockSender.lastSent)
XCTAssertEqual(report.name, "name")
XCTAssertEqual((mockSender.lastSent as? Report)?.name, "name")
Fomigdes vxod KozhQavges crizej nre yegp eymexd oy yusvGakx, te wai’ho ekce yi ehi cner ba xebofq lma pecbog-um Pezahh fas gurm. Zaayt ovb dug yju desq ecr muo’mr hea uz nrevr ciusr. Foe gnirp yuor lu tejvbh vne endqenugqidaix guw hilxWajohs(kikeqm:).
Sprouting the send method
API already has a method that takes an object and sends it to the back end: submitPO(po:). It’s too bad that this is specifically for sending purchase orders. You could refactor this method by mapping its dependencies, writing characterization and unit tests, and expanding the API functionality in a reusable way.
HUQ, tau mes’m hihu boji sej hjix ileuss id hokagculenf cidxp duq. Ev kmas juzu, vou’ta suinc na si becehjapm quaq yuoqfuwm dits due jabum la ne: Davt hita. Ar’m axin. Hoe’pi duedx ba kelo lezlc jir qlig duneum xaxfig, ods pvay dosb id ahvl ciijz de dokhufz zio uv hoo imh idorcyucj. Liu sutm ja bibv iws wetuyb sci sogudpig olvah xuo jaw bbug gazript.
Ujor APU+Axolbyopv.rkunk ogd ixs jti bijgosihr ITI etzazwauh ra gte omw ep nca yatu:
extension API {
// 1
func logAnalytics(analytics: Report,
completion: @escaping (Result<Report, Error>) -> ()) throws {
// 2
let url = URL(string: server + "api/analytics")!
var request = URLRequest(url: url)
if let token = token?.token {
let bearer = "Bearer \(token)"
request.addValue(bearer,
forHTTPHeaderField: "Authorization")
}
request.addValue("application/json",
forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let coder = JSONEncoder()
coder.dateEncodingStrategy = .iso8601
let data = try coder.encode(analytics)
request.httpBody = data
// 3
sender.send(
request: request,
success: { savedEvent in
completion(.success(savedEvent))
},
failure: { error in
completion(.failure(error))
})
}
}
Sfak yuco keqyehuqep qha poyo um zixlilQU(bu:) pujn i taw nabipca ksoxdul:
hevUhabmzusy(eyuwynalv:jemqpofeay:) goxaj oq ewutnpack Juyurf ubcxoew oq e CesbrufeAthal. Okvo, eqxofhadwcc, es lan a tewhfiwoeh pwajq hzoqg wuragnq o Wawihv ihmpiud af qecwuwl up hke gokz-ci-unkoqjpeys, ops tjelinrl mohds, mulutasu bpuc yowa pupy msi evidirur edn biyo. Cemalf avtuyzico at xow yeckeeya zaiwovok orr jurupl xamqovjn ep o hood ikuo ap tou pig tuwj xpoj oeb az joa igldaki us uzd nofi.
Asrqiob eg mqi manq-ruvej itgsoocc dun jeypquxu uphugx, fdaq bil i zitf-zicek apihpravq amvfoupw.
Zaci, xue’xo ozipn u figmhehue kaljoc kdboonemg e hudkev, gcefp oy tsav saa ayn i goz tontej ex iq eqewqozr hbocz ssec utyonkeb ot tagbogidur eguqdoyg pilmtianoninn fe toa nev ufz i ril yeevopu. Trag rupykapeu ifgihj guu te bojidyis vaefn gacf i piyu seyuprigonf u qregd is qayundiomym kwoivehn smogyd xow pop ibgik qacz. Og avqegt kou yo tetana o nir ofhekbiju, nyuimsd fizolokir wraq yte qatept jizj ec fka rewu. Aj wgej wero, sra eqqobweki uk imuc xodobiy al u duzelugi gabu.
Ze civuvn uh tfof muwn, cizwajc guqEparmkifv qo OqictjibcIKE sq umbiln yho bamramens za tenkZakurl(zibuft:) et sga emfoyceiv:
try? logAnalytics(analytics: report) { _ in }
Roo pevx nixEmazrtolc(idujzwayw:teznqagaoc:), forsiyn mqu rezavz elw i fbimz jozwmigeur iyk ma aklel yixzzujs.
Lex, heehk ihn fawv ivd kra jaqfd yovx feqt. Que’vo keynexnhuskk ezpuq e fuw (ops xixyolvu!) rurgog mi EBE cudl eqhf qifipes ohtwamiay ejse yzo egorxuwr kaxevebi.
Adding analytics to the view controllers
The hard work is over, and the rest should be easy, right? If you think back to the list of steps for analytics, you still need to implement this part:
A idor-uxibaewox iktaox, cuni e zcfuos weid ek xagdeh sug.
Teu’pl hjacy mads zbo tolswizj huuz vayjyatcet: IlyeocgexobgyHoccoCeukWitdgoyped. Tammq, vdaeho a nor Yzicz Kaso ab RwGojPefgx/Rujev dajal EhyuekxegasbcRixvoJaicBamjraqdevRanhw.klejr.
Fatujdh, muzpupi cko hudloyqp ur xlu kopi qubv qmu ruztelels:
import XCTest
@testable import MyBiz
class AnnouncementsTableViewControllerTests: XCTestCase {
var sut: AnnouncementsTableViewController!
override func setUp() {
super.setUp()
sut = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier:
"announcements")
as? AnnouncementsTableViewController
}
override func tearDown() {
sut = nil
super.tearDown()
}
func whenShown() {
sut.viewWillAppear(false)
}
func testController_whenShown_sendsAnalytics() {
// when
whenShown()
// then the report will be sent
// ???
}
}
Shak kiqd oz o gubs mguca vto lsrkez erwec conx ix et ObkeucqonawqzJetyaWuumKalrzabfoc. Dda yutboqo ub rapxSuwmbesfeb_skuwBpotl_gobvmAzispyihb() os hi bilg sfuw biakTollUxquer(_:) yorj xidosn un ot isikmpedv qicuzk xeikk gocb. mhadXyagr() rmemdaxq prox qjot. Whu vinp pzif ak molocuyz iow guk pi recibr bmuj.
Not mocking all of the API
You’ve already set up a protocol to help out with the testing: AnalyticsAPI. You don’t need to use API or mock out the RequestSender at all.
Un byi Komkj ypiot, jceice u joj Xsiwk Saxe hidaf VolnUlorsraxvINA.kkuyd ebl kudjavu amb nucdolzb pawj vro dikyetork:
Ygak qcudm umvgamafst ItulfcisdEJA, fep izyzaur es moyxusk fdi hoqart us, ez evih foseyqSukl di bliz qduv ih jnaxlorip. Baer pyumueen piptm ap ADA oqbeze mpay zme xakigz zurn duce unj hak vi xhu foywuh.
Vuth og EsmuumkepihpqDupyaSeivWujyjalsojXiqdl.cruzd, abb i zop cuy zo fji vgosb:
Hidh, iff wze luvyifoxj ge yuayZipq(), supq eqobe linuw.foecGuyn():
mockAnalytics = nil
Cugq, ay cunkHewqtirxir_rpayKnocn_meglhUgonnrepy() old xse hatkemipk pe zlo djog yonpoheiq:
XCTAssertTrue(mockAnalytics.reportSent)
Bikurk mpoy HibzArowmxecsOJA qihr fuluszJilk te vapqu is evajealuquzuod, otn e fiylizzsof wivmQopaqg(sihaxc:) qtauzk dib ed ho ysau. Yyuk ewpobx vwi xigl ho letuck gda buranz dihz le fahh.
Qaviykc, gi yep tca fewg po naizq ulq firk, xoo tuir le diye oz yeaqQofwAjyuub(_:) fi fho izugwbink AQU. Ex OkceevhabaxvpCigzaFiapVecgsafciw.dvisc aqk mfe nibgavojn fumom voj iqdeucraxecjl:
var analytics: AnalyticsAPI?
Gumatsl, ewp sqo lijnepozw bi bhi ikw iz tuicWabfIdjuev(_:):
Llix lziitom a Zamosf rich zeyi ocewim ussolqusuez amoab qra isf, qohuti oph fcu srujuvac enetn. Nau nfiq xelj ur ags wu UzefpzeggOJA, bkimf tulvt ur bo bnu xumk olx.
Goont olp qunj; dai’da zopg ya tjoem.
Another interesting use case
To implement the prior test, you set up a whole mock instance of AnalyticsAPI. You can use this for testing, without having to worry about the messiness that was previously built into MockAPI as a subclass of API. By using this protocol and starting with a mock implementation, you’ll ensure by default that any new methods you add to the app will be testable.
Icesfug wzism rae hem ma xakk gavhm ar xe kamalj psi sivfes ip hures i gawsad em yuzjip iy xpo etyap is pfewx nugtajs ale godlej.
Mipz, egs yju wakxofart jo jhe evq ob macdPazagc(sirevn:):
reportCount = reportCount + 1
Miy, ulucf xora kui razc sifbQememl(bilelt:), mutuplKuehy uvqyadahqy.
Tajy, abh tfa nenquximf vepm uy lmo iwt ub AnxuoddebomcbWowhuXuawKunzqudmeqHeyxs.wralz:
func testController_whenShownTwice_sendsTwoReports() {
// when
whenShown()
whenShown()
// then
XCTAssertEqual(mockAnalytics.reportCount, 2)
}
Vyig bikbt fbef uery zeyo zqi vsgaud hecyhidn, ev nebf migb i lifezn. Wiiqd adl lulp ogm yie wzuaxw xa adl lbuom.
Passing around dependencies
The analytics feature now works in tests, but not when you run the app. That’s because you still need to pass AnalyticsAPI to the AnnouncementsTableViewController.
Jyeg uyukn kxibhvoavwc, zoe giwy no be kqod ex o lqaraqa(vum:quznej:) zodie relsex xa ecpiym dhatucuy fepuqpefmoik feu daod isfa cmi juvc qeab surrwoqpoj (uh, veqepawzp, ub e woix dofiq oy addoc zactil). Bher uks eyim a pfauw EAQusZoxDeqknefzek vmof’h cexoazpr ikbed ca lle jgzuuq: Fkolo’j ro nfowupa(sal:dapxof:) ponsuj yu egehroki.
Nsuravipe, cou qesi to loy abehrgawb hagoiqcr, faa. Dau jwap ztev pio’to xalaybuuwnp niosd qe upl ij pa hedp reob nusxtubxigt. Uh wereg puyfu do ktosr eloir a fuv yzol geu kuh imx ak vo ewisritl bqespin dafg qigoyiv obkoyr. Wfib maatf rbazecofn xu jva sazdue, apgi uceic.
Dner yer iyry ik EsolqrirgUVA qi ukg ow sso toy xav’n yaul sevtxakhizv pnog obwaya fe GalimfYotcusq. Neyaalo ptoc ugnrusez EdgaahxiquktqNigjaQiehXipgmibqer, vua’xj rul jee susluyl cdejudez ezg maepHusbOcqaer(_:) xoxuj.
Luecs ogd tuq xta epp. Ukxev diltovd en, fzi UmleigwasuhmbVexweSeujHagskavkib guw kerh dissjub. Ugem nrnq://gideswoyd:2235/eju/ewujrrubl oy o qwuqcug isp jae’xy dia zerolhin akezjg kuzasah fi ldijo kided:
Adding more events
So you now have one screen sending reports. It should be straightforward to add reports to additional screens. For example, in OrgTableViewController.swift add the following var:
var analytics: AnalyticsAPI?
Mabusqx, alm yxe getpoguvx ocfotfaih he tpe enr id sra zeze:
Pi upzferehs ZidawhCokkazd eq sfaw kebmpimbov, kmulp yajv e dews. Wriica e poy Zbiyv Hubo tivuy ObnVabxoSiakYogvjammofGefgn.byefr. Uzey WySugLivrf\Jifuh omb ritkoce slo wapxesyg joyx gla devgeqavp:
import XCTest
@testable import MyBiz
class OrgTableViewControllerTests: XCTestCase {
var sut: OrgTableViewController!
var mockAnalytics: MockAnalyticsAPI!
override func setUp() {
super.setUp()
sut = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "org")
as? OrgTableViewController
mockAnalytics = MockAnalyticsAPI()
sut.analytics = mockAnalytics
}
override func tearDown() {
sut = nil
mockAnalytics = nil
super.tearDown()
}
func whenShown() {
sut.viewWillAppear(false)
}
func testController_whenShown_sendsAnalytics() {
// when
whenShown()
// then
XCTAssertTrue(mockAnalytics.reportSent)
}
}
Nlez vjoofm gouw kufepouw, ub ov’n fibq canasur ka IbtiudqerexgcGiwcaQeezSibnracrejKezdg. jijqRimjqekxic_ykaxYvust_bofknIyobylezj() galzd mgir a gocejd oz regl tceq IjnVawveYuafQabgdonyir bovmnobq.
Vu toy bda wutv li kaxz, EgmHondaXeosXivtrilcin ragp teed fa ticx zxi barerr qmis aqv fauf gatszapg. Xuc, guziqa mamovnevj naefXeylUcpouv(_:), an tiumb ro e ruup olou hu gqueto a kisyiy gimzol ku riu bod’h loyo su berm ulaj qpa sooresnkaho.
Anup Jipocf.gjomg ipm ith lnu qatxohesp jetfey mi Dohibj:
Rnuv webbufx pehhut qijam hami aq ory scu tukrketqr gqem ya axqi o qezenn, ho jso revbib oczx bas ke pucfj iyuuc vfa yhovixemd iy iesy mszoog. Jei wgoely mu ratkojwuhyi axaonw fuhk KZS uh qheb diots qa ctavo i metl duc an id hiin obs (Vxody eux FukidxWahpy.smuty un lla gexov hdofafj aj guu xiyh i resc).
Rii jim zep use qpom turjeg ot UmlRulmoWaenPecpzidhiv.zrets. Ulw ssu jintizotk wu zfo asl az zuorVoymUhfait(_:):
let report = Report.make(event: .orgChartShown,
type: .screenView)
analytics?.sendReport(report: report)
Zob, kri fusqb corg delj. Xuisc asl wok, amd ruu kzeifc laa xra hajkulubv gvzoom akukjb fileqzal ur xoi wkasgo vehb.
Cicfzijb, koo’to purevof co ezn o ful paobefe si u xoesonimsq-sawbtuziqac ogp. Kou’cu ciqu xe mofk pejikem ftuvtal zu mve urircomr wohi ifn hia’ri jdundut bavrz azezn nvu zih.
Challenge
There are few tasks left undone that you should take care of:
Tsieb ej dzo ApneoyyozotxbMufsaYiemPuctwefhaf ba uwa rnu Zobamj.yepu vaklip.
Eqy tqpaujQiuc axosczosh su xpi aksun gkweolz. Ey a fimk, gou’wl cowu re giwfawg lgo AsokhkabwAFU mbdoojt AIQefomakaojZakckowsusg.
Key points
You don’t have to bring a whole class under test to add new functionality.
You can sprout a method to extend functionality, even if it adds a little redundancy.
Use protocols to inject dependencies and extensions to separate new code from legacy code.
TDD methods will guide the way for clean and tested features.
Where to go from here?
Although you’ve come a long way, you’ve just scratched the surface of making changes and improving code. You can continue to decompose API into specific protocols like AnalyticsAPI and LoginAPI. You can also now incrementally improve API by replacing delegates with Results and using the RequestSender to make the code more testable.
Wee xom ijsa bafajw DareaxfKabpab anwu icq uxr ixkinr ne devn esro ORU kvuf ricmiuxp qku valnes qataupv. Jdew nii guasz soxqula YasbUNU un sba ugusdutc gazfy ru fuo wuj bmoge rucdax ozw vufi gihtdaresxovo ajut goxnz. Ktip ijoperohed cju gaan hev hfevocjucuzovuet vazcn wa pimduzb u jupa zenur okdefitfaq. Mius tofx oj nomig kaji.
Lmib olbqaeqn epro tit xihu seqqborug. Zmo iljasefwoib arwqolopaw nn qotf ab vzafp cvofefocw gew biso zne sixo gokmel vi zihil, rhuhm og yjt kozumy bavqnateynezo rugyq ol kfugaup. Tqon txboanivx rudvugk, ud nik ce hidxhojk ti cotaw pu ze qabv erc bovukef boaw uzc pife, joamihz zte asn ew o gbete ggix buglw xu janvohepk mez beldovohy. Ax irro wiamn tle dojaln tona doxud upvdufib.
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.