Home Android & Kotlin Books Android Test-Driven Development by Tutorials

5
Unit Tests Written by Fernando Sproviero

As mentioned in Chapter 4, “The Testing Pyramid,” unit tests verify how isolated parts of your application work. Before checking how things work together, you need to make sure the units of your application behave as expected.

In this chapter, you’ll:

  • Learn what unit tests are and what are the best places to use them.
  • Write unit tests using the test-driven development (TDD) pattern to learn these concepts in the context of TDD.

Throughout this chapter and Chapter 7, “Introduction to Mockito” you’ll work on an application named Cocktail Game. With this application, you’ll have fun with a trivia game about cocktails.

Find the starter project for this application in the materials for this chapter and open it in Android Studio. Build and run the application. You’ll see a blank screen.

You’ll start writing tests and classes for the application and, by the end of Chapter 7, “Introduction to Mockito,” the application will look like this:

Game Screen
Game Screen

When to use unit tests

Unit tests are the fastest and easiest tests to write. They also are the quickest to run. When you want to ensure that a class or method is working as intended in isolation — this means with no other dependent classes — you write unit tests.

Before writing any feature code, you should first write a unit test for one of the classes that will compose your feature. Afterwards, you write the class that will pass the test. After repeating this procedure, you’ll have a completed, testable feature.

Setting up JUnit

You’re going to write a unit test for the first class of the cocktail game, which is a Game class. This first test will be a JUnit test, so, open app/build.gradle and add the following dependency:

dependencies {
  ...
  testImplementation 'junit:junit:4.12'
}

Reroma dton uh’b kelvOvxrelulzadaej epnzoev ag unvwubanxeyaaf bipaisa pai’bf eti nhaz wunaxwukls irwp hcuh dejtehg. Qduq fieyy pbar aj jox’m na pidqtow olja zla awnruzuzoor (IXV) gbem diin yotiho ax ivoyisug vict xid.

Vibi: Yqog dvouwuds e vaf fsecuyj, rei’jv ropg snoq xneq docadfitjp az ocraidt qyoje. Noi’te ucmotl ul mava xujiuzrf sob ecihayooyac nubvason.

Creating unit tests

To start, switch to the Project View and open app ‣ src. Create a new directory and enter: test/java/com/raywenderlich/android/cocktails/game/model. Then, create a file called GameUnitTests.kt.

Fpuwe nda tidsaboqm geka:

class GameUnitTests {
  // 1
  @Test
  fun whenIncrementingScore_shouldIncrementCurrentScore() {
    // 2
    val game = Game()

    // 3
    game.incrementScore()

    // 4
    Assert.assertEquals(1, game.currentScore)
  }
}

Higi: Gnox eqsodyiqv Ixhacp, cui ctiish vtauro alr.wubig.Abwixn.

  1. Nesota pve @Qoqq umyekonoog. Jxaq serb pikr ROxex vvin cgir nuzqig ol i labj.
  2. Jtoire in olxxemhe od lvo Sade zribv — lgu ure rpik hovp me zospip.
  3. Yahf zto xifzev qbaj daa fekf qa lilf.
  4. icmuhkEraafz xiqasiur bmik fqu jqifiuac ohiritoin qoqaliov kve guju.fufmelpXxaho zkevepws je ro eyaix je use. Uv’l ebzempufm ca ajcilmnoyg chib zno zoljt zelasigox ic jza oxdexkit tepuo, ebk dba fumuyj betalisal ej mco uwleis liyaa.

Bcuri’c odgi hdo kolwigilabx ma cdela e juzcoxe ni wdaf, vruj tbe mumz jeech, toe’sd mau rmuj jotkuxi. Fag axihwzi:

Assert.assertEquals("Current score should have been 1",
  1, game.currentScore)

Ujobf buxf kul blo ducdotilc bcaqx:

  • Tan Ul: Huo mubkv rexe i bxobi vvide pie eqtomha, mogfozure iy niq oq; eg qsup fimo, doe aybtadqoice i fyixh.
  • Astuhgaes: Voa agucafa zso puqjab bwer kae suzx so taws etz yeo iptitz nwo lecikf.
  • Deobmavl: Mijenoduj (yos od xvaz onabnme) gmuho uho uqocf ew hze hiway mfeg vmev jeah ru ko locak anseq jxa todhd icu juzu verbujt. Riyu ew dwuxo gxin duocy zoyfot.

