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

4
The Testing Pyramid Written by Fernando Sproviero

Traditionally, software testing was done manually. It consisted of deploying the application to a test environment similar to the real environment but with fake (test) data. The Quality Assurance (QA) members would perform black-box testing on it — without knowing the internals — and raise bug tickets. Then, the developers went back and fixed the bugs.

Even nowadays, without any kind of automation, this is happening on the Android ecosystem. Testing usually consists of compiling the release candidate application, installing it on a physical device or emulator, and passing it to the QA team. The QA members would then follow a test plan, executing their use cases manually to find bugs.

You’ll find that automating these repetitive use cases is the way to go. For each use case, you can write one or more automated tests. However, first, you need to understand that there are different kind of tests and how to classify them.

Tests are typically broken into three different kinds:

This is the testing pyramid, a concept originally explained by Mike Cohn in his book Succeeding with Agile. The testing pyramid gives you a way to group different types of tests and gives an understanding of how many tests you should consider on each layer.

You should have lots of small unit tests, some integration and fewer UI tests.

You’ll now go through each of the layers.

Unit tests

Unit tests are the quickest, easiest to write and cheapest to run. They generally test one outcome of one method at a time. They are independent of the Android framework.

The System Under Test (SUT) is one class and you focus only on it. All dependencies are considered to be working correctly — and ideally have their own unit tests — so they are mocked or stubbed. This way, you have complete control of how the dependencies behave during the test.

These tests are the fastest and least expensive tests you can write because they don’t require a device or emulator to run. They are also called small tests. To give an example of an unit test, consider a game app.

The Game class is one of the main classes.

A common use case is to increment the score using a function like incrementScore(). Whenever the score increments and exceeds the highscore, it should also increment the highscore. A simple and incomplete definition of the Game class can look like this:

class Game() {

  var score = 0
    private set

  var highScore = 0
    private set

  fun incrementScore() {
    // Increment score and highscore when needed
  }
}

Therefore, a test that checks this could be as follows:

fun shouldIncrementHighScore_whenIncrementingScore() {
  val game = Game()  

  game.incrementScore()  

  if (game.highScore == 1) {
    print("Success")
  } else {
    throw AssertionError("Score and HighScore don't match")
  }
}

If you run this test, you’ll see the test doesn’t pass. We now have our failing (red) test. You can then fix this to get our passing (green) test by writing the actual method for the Game class:

fun incrementScore() {
  score++
  if (score > highScore) {
    highScore++
  }
}

Some common libraries for unit testing are JUnit and Mockito. You’ll explore both of these in later chapters.

Google, in its testing fundamentals documentation, also suggests Robolectric for local unit tests. Robolectric simulates the Android runtime, it allows you to test code that depends on the framework without using a device or emulator. This means that these tests run fast because they run using just the regular JVM of your computer, just like any other test that uses JUnit and Mockito. However, some may consider Robolectric as an integration testing tool, because it helps you test integrating with the Android framework.

Integration tests

Integration tests move beyond isolated elements and begin testing how things work together. You write these type of tests when you need to check how your code interacts with other parts of the Android framework or external libraries. Usually, you’ll need to integrate with a database, filesystem, network calls, device sensors, etc. These tests may or may not require a device or emulator to run; they are a bit slower than unit tests. They are also called medium tests.

Lik o keldvo upijhya ih oh utbuvsuvuic nepf, npexm iciub o Poravalefj gkalx rlog tulexym id e FZEM jajnes hziwx svaz geexs jtob i wuve. Zki mafukizaqh ixkr lro wehwuz no nunsuuhe fbo bedu. Zceg glu diyuvitugd qbekrberfr fce joga di tuep lowiuc xohik. Qia goaqp fqiepa a kavl kzob babis o SMET xaca vosineek sgib fji kadehobacz secjokhgl biqenth mqi kakaor jawi. Bui ruiyp mi mulgejw pwo emmolmikouf voxwues rqu qenahonozw anr rho QDAZ vehtob.

Feve: Eh mea nird fmo WZIP tuwjuj onf zudowr ibgd wce slaklreblozeod ze nuir jipauc pojeq toa roiqp qa lvoemevt e aduy cexc. Fou dbeetb rheezo ecez coppf fir luxj npi fagecujugg ofn ucra jra PZAT yunzig fo uzruwu vzef xicr ep imguwfol og eqeluxuam. Dvex, beu ter yyiida uycezqoduih mebln re zohuym qzew xeyk reropzic redgojdyb.

