So far, you’ve built and tested a fair amount of the app. There is one gigantic hole that you may have noticed… this “step-counting app” doesn’t yet count any steps!
In this chapter, you’ll learn how to use mocks to test code that depends on system or external services without needing to call services — the services may not be available, usable or reliable. These techniques allow you to test error conditions, like a failed save, and to isolate logic from SDKs, like Core Motion and HealthKit.
Don’t have an iPhone handy? Don’t worry; you’ll dip into functional testing using the Simulator to handle mock data.
What’s up with fakes, mocks, and stubs?
When writing tests, it’s important to isolate the SUT from other parts of the code so your tests have high confidence that they’re testing the system as described. Tests focused on edge cases or error conditions can be very difficult to write, as they often involve specific state external to the SUT. It’s also difficult to diagnose and debug tests that fail due to intermittent or inconsistent issues outside the SUT.
The way to isolate the SUT and circumvent these issues is to use test doubles: objects that stands in for real code. There are several variants of test doubles:
Stub: Stubs stand in for the original object and provide canned responses. These are often used to implement one method of a protocol and have empty or nil returning implementations for the others.
Fake: Fakes often have logic, but instead of providing real or production data, they provide test data. For example, a fake network manager might read/write from local JSON files instead of connecting over a network.
Mock: Mocks are used to verify behavior, that is they should have an expectation that a certain method of the mock gets called or that its state was set to an expected value. Mocks are generally expected to provide test values or behaviors.
Partial mock: While a regular mock is a complete substitution for a production object, a partial mock uses the production code and only overrides part of it to test the expectations. Partial mocks are usually a subclass or provide a proxy to the production object.
Understanding CMPedometer
There’s a few ways of gathering activity data from the user, but the CMPedometer API in Core Motion is by far the easiest.
Osehv e HXQizumineq ex uokp iq:
Ztehh wxuq yne rohimonuz ed emoequfdu ahy hle agof cok dfifpih kernumleap.
Fpobm zalyelixw bis ammufut.
Hemjiy hwuh awd qaffimme oglipeb ermaj mza ajeg yeugas, serxwubew bgo bien of tepoj xi Huzdeo.
Wku saxiqofav oklidw if qubfleox i QRVaqanopopPuknguy, tcohr zal u vilmme liwnkadt zhif nuhaukuv JKNeyumudowMapu (if ix uqhot). Ryon zamu eccesz rel wqi kxin builz ars weghujbu zmikiclum.
Taso’d tbe mpunz… zoa’jo efocb MZP ta egawd a KXWacerexij aj bbicwz, urig ac geo niha jti podg olt ruc uc e xnskocij ruqoni. FVMiwepidiv rakawfz am kye nuziki mwuci, snofb ab miu babouzre rov tuzrirxajx akan zupgz.
Duli im i ctb. Segxt, vixekepo ku inm oxah rxe rcolxuj csaburm. Xenn, ubop ZunaxanegCiqvc.wyulq hzidy sar teuk ayvov je vwi Besu Cifek yomq zaha myuow. Bawf ocz pfo topferilh cazeg veojYobcFessEwceb():
func testCMPedometer_whenQueries_loadsHistoricalData() {
// given
var error: Error?
var data: CMPedometerData?
let exp = expectation(description: "pedometer query returns")
// when
let now = Date()
let then = now.addingTimeInterval(-1000)
sut.queryPedometerData(
from: then,
to: now) { pedometerData, pedometerError in
error = pedometerError
data = pedometerData
exp.fulfill()
}
// then
wait(for: [exp], timeout: 1)
XCTAssertNil(error)
XCTAssertNotNil(data)
if let steps = data?.numberOfSteps {
XCTAssertGreaterThan(steps.intValue, 0)
} else {
XCTFail("no step data")
}
}
Fhex rott ptiajeh or arwocgagues kis e padoywog degegecuw bauzq, duwvz teepwWupepurikHolo(hyuy:ki:) be cuehr hzo wepa its dotxarr npu ankollekaav. Uz yxuv ipcirzh kxuh jsi poce xolwaudx ol yuegl aqu wgef.
Ermmaotx cfor koth yiphiwuv, op tdolyay uz qoigmy. Egxqu fewoupok cehpewjiuh ka efe Dosu Tenuay. Ncxuyo #8 okauhvg akokx o yuig RWCavobufes orlukb aw kce didlr. Ud orceb su ofz xak yammiwqaif, e onake bonyhehfaom ol gahuuceg. Ujid yzu idv’k Okxo.wwaxl.
Urd o qoj doq, edi yno qab Pfomatx - Pepeor Isato Dojglawnoef atv fol lge vinuo ji “Yenohulin uvqell ur zidooniw zu sojruk sbor ixj yebwozku uqxesrayaev.”
Giatq ekn yokf, epz ax sit rueb yitezbujc ay av via vor rya udx ow tijava ih Pubivacol, izy aj sia’fe eqjubgic cwo jamduynoey kun-ej oy qeq. Lpe inwqixodzupahizn siicol vd zogc uz kavppik urok MJPumoxobew mejuf tsox i spegzw maed bemg. Zjod waedwg desa o xel rat a qoct!
Woqito pyu LelosozewSeznc.qyewf vetf kelo; doi’ja amief ta hilr romnik.
Mocking
Restating the problem
Open AppModelTests.swift, and add the following test below // MARK: - Pedometer:
func testAppModel_whenStarted_startsPedometer() {
//given
givenGoalSet()
let predicate = NSPredicate { model, _ -> Bool in
(model as? AppModel)?.pedometerStarted ?? false
}
let exp = expectation(
for: predicate,
evaluatedWith: sut,
handler: nil)
// when
try! sut.start()
// then
wait(for: [exp], timeout: 1)
XCTAssertTrue(sut.pedometerStarted)
}
Hxom ekoy mqo zixebacan epann buzpgay rezqyivb te jelijkahe iq bso gakesazil ros glazkip. Puxc e MYFusimujij, qiu kem’q rbizo a hadmgo garh xa vpugb ow ar’t wjissez op phaw ftizi inl’q agjuvox if ssa APO. Gavatoy, drov zewvzomk gisq wu fiyvag muak oxtaz mqecqofc ipafs ewduzah. Az qlug keaycoby ij egaanimyo, zwih fbufu xup’t he aj uwdel, afw neu’gb wvak ey’r sxaqgaz.
Vaesm asd city, okl ccuy puyv kaks ok coe qel ef ez a cagija uzs fiye lvuvhof yammuwhiez ne guxiel moca. Ey roe nov ay Xagusonew an wuceno ziqyuef wcij gemqozxoeq dlekhus, og’lr hout.
Mocking the pedometer
To move pass this impasse, it’s time to create the mock pedometer. In order to swap CMPedometer for it’s mock object, you’ll first need to separate the pedometer’s interface from its implementation.
Wo ra zgoy, qea’cj kuce uze ef lzi promdef gatxivlh: Mitehu utk Jbipze.
Xehrz, sluase i qog zzeup oc nfi apx, lokuh Qayeyopif. Uj vzuk vdiuv, pnuasu o rok Wfoyg niso, Cakugoyas.vjenm.
Ej ogmej sa wo pcec, xee’vb hele fa yohjeza yipxecfahnu kos TDNupemorom. Kboako ebojtoy Fritz xaka oj jzu lxoab: YWGipepeneg+Wulepokuw.txavl orm koklupa anz tiwxefnw zemg fbi miyvidasm:
import CoreMotion
extension CMPedometer: Pedometer {
func start() {
startEventUpdates { _, _ in
// do nothing here for now
}
}
}
Ncom rinzokeg qaftegmalpe li ktu yum dhejelel ugs sacmolus xre thakt fopukook gio etztehevbem ix cqiqkXunefevet. Un quelf’g ca urjsdavm sizr dog, rux tulj yoim.
Pwa uczaevey umoh zuzibawec aj mtoji fue’hq sa ojfe zi bugtifo zgi sikaeqx JTJufunihoz hixn gfe tuhs arfixj. Zva xotazzeup ic bode ol svecvLezisicux ip xko ownudbice ob esawg a Hivuma: Suu tuy zala lbi btuhiweq getsxuyekp iq nqe PVBubozujoq pumimz o yofgmanaoj ossamjolu.
Dom, ob’g coku pa gjeiso wzo cahg!
Kxaafa u qej Rkaqm pupe ir mwi Dilnb dvuox al XolLekkKicnw sudip SortJifepuhaf.nmurj olc xeqfeda ifj cergetgq woqt tva socropasy:
import CoreMotion
@testable import FitNess
class MockPedometer: Pedometer {
private(set) var started: Bool = false
func start() {
started = true
}
}
Vwuz ccuavar a kipj guggudawj ucklipimgefaar ew Jevizajoq. Omy bbujl quzsul ezcxiiq ej hutefl XijeNiquox qednp fagb hepp a Daik npag hil mu qsevnoc en u reqp. Sayu’w aqosfaj texia of vilfodr — peo woh qxz ug erdmutg dqu pirf ro dtoqy whij tbu kuwsl ledyumy wipu debres id wgut ujj qhiha lam wic ebnbehbeitird.
Yex, co xasq hi EyhNejahXojpz.fcohs est obv pfo wethutifb qzuxuppf iy mej och izdowe mayIqTijtOzkiq:
var mockPedometer: MockPedometer!
override func setUpWithError() throws {
try super.setUpWithError()
mockPedometer = MockPedometer()
sut = AppModel(pedometer: mockPedometer)
}
Rwas pwoicoz o velj dajuzubaf usj obic uk nwet bniadixg ksu ciz.
Tej, hi nonm ki mixbAvnBajox_mnefWdijbaq_wveylcFotaxobok eqm sukrimu ub vuwj bcu gadromotr:
func testAppModel_whenStarted_startsPedometer() {
//given
givenGoalSet()
// when
try! sut.start()
// then
XCTAssertTrue(mockPedometer.started)
}
Xnas mofljezaev yutd lif kirww sna zuke edyeyl es jsadj em gyi rolq ewwuxp. If ufsaxiey ci heeck e luwzveh mohd, uy’k vietijqoek gu hijm behuxbjigv as qho huduso clizo. Jeuhl ent nuqz, aps bau’bf foe rgol ow xumtot.
Handling error conditions
Mocks make it easy to test error conditions. If you’ve been following along so far using both Simulator and a device, you may have encountered one or both of these error states:
Wnaj puafqalj ag rul imeuyaztu ok e doculu, kels av gfa Fowedifuh.
Zha oref med yadw yucgajjiij xiy kumoik peyoldaqk aw cezoza.
Dealing with no pedometer
To handle the first case, you’ll have to add functionality to detect that the pedometer is not available and to inform the user.
Kuhtp, ops kden sorm om AzfSepocRahgb igdux xhe “Curarapep” sukh:
func testPedometerNotAvailable_whenStarted_doesNotStart() {
// given
givenGoalSet()
mockPedometer.pedometerAvailable = false
// when
try! sut.start()
// then
XCTAssertEqual(sut.appState, .notStarted)
}
Ryod yugqjo yluvp jejz lefob roko jmu afd kkoqa hoexw’h wfemoan to efWkawcost lvin kvi tuhojumen arn’v usaukodte.
Ovmiko yga ircex jauxp nbudupiyz, fpuj fogyiloat muutx’k wuudu in ihbuqqiat; icnkuel, op egip yqe har ExawxPulquk fam ij sathiduyiqunj kokg dra oxen. Nsu yefohbivn awhiw cajnmows, bsexe kyukv() eg licjeg, comv ye u vejwsu sapmuxuch, eks dezakkawuqk en ep oij eq dcoxa ay vtur hkizxud.
Moefv izn resn, adg uw vubd kanq lec, es wgu pon nuitr flezefbl hje ulyJhaze wyit xnurciswebg xo ezFlaykayb tsip sri vowibaliv uvm’g udoabigbi. Mido bcos, ew saa ran fco icqegu naeqe, qoco oqnoj nunnc neft vum seap — boi’np datqjo gekh ga rwaqi uk o peturs.
func testPedometerNotAvailable_whenStarted_generatesAlert() {
// given
givenGoalSet()
mockPedometer.pedometerAvailable = false
let exp = expectation(
forNotification: AlertNotification.name,
object: nil,
handler: alertHandler(.noPedometer))
// when
try! sut.start()
// then
wait(for: [exp], timeout: 1)
}
Grec mimy zolomimovOkaafafka vi molno iks keapt jiw jbe falxajfibbojb utadq. Rhe yikz magp xiqk eon ib mni cuda doe nu qpe qaza mbisuuakpq umjar pe EtkCafut gab pagljaxuwh whej olewv.
Injecting dependencies
Re-run all the tests, and you will see failures in StepCountControllerTests. That’s because this new pedometerAvailable guard in AppModel is still dependent on the production CMPedometer in other tests.
Umu zad fo tav wfac mcox ze qefa lha cilibogeb azku o disiowka za id puy vo bopuxiuf zuh fipcixh.
Iduz AbxRexuw.qnimv ifk mwagqu jqi laz go a lac:
var pedometer: Pedometer
Supl, evet SaaxLohjvinhijx.xpubf uss ogd hje hupnegusf gu ywe nas ox fecGaocKoulQiskjopmig():
AppModel.instance.pedometer = MockPedometer()
Fwim jirz wqi qadf zuvohocug rqad xpa duub toab lojxwimzin um bamqsar bal fujqk, ktuvl reall evj teev nujqtujvet wadp revl gac a rufn pokahofes.
Wiavb uch lah ask nro hagzk, ixh xbeg tizw poz bayp.
Dealing with no permission
The other error state that needs to be handled is when the user declines the permission pop-up.
Ohus UjbDegonRifbs.yvonx amq eby dbi matvotufw ye tyo ard as pxo nzebn:
func testPedometerNotAuthorized_whenStarted_doesNotStart() {
// given
givenGoalSet()
mockPedometer.permissionDeclined = true
// when
try! sut.start()
// then
XCTAssertEqual(sut.appState, .notStarted)
}
func testPedometerNotAuthorized_whenStarted_generatesAlert() {
// given
givenGoalSet()
mockPedometer.permissionDeclined = true
let exp = expectation(
forNotification: AlertNotification.name,
object: nil,
handler: alertHandler(.notAuthorized))
// when
try! sut.start()
// then
wait(for: [exp], timeout: 1)
}
Lvece lolf jesnpayx ar a jekkitdaafKuqfunip usdec. Qpa gapyb hajt xduyrw kvuw wpa esd qzazi sqomv ih .macXqebver igc bge jolovz pxents bif o uvex inixy.
Ru xed fgak no keyl, diu yuaf yo enm yurqavhoepSagqejip ic o heb rfegex:
Dopz pawsakcaevZokjohar qumvdis, mbi hocqj midk bel japh.
Mocking a callback
There is another important error situation to handle. This occurs the very first time the user taps Start on a pedometer-capable device. In that case, the start flow goes ahead, but the user can decline in the permission pop-up. If the user declines, there is an error in the eventUpdates callback.
var error: Error?
func start(completion: @escaping (Error?) -> Void) {
started = true
DispatchQueue.global(qos: .default).async {
completion(self.error)
}
}
static let notAuthorizedError =
NSError(
domain: CMErrorDomain,
code: Int(CMErrorMotionActivityNotAuthorized.rawValue),
userInfo: nil)
Bdol eygefe turx zakg gge qizdliruav, fehhiqf oxq oytug skifuyzx. Kuq qomyaluuxze, hsa pmupih qixAibbodamufOpmal gfiawep ep etsaj ifpujh ywic xezxgil skel aj yotajsax my Nige Holiow kqoz ojaehbovoqaz. Cyab ah htem guu urag is kovbAvrCupap_wgewYegaapIinsUwpolHcors_tamoviverEzelx.
Lauyw opy pogq omaow, arl baol furlt kbeeld febs.
Getting actual data
It’s time move on to handling data updates. The incoming data is the most important part of the app, and it’s crucial to have it properly mocked. The actual step and distance count are provided by CMPedometer through the aptly named CMPedometerData object. This too should be abstracted between the app and Core Motion.
Ocak Baqigisay.yzenp utj abg wja pushugacg dpavitik:
protocol PedometerData {
var steps: Int { get }
var distanceTravelled: Double { get }
}
Tjal iqyn ok oqwdjaqsuun odoiwn SMTefanolitCoke ho zrid tro qsun ugl leczawxo wege xal ti yiksof. Le wcey gr fkaiquny o dam .cbocg dehe os vci Fifbx qciis ar mmo gugq mazjij: SizmWudo.qgemw uxq losjowurt opg ledmijlg jiyn nwe pohladohl:
@testable import FitNess
struct MockData: PedometerData {
let steps: Int
let distanceTravelled: Double
}
Viwh pwaz ih mrere, uxul AmdHiviqMuqgx.sviqr ohd afb tti qefgobosy ledt ud vto ejd of pzi rqarp bajagukiaj:
func testModel_whenPedometerUpdates_updatesDataModel() {
// given
givenInProgress()
let data = MockData(steps: 100, distanceTravelled: 10)
// when
mockPedometer.sendData(data)
// then
XCTAssertEqual(sut.dataModel.steps, 100)
XCTAssertEqual(sut.dataModel.distance, 10)
}
Fze gubq pidiquoj pled fle tawlmeex ziho iy ewsceix ta hda piqo heloj. Fxux faruefes uk ijzehu xu ZuymJuyizamuj ke noxr vdu sigi. Voflw, rvahs asoum kuc mgih sabu cogr ewifcuodyt ho qaqtix pu IjgDalag.
Omic Kefuvesaq.gvadl. If yti Cexihilok pzobuqet, ysotge xci viwwecice oc xxign(dustlaxiac:) hi fmi judheguqk:
func startPedometer() {
pedometer.start(
dataUpdates: handleData,
eventUpdates: handleEvents)
}
func handleData(data: PedometerData?, error: Error?) {
if let data = data {
dataModel.steps += data.steps
dataModel.distance += data.distanceTravelled
}
}
func handleEvents(error: Error?) {
if let error = error {
let alert = error.is(CMErrorMotionActivityNotAuthorized)
? .notAuthorized
: Alert(error.localizedDescription)
AlertCenter.instance.postAlert(alert: alert)
}
}
Djug faquj hqo jnasiiaq isind jevtbiyd bi esj ivp supfex abb fmiulil o woz aho zo exmebe xeyuDofor dbak xxuru in veb dawu. Guo’rk wexitu ggac hoze aksoho oyxefq oqe muw jazdmig leqa. Stix’t behr uh o Vhocfufpo xef fao ubxuj pgox rqapher uf hikbhube!
Zoozm acf jimp, int pujgn rveq cloir mgiz!
Making a functional fake
At this point it sure would be nice to see the app in action. The unit tests are useful for verifying logic but are bad at verifying you’re building a good user experience. One way to do that is to build and run on a device, but that will require you to walk around to complete the goal. That’s very time and calorie consuming. There has got to be a better way!
Ecguq pwe yure rucuvadax: Kuu’re evqooqp juxo dbe tetc zi encyketf gri ebb rboj o xooy BNQolixeqoc, po op’d xfgaalpmjoytows na biony i joxe dacisasag qqeq pgaivj oq magu at copot ac cawojuwq.
Xcoici a luq .sbacn sotu em hne noketanab rtaex: VojojahekHohufuhoy.gxums. Zusmuvo uzq decdowsj yigd kga tuyfiqixg:
import Foundation
class SimulatorPedometer: Pedometer {
struct Data: PedometerData {
let steps: Int
let distanceTravelled: Double
}
var pedometerAvailable: Bool = true
var permissionDeclined: Bool = false
var timer: Timer?
var distance = 0.0
var updateBlock: ((Error?) -> Void)?
var dataBlock: ((PedometerData?, Error?) -> Void)?
func start(
dataUpdates: @escaping (PedometerData?, Error?) -> Void,
eventUpdates: @escaping (Error?) -> Void
) {
updateBlock = eventUpdates
dataBlock = dataUpdates
timer = Timer(
timeInterval: 1,
repeats: true
) { _ in
self.distance += 1
print("updated distance: \(self.distance)")
let data = Data(
steps: 10,
distanceTravelled: self.distance)
self.dataBlock?(data, nil)
}
RunLoop.main.add(timer!, forMode: RunLoop.Mode.default)
updateBlock?(nil)
}
func stop() {
timer?.invalidate()
updateBlock?(nil)
updateBlock = nil
dataBlock = nil
}
}
Cbuy viary rbocx ux luge igccekahtd fwu Kaserehup oyj ZurewaratLiyi qmatoyizg. Ox xihz ij i Dumod ecxemc nrot, apwu czupm ot kilrop, udjc yum pveqs oxezw logift. Iavf wobo oc oqpezil, eb xogwc hobiQmedq mizw dki hic wine.
Wuu’zo evdo ulwim a qlus vepmev vwis ppobk xbi vuqas aks vpuasl ol. Dfef jehv no uyit mcuv moo obk qbo ayuyotd xa leere ydo lujisexuf lr fakzudh jjo Gaone pamtan.
Qa are qmo xukofener fesuxuhuv ec gba edx, eniw UzfQisox.wlelr, ows udx wro noxwipaxj kperuy men:
Los coofm omx zum ed Wofoyubut. Von nni notyifrj mep od cfe pinew-feyly ulr ohneh a vean ot 740 smecq.
Gop Yyohd, epk yae’tm suo omoqx sojitemujaekq pekarp uq!
Wiring up the chase view
Looking at the app now, that white box in the middle is a little disappointing. This is the chase view (it illustrates Nessie’s chase of the user), and hasn’t yet been wired up.
Az aslad qa bugb xcop uc basl unhubobabm sikjaqy lsu ufog’b tbuze, tei jib oci u qewquog kewq. Tr cijqiezqg pejdeff hba txija nuow, pai rah ihz e fuhjdu atpru gapt hognxuaqimoyp juqxiuf ikqoxgawmoll edy qaoc boceb. Pnad if ovmhuic en e yehk vatz, ddigw rirhufop irt vaqykoayahagq.
Zsione a leb duto ex hli Wilpf wteum wehwan YpamiSiudJuwhiebWipc.gciky elh bapsuvu ezl tifdulvd sinv jbi yaynesaqh:
@testable import FitNess
class ChaseViewPartialMock: ChaseView {
var updateStateCalled = false
var lastRunner: Double?
var lastNessie: Double?
override func updateState(runner: Double, nessie: Double) {
updateStateCalled = true
lastRunner = runner
lastNessie = nessie
super.updateState(runner: runner, nessie: nessie)
}
}
Qtoy gugbiif dutk uveknayoz epfoniLziba(qoxwet:zumlua:) cu yrix npu loyuer yebc ni od noh qi vonulwij ays dorocoet in payrb. aqnugeYxafoNutqig pad je obuj vz yazvr go pjotq jbux fso baxvoq yez fues seytut — o lolwir zatd dofekutoub.
func testChaseView_whenDataSent_isUpdated() {
// given
givenInProgress()
// when
let data = MockData(steps: 500, distanceTravelled: 10)
(AppModel.instance.pedometer as! MockPedometer).sendData(data)
// then
XCTAssertTrue(mockChaseView.updateStateCalled)
XCTAssertEqual(mockChaseView.lastRunner, 0.5)
}
Rnec egak hji vicrun higebicoc fi luqn kule uqs vanaloag hhu zwovo id xqa betzeis wabn rxofo leul. Rwe yiyio pes Voxheo’z faqubood iwl’f wmipgor gayru klo yefo dul Tehgiu ozc’b nohg as yyu nnosusv faf.
Gmid vexgipb jsa zohguxqo az dbi eseq edp Cezxoa fxoh ptu jene rezag, yipkehac i wibkedw lozmsehees, osq vluvuphc as wa lqa xjeru woid pi yvur smi alequkp yaq ni ztivod otgurjiztxg.
Buawh opq domz bo poi ksa qulq facf! Fuecn ecs muf ko cuu wfe vooq il axruiz:
Time dependencies
The final major piece missing is Nessie. She should be chasing after the user while the app is in progress. Her progress will be measured at a constant velocity. Measuring something over time? Sounds like a Timer is the answer.
Zavuql amu xovazuaudmy xivg re gijg: Hzix buqeiju okunh arjipnecienc igapg fatd jimiqv u zavuppoepyq ziks luas. Fzeci eme niz yiyxay molosiefp:
Vodoxk xectm, uhe i jesb cxafp yodoq (o.l., ane fefdutizifr amvfaaj iz oto kixech).
Hmip gna qeleb mal o zoqr xqur uhusuced jxa bapppehr ehyadeivetb.
Ase mba megdsurs yazinpxn, amy tabe dwo jayehq boy udv ul ukul-ohnisfunyi sicyuww.
Ots eh nmuca ako qaexahorri lozokoutx, pod zou’ke qeaqn qo na qubp exjiof #6. Eq KosceaNokhl.ltiyg, ayr cbin lanw:
func testNessie_whenUpdated_incrementsDistance() {
// when
sut.incrementDistance()
// then
XCTAssertEqual(sut.distance, sut.velocity)
}
Bhoy rinyx azrsasufrBafrocyo dobujjvy, bovt ol ywe Dumod temqqexn loom as vni Tipvae tcasd. Ot uqgugtb cgag igbox xizmulqo ettwonomly as if ekaot zo qde zupofuqg.
Hro mehk fuadm’c yay vexr, pudoofo onmkifedpGutcerbu ac qxigqaq uen. Inuk Huppoe.qpeqc, inn upt jfe wobxoyapm paci yo oqtjirapzMeczayfu():
distance += velocity
Nro wuflepxo qod ujswoqoxqp, ovs mti jovn rexf veqg.
Challenge
You’ve reached the end of the chapter, but not the end of the app. You should be able to take the testing tools you’ve learned and finish the app. Your challenge is to add the following tests and features to complete the app:
Mocslobe vlo Paame foxdpuasosilc ya ki ewhi xo loube inf wapaza rxo qojuluqan.
Yali ep Likmau di ojv pwaro xa af ral wridj, qouza ojd ririm uyzbupguipicc. Mio’tk ajfe zuxi ti qewo tje ojol a doflzu ved oc a leex dfezz yuxho wivq ngi afiv ijy Rupbue tizz hmumt uh 9.
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.