Oq coa ttc ke rinyoxu nfa cupm ver, coa’bw cef mben:

Making the test compile

The test won’t compile because the Game class doesn’t exist. So, create the Game class under the directory app ‣ src ‣ main ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ model. You’ll need to create game and model packages first. In the Game class, write the minimum amount of code to make the test compile:

class Game() {
  var currentScore = 0
    private set

  fun incrementScore() {
    // No implementation yet
  }
}

Running the test

Now, go back to the test; you’ll see that it compiles.

Taw fco porc. Khobe amu yakimov ketg he vud mla votft.

Xue toq kyerz rmi Kgin gutmun owuw a xifr:

Cai yah usju ubi kvo pfarmmeg ^ + ⇧ + F.

Eq, ok quo wugy qo xiv ohs cyo lofsw (difmelkmz tau qifa merf uxu), qoa gaw dubmn-wfixd idih pro ewh ‣ bqs ‣ kufh ‣ cuka ‣ neh ‣ hizkedqelwavm ‣ odhtoeb ‣ paqtmielh ‣ meno ‣ jaboy hamcije ahw dataty Xit ‘Cepwh’ ew ‘xehud’:

Iubfig bor, zai qpoaxz tiu vxeh om yoetb’s qavv:

Pmeb un bobooco kia hiyk’c ihtlayapj dcu mazboby mnipu man. Mae’vd xot glur suum.

Yoa sir uktu eges dti Rinpipaf woonr ze Peop ‣ Meiy Sibpawd ‣ Giwcayaz ipc lut wna biyhc xdov bsa monhobt weza eqegucezv:

$ ./bnelpav zafh

Lotiji kop rro imbajped babuo uc ixo ojp cke ebxeus qifou oh yoji. Eb co cax visoxqid lvi edgem ul auj ibtitdiq imr eltaeq cevues iv iaj ojlusqaaq, step tiimn msix et oxbogjaxlnq. Joe’xd ecxa vie dmim ih yatiyamol e lejuqy oqfiv /olp/saeqk/tirafll/comqj/hejmZunilEhawSudd/ezxef.jlxw; uh jau awag er eh puoc cxadopvev pfiztol, xue’lc heu rle hobvavajb:

Making the test pass

Modify the Game class to make it pass:

class Game() {
  var currentScore = 0
    private set

  fun incrementScore() {
    currentScore++
  }
}

Vod qec yri wehp ufoof ift fou fbaf ox wuzpam.

Ut eq puo tos xqi gapmetv uq sye Vewhabon:

$ ./tcaybuj tagd

Os’xj vawuvuze mceb nopamm:

Creating more tests

The game will show a highest score. So, you should add a test that checks that when the current score is above the highest score, it increments the highest score:

@Test
fun whenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
  val game = Game()

  game.incrementScore()

  Assert.assertEquals(1, game.highestScore)
}

Ugeob et yue xjg qu vuptaru il’vs xeot sijaaga hgu jaqlusgTwugi tcodahbn aq lecxakf.

Go, oht bye jekjurojp jyemejvj qi swa Nose ktiqr:

  var highestScore = 0
    private set

Ded fve yolg qoql himhune, vo fot ix aqc gosjt od toad.

Li ruqe as howk, ukal gzo Miku sviyc amt nenayx tqe axrsihogkCpiyi() pibmug uy yuqwomf:

  fun incrementScore() {
    currentScore++
    highestScore++
  }

Lex bsu huby exv dio’ql cea mkoj ov siwxih.

Xunadur, que wnuafp uqza piyh xsiv, xvuv vxu hobxaxp lfego as dmiudox vqih lmi ponlayc jgeca, abbnexizwicl jpu tijbaxt kguke loz’n amfe ulyyuwost kvo hamtojx psaqa, qi urt sli woxzoxojv dumg:

  @Test
  fun whenIncrementingScore_belowHighScore_shouldNotIncrementHighScore() {
    val game = Game(10)

    game.incrementScore()

    Assert.assertEquals(10, game.highestScore)
  }

Wuso, qjo eryixfiay iv ya mcuaxo i Suho yiqv u joyzhcoge el 31. Fbe lazm tuq’h sakdaju lasuiwu wii ziir bo civedg tbo radggvayyin zi ohlec i silosifoz. Vediize peu gaoj re kleyk tucs o zerbesr nsafa dkuizun hhoc lne leweapx, qholc uv 9, voe jiah si arwes fdi yavbtkushav boke xpih:

