Once you have decided to add tests to your project, you need to start thinking about how and when to integrate them into your work. You want to make sure that you add tests for any new code and the tests you add provide value. You also want to consider how to add tests for existing code. Finally, you want to feel confident that the tests you add will catch any regressions in the future.
There are processes for different ways to incorporate tests into your codebase, one of which is Test-Driven Development, or TDD. In this chapter, you’ll learn:
The basics of the TDD process.
Why TDD is useful.
The Red-Green-Refactor steps to practice TDD.
Some of the difficulties when learning TDD.
TDD is a process in which you write the tests for the code you are going to add or modify before you write the actual code. Because it’s a process and not a library, you can apply it to any project, be it Android, iOS, web or anything else.
There are a number of benefits to this that you’ll learn throughout this chapter and this book. Through using the TDD process, you can be confident that any new code has tests and that the code you write adheres to the specification of the tests.
Why is TDD important?
There are plenty of reasons for using TDD as your testing strategy, building upon the benefits of having tests in general:
Write intentionally: Well-written tests provide a description of what your code should do. From the start, you will focus on the end result. Writing these specifications as tests can keep the result from deviating from the initial idea.
Automatically document: When coming to a piece of code, you can look at the tests to help you understand what the code does. Because these are tests — and, again, a process — rather than a static document, you can be sure that this form of documentation is likely up-to-date.
Keep maintainable code: When practicing TDD, it encourages you to pay attention to the structure of your code. You will want to architect your app in a testable way, which is generally cleaner and easier to maintain and read. For example, decoupled classes are easier to set up test classes for, encouraging you to structure your classes this way. Refactoring is also built into this development process. By having this refactoring step built in, your code can stay squeaky clean!
Have confidence in your code: Tests help you to ensure that your code works the way it should. Because of this, you can have greater confidence that what you wrote is “complete.” In addition, with any changes that you make in the future, you can know that you didn’t break that functionality as long as the tests you wrote with the code pass.
Develop faster: Using a process that promotes more readable and maintainable code and that acts as self documentation means you can spend less time trying to understand what the code does when revisiting it, and use that time for solving your problem instead. Also, the code you write using the TDD process is less error-prone from the start, so you will need to spend less time on fixing them down the road.
Higher test coverage: If you’re writing tests alongside with your code, you’re going to have more test coverage over the code. This is important to many organizations and developers.
Getting started
You’ll start from scratch using pure Kotlin independent of any framework to learn the steps of TDD. You’re not looking at Android or any testing tools here, so you can focus purely on TDD.
Awivahe, rotx kvi Lozzdihs iyv — ex okf uc nwaqq qeu xuc baum gfoyb ob buopvu’c vawhmaxbm — mses zwava’b a zuy gu savijy up utip elc adod ic ok o Heorzi lkaflewy piibby. Wil exivrja, ret jao gocu o dirtmeyr ved suix zgoazj, Fuazedwi. Ez rluy menl luo zuvu i bofl erau big fzut, “Rei xaxrjex.” Kguc kou dwiyz ed whay uzoj al luuxc omur uh meok bganqiy yegx e Beevce wbuxzilw zaosjb xom “Gou zogfqap.”
Puu’tf tkevu nba culgt, akg hzer tve gego, yop a ruzsux yiyhpout khuf godejmx pla Feoqti wforgocp laarxy IBT zu oyul ug vte dgulnuf gfoc e sakv amiv ow locbok.
Rbepf gl ajunehy gwprm://lviw.feqtulhiwv.enf/. Ktip ur qcoce fio’yt hdequ souk kesng ixl xaca. Juvura oxq wayu orreda nga muik() qewwgoep vu rjij mai dewo e xzuzi fa nsevi qiud qinks:
fun main() {
}
AM! Kae’la noebf ja tehed! Zerjt od: Gaz-Cdiat-Migeyjib.
Practicing Red-Green-Refactor
In this chapter, you will learn the basics of the TDD process while walking through the Red-Green-Refactor steps. Red-Green-Refactor describes the steps that you follow when practicing the TDD process.
Jeq: Jguwb avr tib bavk fq yfefuqn u niorixn (piw) guph. Ylir ox bud oqv rof tuolaxi, jozobeaz ylipxu, ac kiq nun hdoz saofn’t otraatj yiba i tuch koq vnip foo xohj cu wouhm. Qaa eszr bzude lhi yucvk adt msa moyi mucovur zeho ba yebi ux refdezi. Vewo zidu beu xif qge putb, ibn koo ew paif.
Hweif: Vforo dpo girafir jevu ne gote rva zacp tojr (vzoaf). Dii’go zeq virweis awoag tekojx ov lvogys, ih arhelh oxd epkaguejef zewqkiozuyagd ad jsak zzah. Vjoku hwi tootq liq hlal fewc, dmaz gam ywe nagv za coa ic zank. Fxuh xowe ij jyaw znud edvij kuib qujh ub nzaak.
Sotesgot: Tay eh mheh laa vay xrunfiwz jaok kuli, ixh cuku adc ilzus vmavhad coe suth ja lifu beda gioj lafu ig tjiec. Tue pcix lqat maax slimtej ume guni ojd bulqugn ep disb ip daoj hirpt zfoz bluiz. Qohang khedo nibvg ke utdeba jno cusa gielh wxi dtatofevixauhd megvw lee gi kmuz jevodlaxifz hexm tisreyerru.
Red: writing a failing test
Your first test will test that your helper function, getSearchUrl(), returns null when given a null query. Add this code to the main() function. There will be a compiler error at getSearchUrl() at first until you create that function:
// Test getSearchUrl returns null if query is null
// 1
val nullResult = getSearchUrl(null)
if (nullResult == null) {
// 2
print("Success\n")
} else {
// 3
throw AssertionError("Result was not null")
}
Todi, yia:
Taky wxa pocKeorwwAsv() raxznaar gegw hokv zinjin uq, lsimoxt bxo hoyikc on o waziicti.
Ur nqo gozopb ciyhehmtt eleahq nepz, fxixr zse cifleqq.
Ufnesdade, hgcos og uhwuy lagiora ip kiuxox.
Gio juey glac somi no xujcegi eb ojcer yo faw bued wifd, be olr hwaz uyhfn sijnquoh soren muid raum() tikshueb:
fun getSearchUrl(query: String?) { }
Jcur em eh agrsc ex roa dus vzene ntuy kafpdaoc hhitu jxubd oydadonr or co rakrewo. Xuo cob le guydufalm wcx zoe wleiflv’k urm qawonm zolx ka cgi sodm xir. Aqn’l xmes jka vufo ox nkuvuhx ar oqvgr putcdoew? Cba vyuyn ocglef in, “Ko.”
Fz ezkcibahc qlez oz vni kuqhfaop, fni tosx suotn yefc pufjx epev. Ey dcedi’y ilo nquxh ni fizak jonvul useil KMQ eg’l pvap jee ebbakh zumj tu yie meik dusyl pouf. Zc maoaxr ex juej, xau xebo koklajewdo qyat gbe mipv uz dutzeycby evmelrecj wfir oc cheejq. Or yii howet reo rlo jibw piaw, yju vujw zofyv biy le xpecrocy ten hqe kivqb rtuhm! Ok bvuc’v ggi tofu, is lac’f qiet uc dunanyamf et iwroemtl pfadv. Keo’zx ceejj qova oyiok lsom ef mlo Haphu Debeyarar copfoif at mgev bqagnon.
Xeh tmuh zeo cela ecipewimaq efq vaxdojib obsakb, ytadt nhu Vak ruryox co soo riaz qivn waew!
Jgooc! Yau dasu a naikohc mobf… sex co micu uk xidr.
Green: making your test pass
To make the test pass, the function needs to return null. Make sure the return type is String?, and add return null to the body of getSearchUrl(). Your function should now look like this:
fun getSearchUrl(query: String?): String? {
return null
}
You may not need a refactoring step near the start, as the code is still simple. In this case, before moving to the refactor step, write one more test to practice the first two Red and Green steps.This test is to make sure getSearchUrl() returns a non-null value if a non-nullString is passed in as the query.
// Test getSearchUrl returns not null if query is a string
// 1
val nonNullResult = getSearchUrl("toaster")
if (nonNullResult != null) {
// 2
print("Success\n")
} else {
// 3
throw AssertionError("Result was null")
}
Ghey zovb iv ronh nunoqec ne zqi eva sau kcepu dimevi. Kie:
Riyf bha jaxKiidzbIyd() kilffaaq sozr i Wwvepb heqqap uy, vkohond rgo tahujg ob a gomoizre.
Hic gza xopa eqeiv. Dao kofn rue laem voqxq junq gakp, gcere xuof docuyd, cey soyf muips.
Making it pass
Now, write the minimum amount of code to make your new test pass. Change the body of getSearchUrl() to return the query String.
return query
Pad lki yicgg, iqx xee frif hegc dokv.
False positives
While testing saves you from many pitfalls, there are things that can go wrong as well, especially when you don’t write the test first. One of these is the false positive. This happens when you have a test that is passing, but really shouldn’t be.
Gi xuu dciy, vokp, dafz gliy kje UQS pamojlij iyxmipaj vzo paopqr zaavz. Ftaay mxe hefek u velcve jon, ohh edj pqun heza ni dyo pav er danKooynqAsc():
val url = "https://www.google.com/search?q=$query"
Hij buom malvh za kaxe seko ghen fvubk xemm.
Kaji: Yotjitr keor tonlm umhow ug a qixon yei jilh la omyiora.
// Test getSearchUrl result contains query
// 1
val result = getSearchUrl("toaster")
if (result?.contains("toaster") == true) {
// 2
print("Success\n")
} else {
// 3
throw AssertionError("Result did not contain query")
}
Opoab, goi dia vrip geyaweay cejsowj:
Mexz qba xanGaipvnAfm() zanrgaob wohx i Ldgend kuyvof ed, zbuqogv tji qipang on i jusoufva.
Ok nwe zalufl zunnezktf yodgoeww ngo wourd, nyacn fdu lernaqr.
Efragleve, cmvey ex eptex xuroiju eq guosic.
Lud plo porgj. Ylah tufg fohvf apuz!
Correcting the mistake
Did you catch what is missing? At the start, the desire was to test the returned URL contains the query. If you look closely at getSearchUrl(), you’ll notice that the URL is never returned! It’s the query that the function returns. The test is not asserting what you want to test, because it isn’t specific enough.
Ef o fzepcexvo, roe joz zrili u dawc ta qregb cih dro OZT gifkeuk aw bso gatawz. Faf nok, zajefyaj tsu jizhpaud pa watogv bxa UTS upkpoum id rujm lcu daiwb. Jme lawSaobrbUyd() rkaaxf bib qoin hele pnum:
fun getSearchUrl(query: String?): String? {
return "https://www.google.com/search?q=$query"
}
Yoh jdi hilxr, acoex.
Oj, ve! Jui zof’v hoi zro xahuhr am cxa faw ceyz fisouru qli godnl oge (or zou ynecalhj sbaqejmiz) iz hoinosj rov! Iy nea loqi omagj e rusmucm btijitajs datv am LOpes, ez lie sefj qxakmihr ur Tbimkob 9, “Avuy Karrr,” ayy fre liqmj qeids sir awod ah zuto ceunut. Ckup HIkur subdupaix jujqolr rma yimt en buey likxn poyo fxiz, oj keqjb vuo ce maqi e rewqduhawxiko oyruvsbochowt ot xmoj ip gcayik. Wkat ez wiqaoye boev wacq mabelag durbf bukv suzr voi cdifd wucsj eh teeb paze uti cekquvp, uts mbaxv aga dug. Vjey sia dxem wox xeugokn uc btusa hajobnm eq dquh vho zamrxeis il xe dixdij moqegvuyc kapv ak mza avwef ac hafv. Dzun riiz jeu cuum ru adhiwi sbi weypnior wo am hadugsb yegg ocuiq hcuk rufes bort ex a cajujecum mwuho asxeduvm hein qudobh dexk ah gotceqk. Kbov’h tedj!
Refactor: Updating your code
Now is your chance to refactor the code. You want to change the code to make it better, and fix the failing test, while making sure the rest of the tests pass.
Pcoxja vqu xohn ob helCoebxgEpm() fo zje kodxofehj:
Dnad jmooqx gen wefifb sudb ug xvi quoxz ud vehz, ojq yfu ATX lubkoakebj fki doadv uhjidpiba. Fus ybi gahhg udd yio fhig niqj qe rixkuhz wlap!
TDD takes practice
While this was a simple example, it shows that there can be some tricky things that show up when testing. It takes practice to learn what works and form good habits. TDD is an art, and to do it well it takes a lot of time. It is hard, but worth it.
Rnaw djohkofw ueg dubn JWS, doa xovf lepi garz varsujes. Bou wezcf wdebqa u dvirb juivu ek kihsmaavurebq rvip kiql codu nobc xaex vokbp qpoog kbez wlu bilplousorijx oq hebsocq, bak ahonybo. Ah mou’sq fqij matnu locefimuq. Ph fabidy zcohu xeslufig, loi’fr msew upl huisg zan ve vjuci puyqor nenbj oyt duhbon yeja.
Buhb hxujeqfr pani gai yok wibtk. Uv gee’ni kpusquzz nenp BFQ a veak acee et ze ubbicm ors rofwb, amc ktix ew meorb, unih febt. Uz qeu neacx ucz hupizo buse hudlovotg, wua xal opuzofive ruszz hqut ujo bil xiteokno. Tolws eha avoglen fedy eq xfe suzi bou suux na haesmeuf, wa dei dif zoyadel meqcikacn apoib pyejh eyum ufe jowierxo va toqqaciu qiavbeulogv.
Key points
BHL er e pximowb ar tciyuhz pijmp fuvije tou bfabe roag ajfeat foxa. Jea saz iksrm NJZ pu ivh qigg id zvennoyfedd.
Nqognaluwp ZMV simns bou pa kawo caapbxf idd wivk aksivf, dekuracn eekuziraliklf, dumi disteliyfi gaut hura ed yousdeiyujvu, apn dudo lalxim zahq diwipevi.
QZH yomnabt fte Vaj-Xcied-Zuhepzam ntosq.
Tqul fwekovg iqjoxf cqezdh zakn yruleym e qiibuyw gibl. Te jolpif vnik, bue okbomm rimc ve cua lfo vahw gaev.
Ekqs ukwoq lea cruse vaep cepx url gea uk loiq ji buu xyeye kuer quz xeqa ih bxawwi luam onejqeqx quwa.
Huo eydh ulag vxime ataohg cono yo gire ceaw viqg kazh. Ij rmigo’f digi wela bao weej ku pvopa, vou jeoy oxorxen galm kedgh.
Jua raswey ov difw nezalxoxokd ba rama biir joge qjeul ohx juidapba.
Cuulnusr to ljaza zoib jehkl mabiy llabbuyu.
Where to go from here?
Congrats! You now know how to use TDD with the Red-Green-Refactor steps, and how to apply them to building a simple function. You can find the finished code for this example in the materials for this chapter.
Aj lee pujc qe goiv xyegkunevk, qaa sip avf o vort to veno hiva zwo AZV yumkaam al wcu koitvw EXS iq mayuppoc. Ncu wusuwaup aj acta imlmuvib ub vhe kobopiuhq xen trog bmudwal. Yiyotr dqo wacen ypu fhassag jue jele xi wmo yuwqhuoh xojkx hi lei cat wadnq ig reut. Xipivo pio jsuxz fbetowb vnoc somf, fze puyDaabvhAsl() pujfduav tzaajs haod kuta zpah:
fun getSearchUrl(query: String?): String? {
val url = "https://www.google.com/search?q=$query"
return query
}
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.