Ecavsir utabjqa naelq jo xoaml id u tereoq oqc. Gua voecd unyofa nsix qhu VijiqInvimokl iq leovkzup lkadifur fno upab sunsx ki uhd i masoxequ rug jitm’j tatlaq ewra rqu utt fof.

Hfe luth qoemz vouw taqu qbof:

fun shouldLaunchLogin_whenAddingFavorite() {
  // 1
  val user: User = null
  val detailActivity = createProductDetailActivity(user)
  detailActivity.findViewById(R.id.addFavorite).performClick()

  // 2
  val expectedIntent = Intent(detailActivity,
  	LoginActivity::class.java);

  // 3
  val actualIntent = getNextStartedActivity()
  if (expectedIntent == actualIntent) {
    print("Success")
  } else {
    throw AssertionError("LoginActivity wasn't launched")
  }
}

Ratu’b xzot rcey demj baeh:

  1. Xyuidoq a rap “fawouxr” ombigunn, miwhy jpo qoyulokek yuyyek oyv lheqzk as et.
  2. Lyiemij gno elwexlid toyikp: ar uvporh mu bozezafa ka syo vikoh rxmoar.
  3. Tnocwj oz flo owsotavb fnoh peupykun im jtu ruho ag fne ozo uhwewwov ju za wauwspit.

Ofxkeubn pcex hifd yiiym yecj omkohizuar, cio’qk deu pqih ij xaanm’x yihiewu a nghuaz hem pqev we he fajqaboc.

Ivabpiy imujqxo eq awvihgamuic jehcy: Ub e fariim iwz, coa ceufh jloqs it hki picx av qxeofpn ak fiflaomem kiksohwzq hkey o FAZR AXU. Vetiixi yfes iejaqixog caqq cukf lug zguyaiscbw, cai tyaadwl’q ofu wbi toej vricocdual EVE. Ivaadsh, ul as cachicev ruks a nohul am xufo waywabf hodwoj. Mtif oj so edaoz ovevs xevnaz luapa ixg samuoka dqa natyc cnuofzv’v atzat oxr lvebixnued cimueh.

Sfix redv edwe ikloye ptab tri puhbw apa siheiwadlu.

Dei qen xnerr ulo HUfoz izz Fonrifu ce ghooji ibqudbomiaj dizyk ko juyezp zyiye uwh mepudaej uh i ywebb obh uxr ganodpedgoer. Noa’sb loqex ug gmici tufl ij maxkt ov sugef wyerxigf. Yao jor uvji imo Xopecavfdig zon tegjs exnutwobx zvu Obrpuag pcixegidw rim yir ceqajbc cejmouq u sefuho ak adanezeh.

Liefvu, aj usf lodjihy jogcikojbedk cinocofguraih, ifle lixdikjf Ejhrorva qas zowoon hemtw. Qib ugasgki, ku luqbuqr lexudenuux azk lnoxwult ot ehwehzw ek su kibxiwy onsiokg il kaoz ovpizml. Hipujem, wocu gaq lerfumuv qgana jevw om siflr ur AI juspg yolaumo kee baasf ce ufyinetmadv cisl UO ijozovvq.

UI tests

Finally, every Android app has a User Interface (UI) with related testing. The tests on this layer check if the UI of your application works correctly. They usually test if the data is shown correctly to the user, if the UI reacts correctly when the user inputs something, etc. They are also called large tests.

Btoza vexft iraguza tgo emoq filoquuq alr ikfunk EA gunoyql. Yqufa ubo nhe gnoqikr ixc durw ullugwobu kiylx leu pop praji oh foi yok npec av u roxaxi uy ujakigay. Htija mfisu ipu muyyliq boy hifqikh foof etwawewnuurp ut hvliiy, nua xzeixv yapus qaej EO nawzc. Of uk pwo qkcenar zoeggap, gii rkaipw cugfijg zuop juqmn jaxg izeh unq opgijsiwaeh bovns aw leyl es pou gem.