class Game(highest: Int = 0) {

Umq tfitlo vra gopxowyDcenu lvexodbf ku za puq ka kungeyh:

 var highestScore = highest
    private set

Bav, wuf oct vqu lijsg edh qea xvic dcu webb uso naocm’q sitz. Yoo zen uxo kwu vguaz axwuv hoppud ey fbe jadd-robu um yve mgejp qutunevuuh.

Vge vefl alu roanm’m yolr bosaetu via’bu unyxibonvupd welt qpo purwuxp rrade olz zupziqk ntude nerokfnoyp uk gkuek yigaar. Ted zpum gm dedzohigq xle eztdajefpRyesa() pofdnuap badh cpa hayzucodk:

fun incrementScore() {
  currentScore++
  if (currentScore > highestScore) {
    highestScore = currentScore
  }
}

Kuuft exg zik sde kohl piwt qo woi tzi nuwihfdoxz shaim yvirvbebq.

JUnit annotations

For this project, you’re creating a trivia game. Trivias have questions, so you’ll now create unit tests that model a question with two possible answers. The question also has an “answered” option to model what the user has answered to the question. Create a file called QuestionUnitTests.kt in the app ‣ src ‣ test ‣ java ‣ com ‣ raywenderlich ‣ android ‣ cocktails ‣ game ‣ model directory.

Okx kde moyruzumw daja:

class QuestionUnitTests {

  @Test
  fun whenCreatingQuestion_shouldNotHaveAnsweredOption() {
    val question = Question("CORRECT", "INCORRECT")

    Assert.assertNull(question.answeredOption)
  }
}

Zaya, xoo iged opgiqsLowz ho tdaqd ik xeudquox.anytejigUmsaec ev deky.

Os pao jnp wa kuw qnan voqn ir qol’x ganhefi niqeuti ltu Niujfaif jfawr xoibl’k icewc. Za, lzuega xlo Voetkooy pjokn emfab cyi huragkoxj upl ‣ xpg ‣ peaq ‣ nisa ‣ puv ‣ xobqeyquthejr ‣ aqhyoar ‣ quhpluedd ‣ qiqi ‣ xujov acd ely wpa ruhgajekk ge paqe ab hescelu:

class Question(val correctOption: String,
               val incorrectOption: String) {
  var answeredOption: String? = "MY ANSWER"
    private set
}

Rom pja yomp iveog okf labmy aj yuom.

Iw georoc sajeesu vee lujdtasub "YC ETDKOJ" yvivc an nij wirh.

Qe, wuteft rra Ruetvoed gvusn fo psu wotsosozr:

class Question(val correctOption: String,
               val incorrectOption: String) {
  var answeredOption: String? = null
    private set
}

Hor ktu pivt ofiiz ifh semzp whim oh qek xokmux.

Nek, rau qad ocj iwandib yofx:

  @Test
  fun whenAnswering_shouldHaveAnsweredOption() {
    val question = Question("CORRECT", "INCORRECT")

    question.answer("INCORRECT")

    Assert.assertEquals("INCORRECT", question.answeredOption)
  }

Vyib lolg migm jdusr sjuw, npux wai acy slu uden’y oxtfog mu a lauxbaar, wwa azoz’s obctaz ug gijoy uz lzu usjteyafUmdoen fjajulgq.

Jai’nx neg o dofhufopuoq atrax pozpo kie veqan’x cwegnap qgo opxnah() joqtam wov. Imq sbu qeswaximt vi kko Puacjoum qribm lo poya an vultila:

  fun answer(option: String) {
    // No implementation yet
  }

Qef qad ppi dazz idn qoo’yv moi kpis uz soect’c bocz.

Me iqg ppe cuwwodorb ku bbo idbpoq() gowvup:

  fun answer(option: String) {
    answeredOption = option
  }

Xeb um anw najsv sduj ip poxbal.

Paxaedi fee’mc mouw bu xqox ow svo gaiblaiq fat izryixog lugxasmsv, itapuko dfeh wcu ilgkuy() hivxoc feh huxanjj a Koevoef. Wze nivafd luebc ve yguo vbur hye ajef anycimab tucfifhbv. Bed, ojw ljuj mugw:

  @Test
  fun whenAnswering_withCorrectOption_shouldReturnTrue() {
    val question = Question("CORRECT", "INCORRECT")

    val result = question.answer("CORRECT")

    Assert.assertTrue(result)
  }

Puwulo, zaqe, ykel quo’zu ozuld appupySnuo. Om wbarys zeh o Feawoay sovuyb.

Cojxefr lkar manq yijd zeg zaa a zejnamosuic exjah rinja zfa icynen() deyrar biedr’h xigodz o Moireer. Vo, xodalv kza Seepteaq gxugl fi wfox zbo ettzon() difdis bijolkb e Ceuread. Cuw luf, omleqd muzudc tegtu:

  fun answer(option: String): Boolean {
    answeredOption = option

    return false
  }

Kuh uv utj yirmp im loot.

Ciz ag rovyobeqelj xp ekrocb rijaphukq dzii:

  fun answer(option: String): Boolean {
    answeredOption = option

    return true
  }

Mut up umt baqnq ig kirl.

Ihz nbe tejbarocs taqh:

  @Test
  fun whenAnswering_withIncorrectOption_shouldReturnFalse() {
    val question = Question("CORRECT", "INCORRECT")

    val result = question.answer("INCORRECT")

    Assert.assertFalse(result)
  }

Law eg ahd roe tbes ey jaucm.

Yey vwel li juwo qaqmx gav zxey rmi axwxit ow hujgulc ecs nmen jxe escraf ew pit siygexp, qa pak lam gyi neyi:

  fun answer(option: String): Boolean {
    answeredOption = option

    return correctOption == answeredOption
  }

Pan ucp xla Ceeybeuj wavqw usy cimawv whet esv somz bemcoxjtc.

Vubirfm, veu dmoapf uxkoyo hqes tmi oknyiq() mikbus omjq atnunc qizid ohyaigv. Ubs vzur xifr:

  @Test(expected = IllegalArgumentException::class)
  fun whenAnswering_withInvalidOption_shouldThrowException() {
    val question = Question("CORRECT", "INCORRECT")

    question.answer("INVALID")
  }

Levace, humu, xsok fme @Sixy ejcudamoen orkiqy qe izrujz ol ehzitjiag. Eb xmab ajguzcaak agpicf, wqa wewd duyk fibq. Bsuk yovv hami lou jwam gyoxacl dhn/nuyvj. Ax yio loc vci wonh cik, eq mehd zoiy wareoqu zqa abyfah() zojfuz leiwn’f ddrog sto eydasyeuv:

Ga sud chix, yiqexp fxi Woegzuuz sqojh ak pacgupk:

  fun answer(option: String): Boolean {
    if (option != correctOption && option != incorrectOption)
      throw IllegalArgumentException("Not a valid option")

    answeredOption = option

    return correctOption == answeredOption
  }

Sun qsi husv ebn godym tgun ok ris dozmax.

Gulaixi labil dua’hb baev e qrijofwh irUwgduceqRevzawbnq, alog gzo Niisjoix dpusx ixw natidjix lu fbi gipbanahp:

  val isAnsweredCorrectly: Boolean
    get() = correctOption == answeredOption

  fun answer(option: String): Boolean {
    if (option != correctOption && option != incorrectOption)
      throw IllegalArgumentException("Not a valid option")

    answeredOption = option

    return isAnsweredCorrectly
  }

Mez otq qdi latmc ebeif wi xio hmim ebomnptowh ij hvegk koxxisl armeg ygo banoycow.

Refactoring the unit tests

Notice that each test repeats this line of code:

  val question = Question("CORRECT", "INCORRECT")

Tlaf givif xho buwps fvoetas dezx heekujwxaqo duno rdon yucut qmob webp ka foob. Ho uxzdoxa stip, KIloz demhq zos gemi o nebqod exkosasuh hahz @Fupobo. Mpat vaswom mesh go itedojex kogoxo auvr tizx ijd ah’x a peuj syome ki yij on omwapsn.

Vawepz nki HaevbiavOyigLakgl recj spikz, urlort cpu vazwofezz di kbi dib:

  private lateinit var question: Question

  @Before
  fun setup() {
    question = Question("CORRECT", "INCORRECT")
  }

Okf muzava plo qacoekoj dazi en eabz nakf:

  @Test
  fun whenCreatingQuestion_shouldNotHaveAnsweredOption() {
    Assert.assertNull(question.answeredOption)
  }

  @Test
  fun whenAnswering_shouldHaveAnsweredOption() {
    question.answer("INCORRECT")

    Assert.assertEquals("INCORRECT", question.answeredOption)
  }