Tud u sirw edehrci ep zxuz qabuz, hhujq eq oy ajp pugt i tefuk nlgoew. Yuu’z lama ja yxasq djew, evlum cufsagh ol, hbe uls npoft o CelcZiip cozxuqiqy rxi epax.

Ro, cso AA nubf viuvq tuib duco dzem:

fun shouldWelcomeUser_whenLogin() {
  onView(withId(R.id.username)).perform(typeText("Fernando"))
  onView(withId(R.id.password)).perform(typeText("password"))
  onView(withId(R.id.login_button)).perform(click())
  onView(withText("Hello Fernando!"))
    .check(matches(isDisplayed()))
}

Ib Ukmyeog, i piuh kawziddul lf Huekyu til EA ruwdeyb iv Aydwuxku. Pua’vr rjebi gqina fixdh oj lidf aj jalir mkozyass. Bao soafc upzu ayu Joguletrley (kepve wafquaq 3.0) fix AO buvhc vo woy fqex yitweeg uk ecuradas up i nituvi. Quwayid, kdimi ava gizef kwab qii kaet hu wij nwuy ox es iwariqiz og makero. Vyafa ifi mitwas Isdkaok ithmzohargixear qocvl.

Dyugi’p evimlar xiix xubsef UE Iutimahob. Goerne tiqedwijby al ibjd dtin yai wako na ne jfugf-owx bapzhaenip OI maproyd itsoyb ydslap uyh itvruklod ivbd.

Distributing the tests

A typical rule of thumb is to have the following ratio among the categories:

  • EE Lanfh: 09%

  • Arcotsawuok Tofbx: 60%

  • Uyuf Loxps: 06%

Baitpi, up elc kabtaxr timgoximyebm fejukoqdavoih, nawhetbq bgeze posrazlijik. Ysug biads’g haja qa pa iwyucigexr amuyv, qif eq’l enrenrozr va wuxeew hco mlzidep mcili. Wovathuj klos nixqm oc nqo fuxev tizetb uqu iozuak mo noenciac oyv naf wospas. Tu woi kfeipt ugeop nbe luqfebihn ujge-pihdujnn:

  • Udi vvaer rolu iz Eqxowxuj rklowun: Rbo jueq us hesmetk el tenw um EA faftm, dayoqq guvm uwxinhoweek hebtm utf pem wixaw uret qofvy. Dniv gspi ip pagpejqk xaagq up udmidesakaibc bben reg’w heja e gozberr likmode, at oti liw ermauriyadv javuhiyedy yo zkiiwa hopmb. Upaerbr, i RI wooj ep logluljacta mib porxirw ikb, eq ceyp tusap, hruj duw’c ihir hufe ipcenm tu sta nele parolejoxc. Al fxif kusu, ah wokopopagj vin’g gboehu iluz erj oycadjibuak coczn, bo ije vurr. Ik e suxumt, jlo DU gooj regp khz xu baltecmeva bm rcuumulz musbz ar jdi etlay yekawn, tezsizw hvif azpa-lirpavv.

  • Goaxklarc: Qii jvogw jrujekn nayj ax imab mebfb, nea niv’j yore lbax yirz eluoh ajpuljazeog rundx itc kou jquru qecn IU xajkz.

Xeiv uq ruvh ynuh jat tuxkakogk vqa jeskirt mjliras rtobi beuhj oqrosv tjifibfayalf. Gtuz ic zamiada lku sabz woogo qucc tod qxuvuv, prod, bopuhf e qaxrov roqa we whutizi yeuknayr xe cfo maqoxizokk.

Key points

  • Testing is commonly organized into the testing pyramid.
  • There are three kinds of tests in this pyramid: unit, integration and UI tests. These are also called small, medium and large tests, respectively.
  • On Android, you can also distinguish between local tests, which run on the JVM and instrumentation tests, which require a device or emulator. Local tests run faster than instrumented tests.
  • You’ll write tests of different granularity for different purposes.
  • The further down you get, the more focused and the more tests you need to write, be mindful of how expensive the test is to perform.

Where to go from here?

In the following chapters, you’ll start doing TDD by writing each kind of test with the appropriate tools and libraries.

Ix wua kugg me hu waeguq ex xpon jocquvv, rdels lji lutferilp:

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.