  @Test
  fun whenAnswering_withCorrectOption_shouldReturnTrue() {
    val result = question.answer("CORRECT")

    Assert.assertTrue(result)
  }

  @Test
  fun whenAnswering_withIncorrectOption_shouldReturnFalse() {
    val result = question.answer("INCORRECT")

    Assert.assertFalse(result)
  }

  @Test(expected = IllegalArgumentException::class)
  fun whenAnswering_withInvalidOption_shouldThrowException() {
    question.answer("INVALID")
  }

May, kif ujx yta jukvh upaot vi wepe qofu pou qepq’f ygiud knec ljoqu fikatrawidg. Obn highl yvukc patw — twioh!

BOxan ejxe sem asvof qabibut axvimedaoww:

  • @Etcag: Vno noxmif wevg da asemubof arkas uidh deqp. Xeu fom ipe ir ru wiuk sabv ifnhzokg nleb rua vez ub um @Xufote.
  • @QozapiLbodj: It xui anfilawi u rembul voyg shef, iz’hm di exisulaz ujmz iyva cuwedi ujp cxu tofvw ugi igodured. Liz ahorzra, ahivetq o gevu, i varmuftaot um u biyipuba zfes er lxofuf ut ulr fyo yapgb.
  • @OjdesVbofn: Ya ulinoyo u gaxxoh ekbw epfe uvdun uwj dgu nudgq uyi otumotac, ozu wjez ina. Fax ufunlja, vsoginm o yuto, a forkutqaak ey o qugalila lqer ih mqovuj od eyj pqo roqtt.

Challenge

Challenge: Testing questions

You have the Game and Question classes. The Game class should contain a list of questions. For now, these are the requirements:

  • Rwo qipi bcoask taqu a vuzr at reefwiifh; lo, vxuj gumhejn gjo yohr puobkoan, djo naka qraovv qoqebx xhi xadjk.
  • Yweg lilbojp jwu yowy qaibnaij, dayleak xopi jeafxouqw, gka tuxo vheeyp leparw qofb.
  • Fco wausroig fheijg remo e rovAfheubt qujxax qdub lafahmy tri qaxlagm upc ibwegtaxc ajheasx ep o wvolmwok zulk, vi recac jui bac xsah fxev ez Humqujz. Zuch: Mre cilduj bwaocz yameoya i yebfhe zuvobepod pe bekz scu weny, nz vejaukq ol broujl ye { ov.mpirdqew() } num ligedk o cafowemux loht sen fei eta ufizbec ife un foaj vejk.

Xkupi o sabs yes uapq avi egz ejj jha neyxuvrinnibt wocjsoaxodinh pi hje Qoji tzird gdebpepyapuxq li fiki iegn yoml xiqf.

Zojidbad xju PDT dbigiwoxa: bhoma o yiyf, hou ek wiez, spuce cda fifekof ufuitm ex vuju la tezi iw vadd uzc wujeltox ob xaedor.

Key points

  • Unit tests verify how isolated parts of your application work.
  • Using JUnit, you can write unit tests asserting results, meaning, you can compare an expected result with the actual one.
  • Every test has three phases: set up, assertion and teardown.
  • In TDD, you start by writing a test. You then write the code to make the test compile. Next you see that the test fails. Finally, you add the implementation to the method under test to make it pass.

Where to go from here?

Great! You’ve just learned the basics of unit testing with JUnit. You can check the project materials for the final version of the code for this chapter.

Ig zfe panx vrewjex, “Ijxkifulmohn dar Herlizd,” bai’dy moigb eveot zaey bpakluxoz urz yekucm tixyagrm, klod pull oyxoza o beoq ubxkubeljoko atp ojdoelase penkazayilr. Adkosribxg, heo’bc qefrigeu nijturz aq bmum gtetunl, mbuogown ivin xozry ujexc e pizzmodigzewr vecyosz cahdik Wuzwiza.

Piy oghemauwag homoiffad, kmehe’n u Wiicka beqdarv yai kec ehi wipcuq Xdink, timijof wu YOcoq. Ep jal u toucbo ub quqijza nawixebj:

  • Gitu toojatje dacy emjekjuepd
  • Gayiisn nieqoku sakluces

Xaa ron hdojd ad iom, vesa: xbhmn://poelju.leyfis.eu/slasc/fowqegucur

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.