learning android application testing related/pdfs and books... · 2016. 5. 31. · java testing...

Post on 17-Aug-2020

2 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

LearningAndroidApplicationTesting

TableofContents

LearningAndroidApplicationTesting

Credits

AbouttheAuthors

AbouttheReviewers

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmore

Whysubscribe?

FreeaccessforPacktaccountholders

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Errata

Piracy

Questions

Questions

1.GettingStartedwithTesting

Why,what,how,andwhentotest?

Whattotest

Activitylifecycleevents

Databaseandfilesystemoperations

Physicalcharacteristicsofthedevice

Typesoftests

Unittests

ThesetUp()method

ThetearDown()method

Outsidethetestmethod

Insidethetestmethod

Mockobjects

Integrationtests

UItests

Functionaloracceptancetests

Testcasescenario

Performancetests

Systemtests

AndroidStudioandotherIDEsupport

Javatestingframework

Androidtestingframework

Instrumentation

Gradle

Testtargets

CreatingtheAndroidproject

Packageexplorer

Creatingatestcase

Testannotations

Runningthetests

RunningalltestsfromAndroidStudio

RunningasingletestcasefromyourIDE

Runningfromtheemulator

Runningtestsfromthecommandline

Runningalltests

Runningtestsfromaspecifictestcase

Runningaspecifictestbyname

Runningspecifictestsbycategory

RunningtestsusingGradle

Creatingacustomannotation

Runningperformancetests

Dryrun

Debuggingtests

Othercommand-lineoptions

Summary

2.UnderstandingTestingwiththeAndroidSDK

Thedemonstrationapplication

Assertionsindepth

Custommessages

Staticimports

Viewassertions

Evenmoreassertions

TheTouchUtilsclass

Mockobjects

AnoverviewofMockContext

TheIsolatedContextclass

Alternateroutetofileanddatabaseoperations

TheMockContentResolverclass

TheTestCasebaseclass

Thedefaultconstructor

Thegivennameconstructor

ThesetName()method

TheAndroidTestCasebaseclass

TheassertActivityRequiresPermission()method

Description

Example

TheassertReadingContentUriRequiresPermissionmethod

Description

Example

TheassertWritingContentUriRequiresPermission()method

Description

Example

Instrumentation

TheActivityMonitorinnerclass

Example

TheInstrumentationTestCaseclass

ThelaunchActivityandlaunchActivityWithIntentmethods

ThesendKeysandsendRepeatedKeysmethods

TherunTestOnUiThreadhelpermethod

TheActivityTestCaseclass

ThescrubClassmethod

TheActivityInstrumentationTestCase2class

Theconstructor

ThesetUpmethod

ThetearDownmethod

TheProviderTestCase2<T>class

Theconstructor

Anexample

TheServiceTestCase<T>

Theconstructor

TheTestSuiteBuilder.FailedToCreateTestsclass

Usinglibrariesintestprojects

Summary

3.BakingwithTestingRecipes

Androidunittests

Testingactivitiesandapplications

Mockingapplicationsandpreferences

TheRenamingMockContextclass

Mockingcontexts

Testingactivities

Testingfiles,databases,andcontentproviders

TheBrowserProvidertests

Testingexceptions

Testinglocalandremoteservices

Extensiveuseofmockobjects

Importinglibraries

Mockitousageexample

TheEditNumberfiltertests

Testingviewsinisolation

Testingparsers

Androidassets

Theparsertest

Testingformemoryusage

TestingwithEspresso

Summary

4.ManagingYourAndroidTestingEnvironment

CreatingAndroidVirtualDevices

RunningAVDsfromthecommandline

Headlessemulator

Disablingthekeyguard

Cleaningup

Terminatingtheemulator

Additionalemulatorconfigurations

Simulatingnetworkconditions

SpeedingupyourAVDwithHAXM

AlternativestotheAVD

Runningmonkey

Theclient-servermonkey

Testscriptingwithmonkeyrunner

Gettingtestscreenshots

Recordandplayback

Summary

5.DiscoveringContinuousIntegration

BuildingAndroidapplicationsmanuallyusingGradle

Git–thefastversioncontrolsystem

CreatingalocalGitrepository

ContinuousintegrationwithJenkins

InstallingandconfiguringJenkins

Creatingthejobs

ObtainingAndroidtestresults

Summary

6.PracticingTest-drivenDevelopment

GettingstartedwithTDD

Writingatestcase

Runningalltests

Refactoringthecode

AdvantagesofTDD

Understandingtherequirements

Creatingasampleproject–thetemperatureconverter

Listofrequirements

Userinterfaceconceptdesign

Creatingtheproject

CreatingaJavamodule

CreatingtheTemperatureConverterActivityTestsclass

Creatingthefixture

Creatingtheuserinterface

Testingtheexistenceoftheuserinterfacecomponents

GettingtheIDsdefined

Translatingrequirementstotests

Emptyfields

Viewproperties

Screenlayout

Addingfunctionality

Temperatureconversion

TheEditNumberclass

TheTemperatureConverterunittests

TheEditNumbertests

TheTemperatureChangeWatcherclass

MoreTemperatureConvertertests

TheInputFiltertests

Viewingourfinalapplication

Summary

7.Behavior-drivenDevelopment

Given,When,andThen

FitNesse

RunningFitNessefromthecommandline

CreatingaTemperatureConverterTestssubwiki

Addingchildpagestothesubwiki

Addingtheacceptancetestfixture

Addingthesupportingtestclasses

GivWenZen

Creatingthetestscenario

Summary

8.TestingandProfilingPerformance

YeOldeLoggemethod

Timinglogger

PerformancetestsinAndroidSDK

Launchingtheperformancetest

CreatingtheLaunchPerformanceBaseinstrumentation

CreatingtheTemperatureConverterActivityLaunchPerformanceclass

Runningthetests

UsingtheTraceviewanddmtracedumpplatformtools

Dmtracedump

Microbenchmarks

Calipermicrobenchmarks

Benchmarkingthetemperatureconverter

RunningCaliper

Summary

9.AlternativeTestingTactics

Codecoverage

Jacocofeatures

Temperatureconvertercodecoverage

Generatingcodecoverageanalysisreport

Coveringtheexceptions

IntroducingRobotium

AddingRobotium

Creatingthetestcases

ThetestFahrenheitToCelsiusConversion()test

TestingbetweenActivities

Testingonthehost’sJVM

Comparingtheperformancegain

AddingAndroidtothepicture

IntroducingRobolectric

InstallingRobolectric

Addingresources

Writingsometests

Google’smarchonshadows

IntroducingFest

IntroducingSpoon

IntroducingFork

Summary

Index

LearningAndroidApplicationTesting

LearningAndroidApplicationTestingCopyright©2015PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:June2011

Secondedition:March2015

Productionreference:1240315

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78439-533-9

www.packtpub.com

CreditsAuthors

PaulBlundell

DiegoTorresMilano

Reviewers

BJPeterDeLaCruz

NoureddineDimachk

MiguelLGonzalez

HenrikKirk

SérgioLima

JoãoTrindade

CommissioningEditor

TaronPereira

AcquisitionEditor

RebeccaYoué

ContentDevelopmentEditor

ManasiPandire

TechnicalEditor

IndrajitA.Das

CopyEditors

KhushnumMistry

AlfidaPaiva

VikrantPhadke

AdithiShetty

ProjectCoordinator

SuzanneCoutinho

Proofreaders

SimranBhogal

JoannaMcMahon

Indexer

HemanginiBari

Graphics

ValentinaD’silva

ProductionCoordinator

AlwinRoy

CoverWork

AlwinRoy

AbouttheAuthorsPaulBlundellisanaspiringsoftwarecraftsmanandseniorAndroiddeveloperatNovoda.BeforeNovoda,heworkedatAutoTraderandThales,withappsthathereleasedrackingupoveronemilliondownloads.Astrongbelieverinsoftwarecraftsmanship,SOLIDarchitecture,cleancode,andtesting,PaulhasusedthismethodologytosuccessfullynurtureandcreatemanyAndroidapplications.TheseincludetheTescolauncherapp,whichwaspreinstalledfortherecentlyreleasedHudl2tablet;MUBI,auniquefilmstreamingservice;andtheAutoTraderUKcarsearchapp.

Ifanyonewantstoprovidefeedback,youcanalwaystweettohim@blundell_apps.Healsolikestowrite,soyoucanfindmorematerialathttp://blog.blundellapps.com/.

I’dliketothankeveryoneatNovodaforbeinggreatguys/galsandhelpingeachotherallthetimetolearnanddevelop.Withouttheatmosphereofcraftsmanshipandconstantlearning,myskillsandthisbookwouldnothavebeenpossible.Also,I’dliketothankmygirlfriendforherendlesspatience.Everytimesheaskedmetohelpherout,I’dgivehertheexcuseofwritingmybook.Well,nomoreexcusesbecauseitisfinished!

I’dliketoacknowledgethelegacyauthorofthisbookDiegoTorresMilanofordoingagreatjob.ThechaptersoutlinedaredowntoyourinsightintotheworldoftestingonAndroid,andIhopemyrewritelivesuptoyourideals.

Finally,I’dliketothankallthepeoplewhodon’tknowmebutfromwhomI’velearntalot.Ifyou,asthereader,wantalistofotherauthorsforfurtherresearch,thisisit:KentBeck,MartinFowler,RobertCMartin,RomainGuy,RetoMeier,MarkMurphy,EricEvans,JoshuaBlock,WardCunningham,KevinRutherford,JBRainsberger,andSandroMancuso.

DiegoTorresMilanohasbeeninvolvedwiththeAndroidplatformsinceitsinception,bytheendof2007,whenhestartedexploringandresearchingtheplatform’spossibilities,mainlyintheareasofuserinterfaces,unitandacceptancetests,andTest-drivenDevelopment.

Thisisreflectedbyanumberofarticlesmainlypublishedonhispersonalblog(http://dtmilano.blogspot.com),andhisparticipationasalecturerinsomeconferencesandcourses,suchasMobileDevCamp2008inAmsterdam(Netherlands)andJapanLinuxSymposium2009(Tokyo),DroidconLondon2009,andSkillsmatter2009(London,UK).HehasalsoauthoredAndroidtrainingcoursesdeliveredtovariouscompaniesinEurope.

Previously,hewasthefounderanddeveloperofseveralopensourceprojects,mainlyCULTUniversalLinuxThinProject(http://cult-thinclient.sf.net)andtheverysuccessfulPXESUniversalLinuxThinClientproject(thatwaslateracquiredby2XSoftware,http://www.2x.com).PXESisaLinux-basedoperatingsystemspecializedforthinclients,usedbyhundredsofthousandsofthinclientsallovertheworld.Thisprojecthasapopularitypeakof35millionhitsand400KdownloadsfromSourceForgein2005.Thisprojecthadadualimpact.BigcompaniesinEuropedecidedtouseitbecauseofimprovedsecurityandefficiency;andorganizations,institutions,andschoolsinsomedevelopment

countriesinSouthAmerica,Africa,andAsiadecidedtouseitbecauseoftheminimalhardwarerequirements,havingahugesocialimpactofprovidingcomputers,sometimesrecycledones,toeveryone.

AmongtheotheropensourceprojectsthathefoundedareAutoglade,Gnome-tla,andJGlade,andhehascontributedtovariousLinuxdistributions,suchasRedHat,Fedora,andUbuntu.

HehasalsogivenpresentationsattheLinuxWorld,LinuxTag,GUADECES,UniversityofBuenosAires,andsoon.

Diegohasalsodevelopedsoftware,participatedinopensourceprojects,andadvisedcompaniesworldwideformorethan15years.

Hecanbecontactedat<dtmilano@gmail.com>.

AbouttheReviewersBJPeterDeLaCruzgraduatedwithamaster’sdegreeincomputersciencefromtheUniversityofHawaiiatManoa.In2011,hebeganhiscareerasasoftwaredeveloperatReferentiaSystemsInc.inHonolulu,Hawaii.AtReferentia,heassistedinthedevelopmentoftheLiveActionproduct.AfterworkingatReferentiafor2.5years,hewashiredasaJavawebdeveloperbytheUniversityofHawaii.Betweenfall2014andspring2015semesters,heupgradedLaulima(http://laulima.hawaii.edu),thelearningmanagementsystemthattheuniversityusesfortraditionalface-to-face,online,andhybridclasses.

BJholdsthreeJavacertifications,includingtheOracleCertifiedMaster,JavaSE6Developercertification.

HeisasuccessfulAndroiddeveloper.AsofJanuary2015,hehaspublishedsevenAndroidappsonGooglePlay.Hislatestapp,ChamorroDictionary,isanexcellentlearningtoolfortheChamorrolanguage.Youcancheckouthisappsathttp://tinyurl.com/google-play-bpd.

BJreallylikesGradlebecauseitmakesbuildingapplicationsveryeasy.HewasareviewerforGradleinAction.

HishobbiesincludelearningtheJapaneselanguage,readingbooksaboutJapaneseculture,andmakingYouTubevideos.Youcancontacthimat<bj.peter.delacruz@gmail.com>.Youcanalsovisithiswebsiteathttp://www.bjpeter.com.

IwanttothankGodforgivingmetheopportunitytoreviewthisbook.IalsowanttothankNikitaMichaelforinvitingmetobecomeareviewerandSuzanneCoutinhoforsendingallthechapterstoreview.Arigatougozaimasu!

NoureddineDimachkisapassionatevideogamersincebirth.NoureddinestartedbuildinggamesusingTheGamesFactorywhenhewasjust10yearsold.

Today,heleadsamultinationalteamof17enthusiasticdevelopersspreadacrossLebanon,Argentina,andIndiatobuildcutting-edgeapplicationsthatservemillionsofconcurrentGSMsubscribers,inadditiontomobileapplications.

Ageekbynature,Noureddinelikestoexperimentwithnewtechnologiesinhissparetime,andhe’sapassionateDota2player.

Iwouldliketothankmyamazingwifeforstandingbymeandsupportingmeinmytechnicalventures.

MiguelLGonzalezisaSpanishsoftwareengineerworkingintheUnitedKingdomsince2010.Hetookhisfirstprogrammingcourseattheearlyageofeight,andithasbeenhismainpassionandhobbysincethen.HesoonbecameattractedtotheWebandInternet,whichleadhimtostudytelecommunicationsengineering.

Hehasworkedasaresearcherintheuniversity,designingaccessiblehardwareandwirelesssensornetworks,teachingwebdevelopment,developingamixtureofJava

hardware,desktops,andwebapps,andistheheadofdevelopmentinanagency.SincethetimehearrivedintheUK,hehasmainlyfocusedonwebandnativedevelopmentformobiles,andhedevelopedafewAndroidandiOSappsincoANDcoUK.In2013,hejoinedBBCtoworkoniPlayer,BBC’scatch-upservice.Itwasherethathebecamemoreseriousaboutunittesting,behavioraltesting,andhowtodrivesuccessviacontinuousintegration.

Hetriestokeepimprovinghisprojects,whichcanbefoundathttp://github.com/ktzarandmaintainhispersonalwebsite,http://mentadreams.com.SincehissonAlexwasborn,thesparetimeforsideprojectshasbeenreduced,buthiswife,Dalia,helpshimtofindtimeforthem.Nevertheless,he’slookingforwardtoplayingMonkeyIsland,designinggames,playingtheguitar,andtravelingtheworldwithhisoffspringinafewyearstime.

HenrikKirkholdsamaster’sdegreeincomputersciencefromAarhusUniversityandhasover5yearsofexperienceinAndroidapplicationdevelopment.HeiscuriousaboutnewtechnologiesandhasbeenusingScalaaswellasJavaforAndroiddevelopment.Healsoenjoysoptimizingtheuserexperiencethroughspeedandresponsivedesign.HeiscurrentlyemployedastheleaddeveloperatLapio,creatinganawesometimingandraceexperienceforathletesintheUSandEurope.Inhissparetime,heraceshismountainbike.

SérgioLimaisasoftwareengineerandanairplanepilot.It’seasytoseethathe’saveryambitiouspersonwithbroadand,atthesametime,specificinterests.HecurrentlyworksataPortuguesecompanythataimstorevolutionizetheworldwithtelecomandmobileapplications.Hiscurriculumstartedwithamaster’sdegreeinelectronicsandtelecommunicationsandhespecializedincomputerprogrammingandcomputervision.AfterworkingatsomeinstitutionsinPortugal,heworkedatCERNinSwitzerland,beforereturningtohishomecountry.

Healsolovestoflysmallplanes,suchasthePiper“Cherokee”and“Tomahawk”,fromthenearbyaerodrome,toseePortugalfromabove,admiretheradiantsceneriesofthecountry,andexperiencethefreedomofflying.

Iwouldliketothankmyfamilyandspeciallymywonderfulprincess,“Kika”,forherpatience,support,andloveduringtheprocessofreviewingthisbook.

JoãoTrindadeisasoftwaredeveloperwhospecializesindevelopingAndroidapps.

Currently,heispartofastartupinMilanthattracksyourmobilephoneusageandsuggeststhebesttariffplanforyourneeds.

HecompletedhisPhDincomputerengineeringatLisbonTechandisinterestedineverythingrelatedtomobiledevelopment,softwaretesting,dockercontainers,orcloudcomputing.

For6yearshewasaresearcherinvolvedinmultipleinternationalresearchprojectsandhaspublished18peerreviewedarticles.

Histwitterhandleris@joaotrindadeandhispersonalwebpageishttp://joaoptrindade.com.

HecontributestovariousopensourceproductsonGitHub.Youcanseehisprofileathttp://github.com/joninvski.

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<service@packtpub.com>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

PrefaceItdoesn’tmatterhowmuchtimeyouinvestinAndroiddesign,orevenhowcarefulyouarewhenprogramming,mistakesareinevitableandbugswillappear.ThisbookwillhelpyouminimizetheimpactoftheseerrorsinyourAndroidprojectandincreaseyourdevelopmentproductivity.Itwillshowyoutheproblemsthatareeasilyavoided,tohelpgetyouquicklytothetestingstage.

AndroidApplicationTestingGuideisthefirstandonlybookprovidingapracticalintroductiontothemostcommonlyavailabletechniques,frameworks,andtoolstoimprovethedevelopmentofyourAndroidapplications.Clear,step-by-stepinstructionsshowhowtowritetestsforyourapplicationsandassurequalitycontrolusingvariousmethodologies.

Theauthor’sexperienceinapplyingapplicationtestingtechniquestoreal-worldprojectsenableshimtoshareinsightsoncreatingprofessionalAndroidapplications.

ThebookcoversthebasicsofframeworksupportforteststoarchitecturesandtechniquessuchasTest-drivenDevelopment,whichisanagilecomponentofthesoftwaredevelopmentprocessandatechniquewhereyouwilltacklebugsearlyon.Fromthemostbasicunittestsappliedtoasampleprojecttomoresophisticatedperformancetests,thisbookprovidesadetaileddescriptionofthemostwidelyusedtechniquesintheAndroidtestingworldinarecipe-basedapproach.

Theauthorhasextensiveexperienceofworkingonvariousdevelopmentprojectsthroughouthisprofessionalcareer.AllthisresearchandknowledgehashelpedcreateabookthatwillserveasausefulresourcetoanydevelopernavigatingtheworldofAndroidtesting.

WhatthisbookcoversChapter1,GettingStartedwithTesting,introducesthedifferenttypesoftestingandtheirapplicabilitytosoftwaredevelopmentprojectsingeneralandtoAndroidinparticular.ItthengoesontocovertestingontheAndroidplatform,unittestingandJUnit,creatinganAndroidtestprojectandrunningtests.

Chapter2,UnderstandingTestingwiththeAndroidSDK,startsdiggingabitdeepertorecognizethebuildingblocksavailabletocreatethetests.ItcoversAssertions,TouchUtils,whichareintendedtotestuserinterfaces,mockobjects,instrumentation,andTestCaseclasshierarchies.

Chapter3,BakingwithTestingRecipes,providespracticalexamplesofdifferentsituationsyouwillcommonlyencounterwhileapplyingthedisciplinesandtechniquesdescribedbefore.Theexamplesarepresentedinacookbookstylesoyoucanadaptandusethemforyourprojects.TherecipescoverAndroidunittests,activities,applications,databasesandContentProviders,services,UIs,exceptions,parsers,memoryleaks,andalookattestingwithEspresso.

Chapter4,ManagingYourAndroidTestingEnvironment,providesdifferentconditionstorunthetests.ItstartswiththecreationoftheAndroidVirtualDevices(AVD)toprovidedifferentconditionsandconfigurationsfortheapplicationundertestandrunsthetestsusingtheavailableoptions.Finally,itintroducesmonkeyasawaytogeneratesimulatedeventsusedfortesting.

Chapter5,DiscoveringContinuousIntegration,introducesthisagiletechniqueforsoftwareengineeringandautomationthataimstoimprovethesoftwarequalityandreducethetimetakentointegratechangesbycontinuouslyapplyingintegrationandtestingfrequently.

Chapter6,PracticingTest-drivenDevelopment,introducestheTest-drivenDevelopmentdiscipline.ItstartswithageneralrevisionandlateronmovestotheconceptsandtechniquescloselyrelatedtotheAndroidplatform.Thisisacode-intensivechapter.

Chapter7,Behavior-drivenDevelopment,introducesBehavior-drivenDevelopmentandsomeconcepts,suchastheuseofacommonvocabularytoexpressthetestsandtheinclusionofbusinessparticipantsinthesoftwaredevelopmentproject.

Chapter8,TestingandProfilingPerformance,introducesaseriesofconceptsrelatedtobenchmarkingandprofilesfromtraditionalloggingstatementmethodstocreatingAndroidperformancetestsandusingprofilingtools.

Chapter9,AlternativeTestingTactics,coversaddingcodecoveragetoensureyouknowwhatistestedandwhatisn’t,aswellastestingonthehost’sJavaVirtualMachine,investigatingFest,Spoon,andthefutureofAndroidtestingtobuilduponandexpandyourAndroidtestingrange.

WhatyouneedforthisbookTobeabletofollowtheexamplesinthedifferentchapters,youneedacommonsetofsoftwareandtoolsinstalledandseveralothercomponentsthataredescribedineverychapterinparticular,includingtheirrespectivedownloadlocations.

Alltheexamplesarebasedonthefollowing:

MacOSX10.9.4,fullyupdatedJavaSEversion1.6.0_24(build1.6.0_24-b07)AndroidSDKtools,revision24AndroidSDKplatform-tools,revision21SDKplatformAndroid4.4,API20Androidsupportlibrary,revision21AndroidStudioIDE,Version:1.1.0Gradleversion2.2.1Gitversion1.8.5.2

WhothisbookisforIfyouareanAndroiddeveloperlookingtotestyourapplicationsoroptimizeyourapplicationdevelopmentprocess,thenthisbookisforyou.Nopreviousexperienceinapplicationtestingisrequired.

ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestyles,andanexplanationoftheirmeaning.

Codewordsintextareshownasfollows:“Toinvoketheamcommandwewillbeusingtheadbshellcommand”.

Ablockofcodeissetasfollows:

dependencies{

compileproject(':dummylibrary')

}

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

fahrenheitEditNumber

.addTextChangedListener(

newFehrenheitToCelciusWatcher(fahrenheitEditNumber,celsiusEditNumber));

}

Anycommand-lineinputoroutputiswrittenasfollows:

junit.framework.ComparisonFailure:expected:<[]>butwas:<[123.45]>

atcom.blundell.tut.EditNumberTests.testClear(EditNumberTests.java:31)

atjava.lang.reflect.Method.invokeNative(NativeMethod)

atandroid.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:“ThefirsttestperformsaclickontheGobuttonoftheForwardingActivity.”

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,simplye-mail<feedback@packtpub.com>,andmentionthebook’stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<questions@packtpub.com>,andwewilldoourbesttoaddresstheproblem.

QuestionsYoucancontactusat<questions@packtpub.com>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.

Chapter1.GettingStartedwithTestingFirstly,IwillavoidintroductionstoAndroidsinceitiscoveredinmanybooksalready,andIaminclinedtobelievethatifyouarereadingabookthatcoversthismoreadvancedtopic,youwillhavealreadystartedwithAndroiddevelopment.

Iwillbereviewingthemainconceptsbehindtesting,andthetechniques,frameworks,andtoolsavailabletodeployyourtestingstrategyonAndroid.

Afterthisoverview,wecanputtheconceptslearnedintopractice.Inthischapterwewillcover:

SettinguptheinfrastructuretotestonAndroidRunningunittestsusingJUnitCreatinganAndroidinstrumentationtestprojectRunningmultipletests

WewillbecreatingasimpleAndroidprojectanditscompaniontests.Themainprojectwillbebarebonessothatyoucanconcentrateonthetestingcomponents.

IwouldsuggestthatnewdeveloperswithnoAndroidtestingexperiencereadthisbook.IfyouhavemoreexperiencewithAndroidprojectsandhavebeenusingtestingtechniquesforthem,youmightreadthischapterasarevisionorreaffirmationoftheconcepts.

Why,what,how,andwhentotest?Youshouldunderstandthatearlybugdetectionsavesahugeamountofprojectresourcesandreducessoftwaremaintenancecosts.Thisisthebestknownreasontowritetestsforyoursoftwaredevelopmentproject.Increasedproductivitywillsoonbeevident.

Additionally,writingtestswillgiveyouadeeperunderstandingoftherequirementsandtheproblemtobesolved.Youwillnotbeabletowritetestsforapieceofsoftwareyoudon’tunderstand.

Thisisalsothereasonbehindtheapproachofwritingteststoclearlyunderstandlegacyorthird-partycodeandhavingthetestinginfrastructuretoconfidentlychangeorupdatethecodebase.

Themorethecodeiscoveredbyyourtests,thehigherthelikelihoodofdiscoveringhiddenbugs.

If,duringthiscoverageanalysis,youfindthatsomeareasofyourcodearenotexercised,additionaltestsshouldbeaddedtocoverthiscodeaswell.

Tohelpinthisrequest,enterJacoco(http://www.eclemma.org/jacoco/),anopensourcetoolkitthatmeasuresandreportsJavacodecoverage.Itsupportsvariouscoveragetypes,asfollows:

ClassMethodBlockLine

Coveragereportscanalsobeobtainedindifferentoutputformats.JacocoissupportedtosomedegreebytheAndroidframework,anditispossibletobuildaJacocoinstrumentedversionofanAndroidapp.

WewillbeanalyzingtheuseofJacocoonAndroidtoguideustofulltestcoverageofourcodeinChapter9,AlternativeTestingTactics.

ThisscreenshotshowshowaJacococodecoveragereportisdisplayedasanHTMLfilethatshowsgreenlineswhenthecodehasbeentested:

Bydefault,theJacocogradlepluginisn’tsupportedinAndroidStudio;therefore,youcannotseecodecoverageinyourIDE,andsocodecoveragehastobeviewedasseparateHTMLreports.ThereareotheroptionsavailablewithotherpluginssuchasAtlassian’sCloverorEclipsewithEclEmma.

Testsshouldbeautomated,andyoushouldrunsomeoralltestseverytimeyouintroduceachangeoradditiontoyourcodeinordertoensurethatalltheconditionsthatweremetbeforearestillmet,andthatthenewcodesatisfiesthetestsasexpected.

ThisleadsustotheintroductionofContinuousIntegration,whichwillbediscussedindetailinChapter5,DiscoveringContinuousIntegration,enablingtheautomationoftestsandthebuildingprocess.

Ifyoudon’tuseautomatedtesting,itispracticallyimpossibletoadoptContinuousIntegrationaspartofthedevelopmentprocess,anditisverydifficulttoensurethatchangeswouldnotbreakexistingcode.

Havingtestsstopsyoufromintroducingnewbugsintoalreadycompletedfeatureswhenyoutouchthecodebase.Theseregressionsareeasilydone,andtestsareabarriertothishappening.Further,youcannowcatchandfindproblemsatcompiletime,thatis,whenyouaredeveloping,ratherthanreceivingthemasfeedbackwhenyourusersstartcomplaining.

WhattotestStrictlyspeaking,youshouldtesteverystatementinyourcode,butthisalsodependsondifferentcriteriaandcanbereducedtotestingthemainpathofexecutionorjustsomekeymethods.Usually,there’snoneedtotestsomethingthatcan’tbebroken;forexample,itusuallymakesnosensetotestgettersandsettersasyouprobablywon’tbetestingtheJavacompileronyourowncode,andthecompilerwouldhavealreadyperformeditstests.

Inadditiontoyourdomain-specificfunctionalareasthatyoushouldtest,therearesomeotherareasofanAndroidapplicationthatyoushouldconsider.Wewillbelookingattheseinthefollowingsections.

ActivitylifecycleeventsYoushouldtestwhetheryouractivitieshandlelifecycleeventscorrectly.

IfyouractivityshouldsaveitsstateduringtheonPause()oronDestroy()eventsandlaterbeabletorestoreitinonCreate(BundlesavedInstanceState),thenyoushouldbeabletoreproduceandtestalltheseconditionsandverifythatthestatewascorrectlysavedandrestored.

ConfigurationchangeeventsshouldalsobetestedassomeoftheseeventscausethecurrentActivitytoberecreated.YoushouldtestwhetherthehandlingoftheeventiscorrectandthatthenewlycreatedActivitypreservesthepreviousstate.Configurationchangesaretriggeredevenbyadevicerotation,soyoushouldtestyourapplication’sabilitytohandlethesesituations.

DatabaseandfilesystemoperationsDatabaseandfilesystemoperationsshouldbetestedtoensurethattheoperationsandanyerrorsarehandledcorrectly.Theseoperationsshouldbetestedinisolationatthelowersystemlevel,atahigherlevelthroughContentProviders,orfromtheapplicationitself.

Totestthesecomponentsinisolation,Androidprovidessomemockobjectsintheandroid.test.mockpackage.Asimplewaytothinkofamockisasadrop-inreplacementfortherealobject,whereyouhavemorecontroloftheobject’sbehavior.

PhysicalcharacteristicsofthedeviceBeforeshippingyourapplication,youshouldbesurethatallofthedifferentdevicesitcanberunonaresupported,oratleastyoushoulddetecttheunsupportedsituationandtakepertinentmeasures.

Thecharacteristicsofthedevicesthatyoushouldtestare:

NetworkcapabilitiesScreendensitiesScreenresolutionsScreensizesAvailabilityofsensorsKeyboardandotherinputdevices

GPSExternalstorage

Inthisrespect,anAndroidemulatorcanplayanimportantrolebecauseitispracticallyimpossibletohaveaccesstoallofthedeviceswithallofthepossiblecombinationsoffeatures,butyoucanconfigureemulatorsforalmosteverysituation.However,asmentionedbefore,leaveyourfinaltestsforactualdeviceswheretherealuserswillruntheapplicationsoyougetfeedbackfromarealenvironment.

TypesoftestsTestingcomesinavarietyofframeworkswithdifferinglevelsofsupportfromtheAndroidSDKandyourIDEofchoice.Fornow,wearegoingtoconcentrateonhowtotestAndroidappsusingtheinstrumentedAndroidtestingframework,whichhasfullSDKandASidesupport,andlateron,wewilldiscussthealternatives.

Testingcanbeimplementedatanytimeinthedevelopmentprocess,dependingonthetestmethodemployed.However,wewillbepromotingtestingatanearlystageofthedevelopmentcycle,evenbeforethefullsetofrequirementshasbeendefinedandthecodingprocesshasbeenstarted.

Thereareseveraltypesoftestsdependingonthecodebeingtested.Regardlessofitstype,atestshouldverifyaconditionandreturntheresultofthisevaluationasasingleBooleanvaluethatindicatesitssuccessorfailure.

UnittestsUnittestsaretestswrittenbyprogrammersforotherprogrammers,andtheyshouldisolatethecomponentundertestsandbeabletotestitinarepeatableway.That’swhyunittestsandmockobjectsareusuallyplacedtogether.Youusemockobjectstoisolatetheunitfromitsdependencies,tomonitorinteractions,andalsotobeabletorepeatthetestanynumberoftimes.Forexample,ifyourtestdeletessomedatafromadatabase,youprobablydon’twantthedatatobeactuallydeletedand,therefore,notfoundthenexttimethetestisran.

JUnitisthedefactostandardforunittestsonAndroid.It’sasimpleopensourceframeworkforautomatingunittesting,originallywrittenbyErichGammaandKentBeck.

AndroidtestcasesuseJUnit3(thisisabouttochangetoJUnit4inanimpendingGooglerelease,butasofthetimeofthiswriting,weareshowingexampleswithJUnit3).Thisversiondoesn’thaveannotations,andusesintrospectiontodetectthetests.

AtypicalAndroid-instrumentedJUnittestwouldbesomethinglikethis:

publicclassMyUnitTestCaseextendsTestCase{

publicMyUnitTestCase(){

super("testSomething");

}

publicvoidtestSomething(){

fail("Testnotimplementedyet");

}

}

TipYoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Thefollowingsectionsexplainthecomponentsthatcanbeusedtobuildupatestcase.Notethatthesecomponentsandthepatternofworkingwithatestcasearenotuniquetounittests,andtheycanbedeployedfortheothertesttypesthatwewilldiscussinthefollowingsections.

ThesetUp()methodThismethodiscalledtoinitializethefixture(fixturebeingthetestanditssurroundingcodestate).

Overridingit,youhavetheopportunitytocreateobjectsandinitializefieldsthatwillbeusedbytests.It’sworthnotingthatthissetupoccursbeforeeverytest.

ThetearDown()methodThismethodiscalledtofinalizethefixture.

Overridingit,youcanreleaseresourcesusedbytheinitializationortests.Again,thismethodisinvokedaftereverytest.

Forexample,youcanreleaseadatabaseorcloseanetworkconnectionhere.

Therearemoremethodsyoucanhookintobeforeandafteryourtestmethods,buttheseareusedrarely,andwillbeexplainedaswebumpintothem.

OutsidethetestmethodJUnitisdesignedinawaythattheentiretreeoftestinstancesisbuiltinonepass,andthenthetestsareexecutedinasecondpass.Therefore,thetestrunnerholdsstrongreferencestoalltestinstancesforthedurationofthetestexecution.ThismeansthatforverylargeandverylongtestrunswithmanyTestinstances,noneofthetestsmaybegarbagecollecteduntiltheentiretestisrun.ThisisparticularlyimportantinAndroidandwhiletestingonlimiteddevicesassometestsmayfailnotbecauseofanintrinsicfailurebutbecauseoftheamountofmemoryneededtoruntheapplication,inadditiontoitstestsexceedingthedevicelimits.

Therefore,ifyouallocateexternalorlimitedresourcesinatest,suchasServicesorContentProviders,youareresponsibleforfreeingthoseresources.ExplicitlysettinganobjecttonullinthetearDown()method,forexample,allowsittobegarbagecollectedbeforetheendoftheentiretestrun.

InsidethetestmethodAllpublicvoidmethodswhosenamesstartwithtestwillbeconsideredasatest.AsopposedtoJUnit4,JUnit3doesn’tuseannotationstodiscoverthetests;instead,itusesintrospectiontofindtheirnames.TherearesomeannotationsavailableintheAndroidtestframeworksuchas@SmallTest,@MediumTest,or@LargeTest,whichdon’tturnasimplemethodintoatestbutorganizethemindifferentcategories.Ultimately,youwillhavetheabilitytoruntestsforasinglecategoryusingthetestrunner.

Asaruleofthumb,nameyourtestsinadescriptivewayandusenounsandtheconditionbeingtested.Also,remembertotestforexceptionsandwrongvaluesinsteadofjusttestingpositivecases.

Forexample,somevalidtestsandnamingcouldbe:

testOnCreateValuesAreLoaded()

testGivenIllegalArgumentThenAConversionErrorIsThrown()

testConvertingInputToStringIsValid()

Duringtheexecutionofthetest,someconditions,sideeffects,ormethodreturnsshouldbecomparedagainsttheexpectations.Toeasetheseoperations,JUnitprovidesafullsetofassert*methodstocomparetheexpectedresultsfromthetesttotheactualresultsafterrunningthem,throwingexceptionsiftheconditionsarenotmet.Then,thetestrunnerhandlestheseexceptionsandpresentstheresults.

Thesemethods,whichareoverloadedtosupportdifferentarguments,include:

assertTrue()

assertFalse()

assertEquals()

assertNull()

assertNotNull()

assertSame()

assertNotSame()

fail()

InadditiontotheseJUnitassertmethods,AndroidextendsAssertintwospecializedclasses,providingadditionaltests:

MoreAsserts

ViewAsserts

Mockobjects

Mockobjectsaremimicobjectsusedinsteadofcallingtherealdomainobjectstoenabletestingunitsinisolation.

Generally,thisisaccomplishedtoverifythatthecorrectmethodsarecalled,buttheycanalsobeofgreathelptoisolateyourtestsfromthesurroundingcodeandbeabletorunthetestsindependentlyandensurerepeatability.

TheAndroidtestingframeworksupportsmockobjectsthatyouwillfindveryusefulwhenwritingtests.Youneedtoprovidesomedependenciestobeabletocompilethetests.Therearealsoexternallibrariesthatcanbeusedwhenmocking.

SeveralclassesareprovidedbytheAndroidtestingframeworkintheandroid.test.mockpackage:

MockApplication

MockContentProvider

MockContentResolver

MockContext

MockCursor

MockDialogInterface

MockPackageManager

MockResources

AlmostanycomponentoftheplatformthatcouldinteractwithyourActivitycanbecreatedbyinstantiatingoneoftheseclasses.

However,theyarenotrealimplementationsbutstubs,theideabeingyouextendoneoftheseclassestocreatearealmockobjectandoverridethemethodsyouwanttoimplement.AnymethodsyoudonotoverridewillthrowanUnsupportedOperationException.

IntegrationtestsIntegrationtestsaredesignedtotestthewayindividualcomponentsworktogether.Modulesthathavebeenunittestedindependentlyarenowcombinedtogethertotesttheintegration.

Usually,AndroidActivitiesrequiresomeintegrationwiththesysteminfrastructuretobeabletorun.TheyneedtheActivitylifecycleprovidedbytheActivityManager,andaccesstoresources,thefilesystem,anddatabases.

ThesamecriteriaapplytootherAndroidcomponentssuchasServicesorContentProvidersthatneedtointeractwithotherpartsofthesystemtoachievetheirduty.

Inallthesecases,therearespecializedtestclassesprovidedbytheAndroidtestingframeworkthatfacilitatesthecreationoftestsforthesecomponents.

UItestsUserInterfaceteststestthevisualrepresentationofyourapplication,suchashowadialoglooksorwhatUIchangesaremadewhenadialogisdismissed.

SpecialconsiderationsshouldbetakenifyourtestsinvolveUIcomponents.Asyoumayhavealreadyknown,onlythemainthreadisallowedtoaltertheUIinAndroid.Thus,aspecialannotation@UIThreadTestisusedtoindicatethataparticulartestshouldberunonthatthreadanditwouldhavetheabilitytoaltertheUI.Ontheotherhand,ifyouonlywanttorunpartsofyourtestontheUIthread,youmayusetheActivity.runOnUiThread(Runnabler)methodthatprovidesthecorrespondingRunnable,whichcontainsthetestinginstructions.

AhelperclassTouchUtilsisalsoprovidedtoaidintheUItestcreation,allowingthegenerationofthefollowingeventstosendtotheViews,suchas:

ClickDragLongclickScrollTapTouch

Bythesemeans,youcanactuallyremotecontrolyourapplicationfromthetests.Also,AndroidhasrecentlyintroducedEspressoforUIinstrumentedtests,andwewillbecoveringthisinChapter3,BakingwithTestingRecipes.

FunctionaloracceptancetestsInagilesoftwaredevelopment,functionaloracceptancetestsareusuallycreatedbybusinessandQualityAssurance(QA)people,andexpressedinabusinessdomainlanguage.Thesearehigh-levelteststoassertthecompletenessandcorrectnessofauserstoryorfeature.Theyarecreatedideallythroughcollaborationbetweenbusinesscustomers,businessanalysts,QA,testers,anddevelopers.However,thebusinesscustomers(productowners)aretheprimaryownersofthesetests.

Someframeworksandtoolscanhelpinthisfield,suchasCalabash(http://calaba.sh)ormostnotablyFitNesse(http://www.fitnesse.org),whichcanbeeasilyintegrated,uptosomepoint,intotheAndroiddevelopmentprocess,andwillletyoucreateacceptancetestsandchecktheirresultsasfollows:

Lately,withinacceptancetesting,anewtrendnamedBehavior-drivenDevelopmenthasgainedsomepopularity,andinaverybriefdescription,itcanbeunderstoodasacousinofTest-drivenDevelopment.Itaimstoprovideacommonvocabularybetweenbusinessandtechnologypeopleinordertoincreasemutualunderstanding.

Behavior-drivenDevelopmentcanbeexpressedasaframeworkofactivitiesbasedonthreeprinciples(moreinformationcanbefoundathttp://behaviour-driven.org):

BusinessandtechnologyshouldrefertothesamesysteminthesamewayAnysystemshouldhaveanidentified,verifiablevaluetothebusinessUpfrontanalysis,design,andplanning,allhaveadiminishingreturn

Toapplytheseprinciples,businesspeopleareusuallyinvolvedinwritingtestcasescenariosinahigh-levellanguageanduseatoolsuchasjbehave(http://jbehave.org).Inthefollowingexample,thesescenariosaretranslatedintoJavacodethatexpressesthesametestscenario.

TestcasescenarioAsanillustrationofthistechnique,hereisanoversimplifiedexample.

Thescenario,aswrittenbyaproductowner,isasfollows:

GivenI'musingtheTemperatureConverter.

WhenIenter100intoCelsiusfield.

ThenIobtain212inFahrenheitfield.

Itwouldbetranslatedintosomethingsimilarto:

@Given("IamusingtheTemperatureConverter")

publicvoidcreateTemperatureConverter(){

//donothingthisissyntacticsugarforreadability

}

@When("Ienter$celsiusintoCelsiusfield")

publicvoidsetCelsius(intcelsius){

this.celsius=celsius;

}

@Then("Iobtain$fahrenheitinFahrenheitfield")

publicvoidtestCelsiusToFahrenheit(intfahrenheit){

assertEquals(fahrenheit,

TemperatureConverter.celsiusToFahrenheit(celsius));

}

Thisallowsboththeprogrammersandthebusinessuserstospeakthelanguageofthedomain(inthiscase,temperatureconversions),andbothareabletorelateitbacktotheirday-to-daywork.

PerformancetestsPerformancetestsmeasureperformancecharacteristicsofthecomponentsinarepeatableway.Ifperformanceimprovementsarerequiredbysomepartoftheapplication,thebestapproachistomeasureperformancebeforeandafterachangeisintroduced.

Asiswidelyknown,prematureoptimizationdoesmoreharmthangood,soitisbettertoclearlyunderstandtheimpactofyourchangesontheoverallperformance.

TheintroductionoftheDalvikJITcompilerinAndroid2.2changedsomeoptimizationpatternsthatwerewidelyusedinAndroiddevelopment.Nowadays,everyrecommendationaboutperformanceimprovementsintheAndroiddeveloper’ssiteisbackedupbyperformancetests.

SystemtestsThesystemistestedasawhole,andtheinteractionbetweenthecomponents,software,andhardwareisexercised.Normally,systemtestsincludeadditionalclassesoftestssuchas:

GUItestsSmoketestsMutationtestsPerformancetestsInstallationtests

AndroidStudioandotherIDEsupportJUnitisfullysupportedbyAndroidStudio,anditletsyoucreatetestedAndroidprojects.Furthermore,youcanrunthetestsandanalyzetheresultswithoutleavingtheIDE(tosomeextent).

Thisalsoprovidesamoresubtleadvantage;beingabletorunthetestsfromtheIDEallowsyoutodebugtheteststhatarenotbehavingcorrectly.

Inthefollowingscreenshot,wecanseehowASideruns19unittests,taking1.043seconds,with0Errorsand0Failuresdetected.Thenameofeachtestanditsdurationisalsodisplayed.Iftherewereafailure,theFailureTracewouldshowtherelatedinformation,asshowninthefollowingscreenshot:

ThereisalsoAndroidsupportinEclipseIDEusingtheAndroidDevelopmentToolsplugin.

EvenifyouarenotdevelopinginanIDE,youcanfindsupporttorunthetestswithgradle(checkhttp://gradle.orgifyouarenotfamiliarwiththistool).ThetestsarerunusingthecommandgradleconnectedAndroidTest.ThiswillinstallandrunthetestsforthedebugbuildonaconnectedAndroiddevice.

ThisisactuallythesamemethodthatAndroidStudiousesunderthehood.ASidewilljustruntheGradlecommandstobuildtheprojectandrunthetests,althoughwithselective

compilation.

JavatestingframeworkTheJavatestingframeworkisthebackboneofAndroidtesting,andsometimes,youcangetawaywithoutwritingAndroid-specificcode.Thiscanbeagoodthing,becauseaswecontinueonourtestingquest,youwillnoticethatwedeployAndroidframeworkteststoadevice,andthishasanimpactonthespeedofourtests,thatis,thespeedwegetfeedbackfromapassorafail.

Ifyouarchitectyourappinacleverway,youcancreatepureJavaclassesthatcanbetestedinisolationawayfromAndroid.Thetwomainbenefitsofthisareincreasedspeedoffeedbackfromtestresults,andalso,toquicklyplugtogetherlibrariesandcodesnippetstocreatepowerfultestsuites,youcanusetheneartenyearsofexperienceofotherprogrammersdoingJavatesting.

AndroidtestingframeworkAndroidprovidesaveryadvancedtestingframeworkthatextendstheindustrystandardJUnitlibrarywithspecificfeaturesthataresuitabletoimplementallofthetestingstrategiesandtypeswementionedbefore.Insomecases,additionaltoolsareneeded,buttheintegrationofthesetoolsis,inmostofthecases,simpleandstraightforward.

MostrelevantkeyfeaturesoftheAndroidtestingenvironmentinclude:

AndroidextensionstotheJUnitframeworkthatprovideaccesstoAndroidsystemobjectsAninstrumentationframeworkthatletsthetestscontrolandexaminetheapplicationMockversionsofcommonlyusedAndroidsystemobjectsToolstorunsingletestsortestsuites,withorwithoutinstrumentationSupporttomanagetestsandtestprojectsinAndroidStudioandatthecommandline

InstrumentationTheinstrumentationframeworkisthefoundationofthetestingframework.Instrumentationcontrolstheapplicationundertestsandpermitstheinjectionofmockcomponentsrequiredbytheapplicationtorun.Forexample,youcancreatemockContextsbeforetheapplicationstartsandlettheapplicationuseit.

Alltheinteractionsoftheapplicationwiththesurroundingenvironmentcanbecontrolledusingthisapproach.Youcanalsoisolateyourapplicationinarestrictedenvironmenttobeabletopredicttheresultsthatforcethevaluesreturnedbysomemethods,orthatmockpersistentandunchangeddatafortheContentProvider'sdatabasesoreventhefilesystemcontent.

AstandardAndroidprojecthasitsinstrumentationtestsinacorrelatedsourcefoldercalledandroidTest.Thiscreatesaseparateapplicationthatrunstestsonyourapplication.ThereisnoAndroidManifesthereasitisautomaticallygenerated.TheinstrumentationcanbecustomizedinsidetheAndroidclosureofyourbuild.gradlefile,andthesechangesarereflectedintheautogeneratedAndroidManifest.However,youcanstillrunyourtestswiththedefaultsettingsifyouchoosetochangenothing.

Examplesofthingsyoucanchangearethetestapplicationpackagename,yourtestrunner,orhowtotoggleperformance-testingfeatures:

testApplicationId"com.blundell.something.non.default"

testInstrumentationRunner"com.blundell.tut.CustomTestRunner"

testHandleProfilingfalse

testFunctionalTesttrue

testCoverageEnabledtrue

Here,theInstrumentationpackage(testApplicationId)isadifferentpackagetothemainapplication.Ifyoudon’tchangethisyourself,itwilldefaulttoyourmainapplicationpackagewiththe.testsuffixadded.

Then,theInstrumentationtestrunnerisdeclared,whichcanbehelpfulifyoucreatecustomannotationstoallowspecialbehavior;forexample,eachtestrunstwiceuponfailure.Inthecaseofnotdeclaringarunner,thedefaultcustomrunnerandroid.test.InstrumentationTestRunnerisused.

Atthemoment,testHandleProfilingandtestFunctionalTestareundocumentedandunused,sowatchoutforwhenwearetoldwhatwecandowiththese.SettingtestCoverageEnabledtotruewillallowyoutogathercodecoveragereportsusingJacoco.Wewillcomebacktothislater.

Also,noticethatboththeapplicationbeingtestedandtheteststhemselvesareAndroidapplicationswiththeircorrespondingAPKsinstalled.Internally,theywillbesharingthesameprocessandthushaveaccesstothesamesetoffeatures.

Whenyourunatestapplication,theActivityManager(http://developer.android.com/intl/de/reference/android/app/ActivityManager.html)usestheinstrumentationframeworktostartandcontrolthetestrunner,whichinturnuses

instrumentationtoshutdownanyrunninginstancesofthemainapplication,startsthetestapplication,andthenstartsthemainapplicationinthesameprocess.Thisallowsvariousaspectsofthetestapplicationtoworkdirectlywiththemainapplication.

GradleGradleisanadvancedbuildtoolkitthatallowsyoutomanagedependenciesanddefineacustomlogintobuildyourproject.TheAndroidbuildsystemisapluginontopofGradle,andthisiswhatgivesyouthedomain-specificlanguagediscussedpreviouslysuchassettingatestInstrumentationRunner.

TheideaofusingGradleisthatitallowsyoutobuildyourAndroidappsfromthecommandlineformachineswithoutusinganIDEsuchasacontinuousintegrationmachine.Also,withfirstlineintegrationofGradleintothebuildingofprojectsinAndroidStudio,yougettheexactsamecustombuildconfigurationfromtheIDEorcommandline.

Otherbenefitsincludebeingabletocustomizeandextendthebuildprocess;forexample,eachtimeyourCIbuildsyourproject,youcouldautomaticallyuploadabetaAPKtotheGoogleplaystore.YoucancreatemultipleAPKswithdifferentfeaturesusingthesameproject,forexample,oneversionthattargetsGoogleplayinanapppurchaseandanotherthattargetstheAmazonappstore’scoinpayments.

GradleandtheAndroidGradlepluginmakeforapowerfulcombination,andso,wewillbeusingthisbuildframeworkthroughouttherestofthesamplesinthisbook.

TesttargetsDuringtheevolutionofyourdevelopmentproject,yourtestswouldbetargetedtodifferentdevices.Fromsimplicity,flexibility,andspeedoftestingonanemulatortotheunavoidablefinaltestingonthespecificdeviceyouareintendingyourapplicationtoberunupon,youshouldbeabletorunyourapplicationonallofthem.

TherearealsosomeintermediatecasessuchasrunningyourtestsonalocalJVMvirtualmachine,onthedevelopmentcomputer,oronaDalvikvirtualmachineorActivity,dependingonthecase.

Everycasehasitsprosandcons,butthegoodnewsisthatyouhaveallofthesealternativesavailabletorunyourtests.

Theemulatorisprobablythemostpowerfultargetasyoucanmodifyalmosteveryparameterfromitsconfigurationtosimulatedifferentconditionsforyourtests.Ultimately,yourapplicationshouldbeabletohandleallofthesesituations,soit’smuchbettertodiscovertheproblemsupfrontthanwhentheapplicationhasbeendelivered.

Therealdevicesarearequirementforperformancetests,asitissomewhatdifficulttoextrapolateperformancemeasurementsfromasimulateddevice.Youwillenjoytherealuserexperienceonlywhenusingtherealdevice.Rendering,scrolling,flinging,andothercasesshouldbetestedbeforedeliveringtheapplication.

CreatingtheAndroidprojectWewillcreateanewAndroidproject.ThisisdonefromtheASidemenubygoingtoFile|NewProject.Thisthenleadsusthroughthewysiwygguidetocreateaproject.

Inthisparticularcase,weareusingthefollowingvaluesfortherequiredcomponentnames(clickingontheNextbuttoninbetweenscreens):

Applicationname:AndroidApplicationTestingGuideCompanydomain:blundell.comFormfactor:PhoneandTabletMinimumSDK:17AddanActivity:BlankActivity(gowithdefaultnames)

Thefollowingscreenshotshowsthestartoftheformeditorforreference:

WhenyouclickonFinishandtheapplicationiscreated,itwillautomaticallygeneratetheandroidTestsourcefolderundertheapp/srcdirectory,andthisiswhereyoucanaddyourinstrumentedtestcases.

TipAlternatively,tocreateanandroidTestfolderforanexistingGradleAndroidproject,youcanselectthesrcfolderandthengotoFile|New|Directory.Then,writeandroidTest/javainthedialogprompt.Whentheprojectrebuilds,thepathwillthenautomaticallybeaddedsothatyoucancreatetests.

PackageexplorerAfterhavingcreatedourproject,theprojectviewshouldlooklikeoneoftheimagesshowninthefollowingscreenshot.ThisisbecauseASidehasmultiplewaystoshowtheprojectoutline.Ontheleft,wecannotetheexistenceofthetwosourcedirectories,onecoloredgreenforthetestsourceandtheotherbluefortheprojectsource.Ontheright,wehavethenewAndroidprojectviewthattriestosimplifythehierarchybycompressinguselessandmergingfunctionallysimilarfolders.

Nowthatwehavethebasicinfrastructuresetup,it’stimeforustostartaddingsometests,asshowninthefollowingscreenshot:

There’snothingtotestrightnow,butaswearesettingupthefundamentalsofaTest-drivenDevelopmentdiscipline,weareaddingadummytestjusttogetacquaintedwiththetechnique.

Thesrc/androidTest/javafolderinyourAndroidApplicationTestingGuideprojectistheperfectplacetoaddthetests.Youcoulddeclareadifferentfolderifyoureallywantedto,butwe’restickingtodefaults.Thepackageshouldbethesameasthecorrespondingpackageofthecomponentbeingtested.

Rightnow,wearenotconcentratingonthecontentofthetestsbutontheconceptsandplacementofthosetests.

CreatingatestcaseAsdescribedbefore,wearecreatingourtestcasesinthesrc/androidTest/javafolderoftheproject.

Youcancreatethefilemanuallybyright-clickingonthepackageandselectingNew…|JavaClass.However,inthisparticularcase,we’lltakeadvantageofASidetocreateourJUnitTestCase.Opentheclassundertest(inthiscase,MainActivity)andhoverovertheclassnameuntilyouseealightbulb(orpressCtrl/Command+1).SelectCreateTestfromthemenuthatappears.

Thesearethevaluesthatweshouldenterwhenwecreatethetestcase:

Testinglibrary:JUnit3Classname:MainActivityTestSuperclass:junit.framework.TestCaseDestinationpackage:com.blundell.tutSuperclass:junit.framework.TestCaseGenerate:Selectnone

Afterenteringalltherequiredvalues,ourJUnittestcasecreationdialogwouldlooklikethis.

Asyoucansee,youcouldalsohavecheckedoneofthemethodsoftheclasstogenerateanemptytestmethodstub.Thesestubmethodsmaybeusefulinsomecases,butyouhavetoconsiderthattestingshouldbeabehavior-drivenprocessratherthanamethod-drivenone.

Thebasicinfrastructureforourtestsisinplace;whatisleftistoaddadummytesttoverifythateverythingisworkingasexpected.Wenowhaveatestcasetemplate,sothenextstepistostartcompletingittosuitourneeds.Todoit,opentherecentlycreatedtestclassandaddthetestSomething()test.

Weshouldhavesomethinglikethis:

packagecom.blundell.tut;

importandroid.test.suitebuilder.annotation.SmallTest;

importjunit.framework.TestCase;

publicclassMainActivityTestextendsTestCase{

publicMainActivityTest(){

super("MainActivityTest");

}

@SmallTest

publicvoidtestSomething()throwsException{

fail("Notimplementedyet");

}

}

TipTheno-argumentconstructorisneededtorunaspecifictestfromthecommandline,asexplainedlaterusingaminstrumentation.

Thistestwillalwaysfail,presentingthemessage:Notimplementedyet.Inordertodothis,wewillusethefailmethodfromthejunit.framework.Assertclassthatfailsthetestwiththegivenmessage.

TestannotationsLookingcarefullyatthetestdefinition,youmightnoticethatwedecoratedthetestusingthe@SmallTestannotation,whichisawaytoorganizeorcategorizeourtestsandrunthemseparately.

Thereareotherannotationsthatcanbeusedbythetests,suchas:

Annotation Description

@SmallTest Marksatestthatshouldrunaspartofthesmalltests.

@MediumTest Marksatestthatshouldrunaspartofthemediumtests.

@LargeTest Marksatestthatshouldrunaspartofthelargetests.

@SmokeMarksatestthatshouldrunaspartofthesmoketests.Theandroid.test.suitebuilder.SmokeTestSuiteBuilderwillrunalltestswiththisannotation.

@FlakyTest

UsethisannotationontheInstrumentationTestCaseclass’testmethods.Whenthisispresent,thetestmethodisre-executedifthetestfails.Thetotalnumberofexecutionsisspecifiedbythetolerance,anddefaultsto1.Thisisusefulforteststhatmayfailduetoanexternalconditionthatcouldvarywithtime.

Forexample,tospecifyatoleranceof4,youwouldannotateyourtestwith:@FlakyTest(tolerance=4).

@UIThreadTest

UsethisannotationontheInstrumentationTestCaseclass’testmethods.Whenthisispresent,thetestmethodisexecutedontheapplication’smainthread(orUIthread).

Asinstrumentationmethodsmaynotbeusedwhenthisannotationispresent,thereareothertechniquesif,forexample,youneedtomodifytheUIandgetaccesstotheinstrumentationwithinthesametest.

Insuchcases,youcanresorttotheActivity.runOnUIThread()methodthatallowsyoutocreateanyRunnableandrunitintheUIthreadfromwithinyourtest:

mActivity.runOnUIThread(newRunnable(){

publicvoidrun(){

//dosomethings

}

});

@Suppress

Usethisannotationontestclassesortestmethodsthatshouldnotbeincludedinatestsuite.

Thisannotationcanbeusedattheclasslevel,wherenoneofthemethodsinthatclassareincludedinthetestsuite,oratthemethodlevel,toexcludejustasinglemethodorasetofmethods.

Nowthatwehavethetestsinplace,it’stimetorunthem,andthat’swhatwearegoingtodonext.

RunningthetestsThereareseveralwaysofrunningourtests,andwewillanalyzethemhere.

Additionally,asmentionedintheprevioussectionaboutannotations,testscanbegroupedorcategorizedandruntogether,dependingonthesituation.

RunningalltestsfromAndroidStudioThisisperhapsthesimplestmethodifyouhaveadoptedASideasyourdevelopmentenvironment.Thiswillrunallthetestsinthepackage.

SelecttheappmoduleinyourprojectandthengotoRun|(androidicon)AllTests.

Ifasuitabledeviceoremulatorisnotfound,youwillbeaskedtostartorconnectone.

Thetestsarethenrun,andtheresultsarepresentedinsidetheRunperspective,asshowninthefollowingscreenshot:

AmoredetailedviewoftheresultsandthemessagesproducedduringtheirexecutioncanalsobeobtainedintheLogCatviewwithintheAndroidDDMSperspective,asshowninthefollowingscreenshot:

RunningasingletestcasefromyourIDEThereisanoptiontorunasingletestcasefromASide,shouldyouneedto.Openthefilewherethetestresides,right-clickonthemethodnameyouwanttorun,andjustlikeyourunallthetests,selectRun|(androidicon)testMethodName.

Whenyourunthis,asusual,onlythistestwillbeexecuted.Inourcase,wehaveonlyonetest,sotheresultwillbesimilartothescreenshotpresentedearlier.

NoteRunningasingletestlikethisisashortcutthatactuallycreatesarunconfigurationforyouthatisspecifictothatonemethod.Ifyouwanttolookintothedetailsofthis,fromthemenu,selectRun|EditConfigurations,andunderAndroidTests,youshouldbeabletoseeaconfigurationwiththenameofthetestyoujustexecuted.

RunningfromtheemulatorThedefaultsystemimageusedbytheemulatorhastheDevToolsapplicationinstalled,providingseveralhandytoolsandsettings.Amongthesetools,wecanfindaratherlonglist,asisshowninthefollowingscreenshot:

Now,weareinterestedinInstrumentation,whichisthewaytorunourtests.Thisapplicationlistsallofthepackagesinstalledthatdefineinstrumentationtagtestsintheirproject.Wecanrunthetestsbyselectingourtestsbasedonthepackagename,asshowninthefollowingscreenshot:

Whenthetestsareruninthisway,theresultscanbeseenthroughDDMS/LogCat,asdescribedintheprevioussection.

RunningtestsfromthecommandlineFinally,testscanberunfromthecommandlinetoo.Thisisusefulifyouwanttoautomateorscripttheprocess.

Torunthetests,weusetheaminstrumentcommand(strictlyspeaking,theamcommandandinstrumentsubcommand),whichallowsustoruninstrumentationsspecifyingthepackagenameandsomeotheroptions.

Youmightwonderwhat“am”standsfor.ItisshortforActivityManager,amaincomponentoftheinternalAndroidinfrastructurethatisstartedbytheSystemServeratthebeginningofthebootprocess,anditisresponsibleformanagingActivitiesandtheirlifecycle.Additionally,aswecanseehere,itisalsoresponsibleforActivityinstrumentation.

Thegeneralusageoftheaminstrumentcommandis:

aminstrument[flags]<COMPONENT>-r-e<NAME><VALUE>-p<FILE>-w

Thistablesummarizesthemostcommonoptions:

Option Description

-r Printsrawresults.Thisisusefultocollectrawperformancedata.

-e<NAME>

<VALUE>

Setsargumentsbyname.Wewillexamineitsusageshortly.Thisisagenericoptionargumentthatallowsustosetthe<name,value>pairs.

-p<FILE> Writesprofilingdatatoanexternalfile.

-wWaitsforinstrumentationtofinishbeforeexiting.Thisisnormallyusedincommands.Althoughnotmandatory,it’sveryhandy,asotherwise,youwillnotbeabletoseethetest’sresults.

Toinvoketheamcommand,wewillbeusingtheadbshellcommandor,ifyoualreadyhaveashellrunningonanemulatorordevice,youcanissuetheamcommanddirectlyintheshellcommandprompt.

RunningalltestsThiscommandlinewillopentheadbshellandthenrunalltestswiththeexceptionofperformancetests:

$:adbshell

#:aminstrument-w

com.blundell.tut.test/android.test.InstrumentationTestRunner

com.blundell.tut.MainActivityTest:

FailureintestSomething:

junit.framework.AssertionFailedError:Notimplementedyet

at

com.blundell.tut.MainActivityTest.testSomething(MainActivityTest.java:15)

atjava.lang.reflect.Method.invokeNative(NativeMethod)

atandroid.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)

atandroid.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)

atandroid.test.InstrumentationTestRunner.onStart

(InstrumentationTestRunner.java:554)

atandroid.app.Instrumentation$InstrumentationThread.run

(Instrumentation.java:1701)

TestresultsforInstrumentationTestRunner=.F

Time:0.002

FAILURES!!!

Testsrun:1,Failures:1,Errors:0

Notethatthepackageyoudeclarewith–wisthepackageofyourinstrumentationtests,notthepackageoftheapplicationundertest.

RunningtestsfromaspecifictestcaseTorunallthetestsinaspecifictestcase,youcanuse:

$:adbshell

#:aminstrument-w-eclasscom.blundell.tut.MainActivityTest

com.blundell.tut.test/android.test.InstrumentationTestRunner

RunningaspecifictestbynameAdditionally,wehavethealternativeofspecifyingwhichtestwewanttoruninthecommandline:

$:adbshell

#:aminstrument-w-eclass

com.blundell.tut.MainActivityTest\#testSomething

com.blundell.tut.test/android.test.InstrumentationTestRunner

Thistestcannotberuninthiswayunlesswehaveano-argumentconstructorinourtestcase;thatisthereasonweaddeditbefore.

RunningspecifictestsbycategoryAsmentionedbefore,testscanbegroupedintodifferentcategoriesusingannotations(TestAnnotations),andyoucanrunalltestsinthiscategory.

Thefollowingoptionscanbeaddedtothecommandline:

Option Description

-eunittrueThisrunsallunittests.TheseareteststhatarenotderivedfromInstrumentationTestCase(andarenotperformancetests).

-efunctrueThisrunsallfunctionaltests.TheseareteststhatarederivedfromInstrumentationTestCase.

-eperftrue Thisincludesperformancetests.

-esize{small|medium

|large}Thisrunssmall,medium,orlargetestsdependingontheannotationsaddedtothetests.

-eannotation

<annotation-name>

Thisrunstestsannotatedwiththisannotation.Thisoptionismutuallyexclusivewiththesizeoption.

Inourexample,weannotatedthetestmethodtestSomething()with@SmallTest.Sothistestisconsideredtobeinthatcategory,andisthusruneventuallywithotherteststhatbelongtothatsamecategory,whenwespecifythetestsizeassmall.

Thiscommandlinewillrunallthetestsannotatedwith@SmallTest:

$:adbshell

#:aminstrument-w-esizesmall

com.blundell.tut.test/android.test.InstrumentationTestRunner

RunningtestsusingGradleYourgradlebuildscriptcanalsohelpyourunthetestsandthiswillactuallydothepreviouscommandsunderthehood.Gradlecanrunyourtestswiththiscommand:

gradleconnectedAndroidTest

Creatingacustomannotation

Incaseyoudecidetosortthetestsbyacriterionotherthantheirsize,acustomannotation

canbecreatedandthenspecifiedinthecommandline.

Asanexample,let’ssaywewanttoarrangeourtestsaccordingtotheirimportance,sowecreateanannotation@VeryImportantTest,whichwewilluseinanyclasswherewewritetests(MainActivityTestforexample):

packagecom.blundell.tut;

/**

*Markerinterfacetosegregateimportanttests

*/

@Retention(RetentionPolicy.RUNTIME)

public@interfaceVeryImportantTest{

}

Followingthis,wecancreateanothertestandannotateitwith@VeryImportantTest:

@VeryImportantTest

publicvoidtestOtherStuff(){

fail("Alsonotimplementedyet");

}

So,aswementionedbefore,wecanincludethisannotationintheaminstrumentcommandlinetorunonlytheannotatedtests:

$:adbshell

#:aminstrument-w-eannotationcom.blundell.tut.VeryImportantTest

com.blundell.tut.test/android.test.InstrumentationTestRunner

Runningperformancetests

WewillbereviewingperformancetestdetailsinChapter8,TestingandProfilingPerformance,buthere,wewillintroducetheavailableoptionstotheaminstrumentcommand.

Toincludeperformancetestsonyourtestrun,youshouldaddthiscommandlineoption:

-eperftrue:Thisincludesperformancetests

Dryrun

Sometimes,youmightonlyneedtoknowwhattestswillberuninsteadofactuallyrunningthem.

Thisistheoptionyouneedtoaddtoyourcommandline:

-elogtrue:Thisdisplaystheteststoberuninsteadofrunningthem

Thisisusefulifyouarewritingscriptsaroundyourtestsorperhapsbuildingothertools.

DebuggingtestsYoushouldassumethatyourtestsmighthavebugstoo.Insuchacase,usualdebuggingtechniquesapply,forexample,addingmessagesthroughLogCat.

Ifamoresophisticateddebuggingtechniqueisneeded,youshouldattachthedebuggertothetestrunner.

InordertodothiswithoutgivingupontheconvenienceoftheIDEandnothavingtorememberhard-to-memorizecommand-lineoptions,youcanDebugRunyourrunconfigurations.Thus,youcansetabreakpointinyourtestsanduseit.Totoggleabreakpoint,youcanselectthedesiredlineintheeditorandleft-clickonthemargin.

Onceitisdone,youwillbeinastandarddebuggingsession,andthedebugwindowshouldbeavailabletoyou.

Itisalsopossibletodebugyourtestsfromthecommandline;youcanusecodeinstructionstowaitforyourdebuggertoattach.Wewon’tbeusingthiscommand;ifyouwantmoredetails,theycanbefoundat(http://developer.android.com/reference/android/test/InstrumentationTestRunner.html).

Othercommand-lineoptionsTheaminstrumentcommandacceptsother<name,value>pairsbesidethepreviouslymentionedones:

Name Value

debug true.Setbreakpointsinyourcode.

package Thisisafullyqualifiedpackagenameofoneorseveralpackagesinthetestapplication.

classAfullyqualifiedtestcaseclasstobeexecutedbythetestrunner.Optionally,thiscouldincludethetestmethodnameseparatedfromtheclassnamebyahash(#).

coverage

true.RunstheEMMAcodecoverageandwritestheoutputtoafilethatcanalsobespecified.WewilldigintothedetailsaboutsupportingEMMAcodecoverageforourtestsinChapter9,AlternativeTestingTactics.

SummaryWehavereviewedthemaintechniquesandtoolsbehindtestingonAndroid.Havingacquiredthisknowledge,itwillletusbeginourjourneysothatwecanstartexploitingthebenefitsoftestinginoursoftwaredevelopmentprojects.

Sofar,wehavevisitedthefollowingsubjects:

Webrieflyanalyzedthewhys,whats,hows,andwhensoftesting.Henceforth,wewillconcentratemoreonexploringthehows,nowthatyou’regivingtestingtheimportanceitdeserves.Weenumeratedthedifferentandmostcommontypesoftestsyouwouldneedinyourprojects,describedsomeofthetoolswecancountonourtestingtoolbox,andprovidedanintroductoryexampleofaJUnitunittesttobetterunderstandwhatwearediscussing.WealsocreatedourfirstAndroidprojectwithtests,usingtheAndroidStudioIDEandGradle.WealsocreatedasimpletestclasstotesttheActivityinourproject.Wehaven’taddedanyusefultestcasesyet,butaddingthosesimpleoneswasintendedtovalidateourinfrastructure.WealsoranthissimpletestfromourIDEandfromthecommandlinetounderstandthealternativeswehave.Inthisprocess,wementionedtheActivityManageranditscommandlineincarnationam.Wecreatedacustomannotationtosortourtestsanddemonstratehowwecanseparateordifferentiatesuitesoftests.

Inthenextchapter,wewillstartanalyzingthementionedtechniques,frameworks,andtoolsinmuchgreaterdetail,andprovideexamplesoftheirusage.

Chapter2.UnderstandingTestingwiththeAndroidSDKWenowknowhowtocreatetestsinsideanAndroidprojectandhowtorunthesetests.Itisnowtimetostartdiggingabitdeepertorecognizethebuildingblocksavailabletocreatemoreusefultests.

Inthissecondchapter,wewillbecoveringthefollowingtopics:

CommonassertionsViewassertionsOtherassertiontypesHelperstotestUserInterfacesMockobjectsInstrumentationTestCaseclasshierarchiesUsingexternallibraries

Wewillbeanalyzingthesecomponentsandshowingexamplesoftheirusewhenapplicable.TheexamplesinthischapterareintentionallysplitfromtheoriginalAndroidprojectthatcontainsthem.Thisisdonetoletyouconcentrateandfocusonlyonthesubjectbeingpresented,thoughthecompleteexamplesinasingleprojectcanbedownloadedasexplainedlater.Rightnow,weareinterestedinthetreesandnottheforest.

Alongwiththeexamplespresented,wewillbeidentifyingreusablecommonpatternsthatwillhelpyouinthecreationoftestsforyourownprojects.

ThedemonstrationapplicationAverysimpleapplicationhasbeencreatedtodemonstratetheuseofsomeofthetestsinthischapter.ThesourcefortheapplicationcanbedownloadedfromXXXXXXXXXXXXX.

Thefollowingscreenshotshowsthisapplicationrunning:

Whenreadingtheexplanationofthetestsinthischapter,atanypoint,youcanrefertothedemoapplicationthatisprovidedinordertoseethetestinaction.Theprevioussimpleapplicationhasaclickablelink,textinput,clickonabuttonandadefinedlayoutUI,wecantesttheseonebyone.

AssertionsindepthAssertionsaremethodsthatcheckforaconditionthatcanbeevaluated.Iftheconditionisnotmet,theassertionmethodwillthrowanexception,therebyabortingtheexecutionofthetest.

TheJUnitAPIincludestheclassAssert.ThisisthebaseclassofalltheTestCaseclassesthatholdseveralassertionmethodsusefulforwritingtests.Theseinheritedmethodstestforavarietyofconditionsandareoverloadedtosupportdifferentparametertypes.Theycanbegroupedtogetherinthefollowingdifferentsets,dependingontheconditionchecked,forexample:

assertEqualsassertTrueassertFalseassertNullassertNotNullassertSameassertNotSamefail

Theconditiontestedisprettyobviousandiseasilyidentifiablebythemethodname.PerhapstheonesthatdeservesomeattentionareassertEquals()andassertSame().Theformer,whenusedonobjects,assertsthatbothobjectspassedasparametersareequallycallingtheobjects’equals()method.Thelatterassertsthatbothobjectsrefertothesameobject.If,insomecase,equals()isnotimplementedbytheclass,thenassertEquals()andassertSame()willdothesamething.

Whenoneoftheseassertionsfailsinsideatest,anAssertionFailedExceptionisthrown,andthisindicatesthatthetesthasfailed.

Occasionally,duringthedevelopmentprocess,youmightneedtocreateatestthatyouarenotimplementingatthatprecisetime.However,youwanttoflagthatthecreationofthetestwaspostponed(wedidthisinChapter1,GettingStartedwithTesting,whenweaddedjustthetestmethodstubs).Insuchcases,youcanusethefail()method,whichalwaysfailsandusesacustommessagethatindicatesthecondition:

publicvoidtestNotImplementedYet(){

fail("Notimplementedyet");

}

Still,thereisanothercommonuseforfail()thatisworthmentioning.Ifweneedtotestwhetheramethodthrowsanexception,wecansurroundthecodewithatry-catchblockandforceafailiftheexceptionwasnotthrown.Forexample:

publicvoidtestShouldThrowException(){

try{

MyFirstProjectActivity.methodThatShouldThrowException();

fail("Exceptionwasnotthrown");

}catch(Exceptionex){

//donothing

}

}

NoteJUnit4hastheannotation@Test(expected=Exception.class),andthissupersedestheneedforusingfail()whentestingexceptions.Withthisannotation,thetestwillonlypassiftheexpectedexceptionisthrown.

CustommessagesItisworthknowingthatallassertmethodsprovideanoverloadedversionincludingacustomStringmessage.Shouldtheassertionfail,thiscustommessagewillbeprintedbythetestrunner,insteadofadefaultmessage.

Thepremisebehindthisisthat,sometimes,thegenericerrormessagedoesnotrevealenoughdetails,anditisnotobvioushowthetestfailed.Thiscustommessagecanbeextremelyusefultoeasilyidentifythefailureonceyouarelookingatthetestreport,soit’shighlyrecommendedasabestpracticetousethisversion.

Thefollowingisanexampleofasimpletestthatusesthisrecommendation:

publicvoidtestMax(){

inta=10;

intb=20;

intactual=Math.max(a,b);

StringfailMsg="Expected:"+b+"butwas:"+actual;

assertEquals(failMsg,b,actual);

}

Intheprecedingexample,wecanseeanotherpracticethatwouldhelpyouorganizeandunderstandyourtestseasily.Thisistheuseofexplicitnamesforvariablesthatholdtheactualvalues.

NoteThereareotherlibrariesavailablethathavebetterdefaulterrormessagesandalsoamorefluidinterfacefortesting.OneofthesethatisworthlookingatisFest(https://code.google.com/p/fest/).

StaticimportsThoughbasicassertionmethodsareinheritedfromtheAssertbaseclass,someotherassertionsneedspecificimports.Toimprovethereadabilityofyourtests,thereisapatterntostaticallyimporttheassertmethodsfromthecorrespondingclasses.Usingthispatterninsteadofhaving:

publicvoidtestAlignment(){

intmargin=0;

...

android.test.ViewAsserts.assertRightAligned(errorMsg,editText,margin);

}

Wecansimplifyitbyaddingthestaticimport:

importstaticandroid.test.ViewAsserts.assertRightAligned;

publicvoidtestAlignment(){

intmargin=0;

assertRightAligned(errorMsg,editText,margin);

}

ViewassertionsTheassertionsintroducedearlierhandleavarietyoftypesasparameters,buttheyareonlyintendedtotestsimpleconditionsorsimpleobjects.

Forexample,wehaveasertEquals(shortexpected,shortactual)totestshortvalues,assertEquals(intexpected,intactual)totestintegervalues,assertEquals(Objectexpected,Objectexpected)totestanyObjectinstance,andsoon.

Usually,whiletestinguserinterfacesinAndroid,youwillfacetheproblemofmoresophisticatedmethods,whicharemainlyrelatedwithViews.Inthisrespect,Androidprovidesaclasswithplentyofassertionsinandroid.test.ViewAsserts(seehttp://developer.android.com/reference/android/test/ViewAsserts.htmlformoredetails),whichtestrelationshipsbetweenViewsandtheirabsoluteandrelativepositionsonthescreen.

Thesemethodsarealsooverloadedtoprovidedifferentconditions.Amongtheassertions,wecanfindthefollowing:

assertBaselineAligned:ThisassertsthattwoViewsarealignedontheirbaseline;thatis,theirbaselinesareonthesameylocation.assertBottomAligned:Thisassertsthattwoviewsarebottomaligned;thatis,theirbottomedgesareonthesameylocation.assertGroupContains:Thisassertsthatthespecifiedgroupcontainsaspecificchildonceandonlyonce.assertGroupIntegrity:Thisassertsthespecifiedgroup’sintegrity.Thechildcountshouldbe>=0andeachchildshouldbenon-null.assertGroupNotContains:Thisassertsthatthespecifiedgroupdoesnotcontainaspecificchild.assertHasScreenCoordinates:ThisassertsthataViewhasaparticularxandypositiononthevisiblescreen.assertHorizontalCenterAligned:ThisassertsthatthetestViewishorizontallycenteralignedwithrespecttothereferenceview.assertLeftAligned:ThisassertsthattwoViewsareleftaligned;thatis,theirleftedgesareonthesamexlocation.Anoptionalmargincanalsobeprovided.assertOffScreenAbove:Thisassertsthatthespecifiedviewisabovethevisiblescreen.assertOffScreenBelow:Thisassertsthatthespecifiedviewisbelowthevisiblescreen.assertOnScreen:ThisassertsthataViewisonthescreen.assertRightAligned:ThisassertsthattwoViewsareright-aligned;thatis,theirrightedgesareonthesamexlocation.Anoptionalmargincanalsobespecified.assertTopAligned:ThisassertsthattwoViewsaretopaligned;thatis,theirtopedgesareonthesameylocation.Anoptionalmargincanalsobespecified.assertVerticalCenterAligned:ThisassertsthatthetestViewisverticallycenter-

alignedwithrespecttothereferenceView.

ThefollowingexampleshowshowyoucanuseViewAssertionstotesttheuserinterfacelayout:

publicvoidtestUserInterfaceLayout(){

intmargin=0;

Vieworigin=mActivity.getWindow().getDecorView();

assertOnScreen(origin,editText);

assertOnScreen(origin,button);

assertRightAligned(editText,button,margin);

}

TheassertOnScreenmethodusesanorigintostartlookingfortherequestedViews.Inthiscase,weareusingthetop-levelwindowdecorView.If,forsomereason,youdon’tneedtogothathighinthehierarchy,orifthisapproachisnotsuitableforyourtest,youmayuseanotherrootViewinthehierarchy,forexampleView.getRootView(),which,inourconcreteexample,wouldbeeditText.getRootView().

EvenmoreassertionsIftheassertionsthatarereviewedpreviouslydonotseemtobeenoughforyourtests’needs,thereisstillanotherclassincludedintheAndroidframeworkthatcoversothercases.ThisclassisMoreAsserts(http://developer.android.com/reference/android/test/MoreAsserts.html).

Thesemethodsarealsooverloadedtosupportdifferentparametertypes.Amongtheassertions,wecanfindthefollowing:

assertAssignableFrom:Thisassertsthatanobjectisassignabletoaclass.assertContainsRegex:ThisassertsthatanexpectedRegexmatchesanysubstringofthespecifiedString.Itfailswiththespecifiedmessageifitdoesnot.assertContainsInAnyOrder:ThisassertsthatthespecifiedIterablecontainspreciselytheelementsexpected,butinanyorder.assertContainsInOrder:ThisassertsthatthespecifiedIterablecontainspreciselytheelementsexpected,butinthesameorder.assertEmpty:ThisassertsthatanIterableisempty.assertEquals:ThisisforsomeCollectionsnotcoveredinJUnitasserts.assertMatchesRegex:ThisassertsthatthespecifiedRegexexactlymatchestheStringandfailswiththeprovidedmessageifitdoesnot.assertNotContainsRegex:ThisassertsthatthespecifiedRegexdoesnotmatchanysubstringofthespecifiedString,andfailswiththeprovidedmessageifitdoes.assertNotEmpty:ThisassertsthatsomeCollectionsnotcoveredinJUnitassertsarenotempty.assertNotMatchesRegex:ThisassertsthatthespecifiedRegexdoesnotexactlymatchthespecifiedString,andfailswiththeprovidedmessageifitdoes.checkEqualsAndHashCodeMethods:Thisisautilityusedtotesttheequals()andhashCode()resultsatonce.Thistestswhetherequals()thatisappliedtobothobjectsmatchesthespecifiedresult.

ThefollowingtestchecksforanerrorduringtheinvocationofthecapitalizationmethodcalledviaaclickontheUIbutton:

@UiThreadTest

publicvoidtestNoErrorInCapitalization(){

Stringmsg="capitalizethistext";

editText.setText(msg);

button.performClick();

Stringactual=editText.getText().toString();

StringnotExpectedRegexp="(?i:ERROR)";

StringerrorMsg="Capitalizationerrorfor"+actual;

assertNotContainsRegex(errorMsg,notExpectedRegexp,actual);

}

Ifyouarenotfamiliarwithregularexpressions,investsometimeandvisithttp://developer.android.com/reference/java/util/regex/package-summary.htmlbecauseit

willbeworthit!

Inthisparticularcase,wearelookingforthewordERRORcontainedintheresultwithacase-insensitivematch(settingtheflagiforthispurpose).Thatis,ifforsomereason,capitalizationdoesn’tworkinourapplication,anditcontainsanerrormessage,wecandetectthisconditionwiththeassertion.

NoteNotethatbecausethisisatestthatmodifiestheuserinterface,wemustannotateitwith@UiThreadTest;otherwise,itwon’tbeabletoaltertheUIfromadifferentthread,andwewillreceivethefollowingexception:

INFO/TestRunner(610):-----beginexception-----

INFO/TestRunner(610):android.view.ViewRoot$CalledFromWrongThreadException:

Onlytheoriginalthreadthatcreatedaviewhierarchycantouchitsviews.

INFO/TestRunner(610):at

android.view.ViewRoot.checkThread(ViewRoot.java:2932)

[...]

INFO/TestRunner(610):at

android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:

1447)

INFO/TestRunner(610):-----endexception-----

TheTouchUtilsclassSometimes,whentestingUIs,itishelpfultosimulatedifferentkindsoftouchevents.Thesetoucheventscanbegeneratedinmanydifferentways,butprobablyandroid.test.TouchUtilsisthesimplesttouse.ThisclassprovidesreusablemethodstogeneratetoucheventsintestcasesthatarederivedfromInstrumentationTestCase.

ThefeaturedmethodsallowasimulatedinteractionwiththeUIundertest.TheTouchUtilsclassprovidestheinfrastructuretoinjecttheeventsusingthecorrectUIormainthread,sonospecialhandlingisneeded,andyoudon’tneedtoannotatethetestusing@UIThreadTest.

TouchUtilssupportsthefollowing:

ClickingonaViewandreleasingitTappingonaView(touchingitandquicklyreleasing)Long-clickingonaViewDraggingthescreenDraggingViews

ThefollowingtestrepresentsatypicalusageofTouchUtils:

publicvoidtestListScrolling(){

listView.scrollTo(0,0);

TouchUtils.dragQuarterScreenUp(this,activity);

intactualItemPosition=listView.getFirstVisiblePosition();

assertTrue("Wrongposition",actualItemPosition>0);

}

Thistestdoesthefollowing:

RepositionsthelistatthebeginningtostartfromaknownconditionScrollsthelistChecksforthefirstvisiblepositiontoseethatitwascorrectlyscrolled

EventhemostcomplexUIscanbetestedinthatway,anditwouldhelpyoudetectavarietyofconditionsthatcouldpotentiallyaffecttheuserexperience.

MockobjectsWehaveseenthemockobjectsprovidedbytheAndroidtestingframeworkinChapter1,GettingStartedwithTesting,andevaluatedtheconcernsaboutnotusingrealobjectstoisolateourtestsfromthesurroundingenvironment.

ThenextchapterdealswithTest-drivenDevelopment,andifwewereTest-drivenDevelopmentpurists,wecanargueabouttheuseofmockobjectsandbemoreinclinedtouserealones.MartinFowlercallsthesetwostylestheclassicalandmockistTest-drivenDevelopmentdichotomyinhisgreatarticleMocksaren’tstubs,whichcanbereadonlineathttp://www.martinfowler.com/articles/mocksArentStubs.html.

Independentofthisdiscussion,weareintroducingmockobjectsasoneoftheavailablebuildingblocksbecause,sometimes,usingmockobjectsinourtestsisrecommended,desirable,useful,orevenunavoidable.

TheAndroidSDKprovidesthefollowingclassesinthesubpackageandroid.test.mocktohelpus:

MockApplication:ThisisamockimplementationoftheApplicationclass.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockContentProvider:ThisisamockimplementationofContentProvider.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockContentResolver:ThisisamockimplementationoftheContentResolverclassthatisolatesthetestcodefromtherealcontentsystem.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockContext:Thisisamockcontextclass,andthiscanbeusedtoinjectotherdependencies.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockCursor:ThisisamockCursorclassthatisolatesthetestcodefromrealCursorimplementation.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockDialogInterface:ThisisamockimplementationoftheDialogInterfaceclass.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockPackageManager:ThisisamockimplementationofthePackageManagerclass.Allmethodsarenon-functionalandthrowUnsupportedOperationException.MockResources:ThisisamockResourcesclass.

Alloftheseclasseshavenon-functionalmethodsthatthrowUnsupportedOperationExceptionwhenused.Ifyouneedtousesomeofthesemethods,orifyoudetectthatyourtestisfailingwiththisException,youshouldextendoneofthesebaseclassesandprovidetherequiredfunctionality.

AnoverviewofMockContextThismockcanbeusedtoinjectotherdependencies,mocks,ormonitorsintotheclassesundertest.Extendthisclasstoprovideyourdesiredbehavior,overridingthecorrespondentmethods.TheAndroidSDKprovidessomeprebuiltmockContextobjects,eachofwhichhasaseparateusecase.

TheIsolatedContextclassInyourtests,youmightfindtheneedtoisolatetheActivityundertestfromotherAndroidcomponentstopreventunwantedinteractions.Thiscanbeacompleteisolation,butsometimes,thisisolationavoidsinteractingwithothercomponents,andforyourActivitytostillruncorrectly,someconnectionwiththesystemisrequired.

Forthosecases,theAndroidSDKprovidesandroid.test.IsolatedContext,amockContextthatnotonlypreventsinteractionwithmostoftheunderlyingsystembutalsosatisfiestheneedsofinteractingwithotherpackagesorcomponentssuchasServicesorContentProviders.

AlternateroutetofileanddatabaseoperationsInsomecases,allweneedistobeabletoprovideanalternateroutetothefileanddatabaseoperations.Forexample,ifwearetestingtheapplicationonarealdevice,weperhapsdon’twanttoaffecttheexistingdatabasebutuseourowntestingdata.

Suchcasescantakeadvantageofanotherclassthatisnotpartoftheandroid.test.mocksubpackagebutispartofandroid.testinstead,thatis,RenamingDelegatingContext.

Thisclassletsusalteroperationsonfilesanddatabasesbyhavingaprefixthatisspecifiedintheconstructor.AllotheroperationsaredelegatedtothedelegatingContextthatyoumustspecifyintheconstructortoo.

SupposeourActivityundertestusesadatabasewewanttocontrol,probablyintroducingspecializedcontentorfixturedatatodriveourtests,andwedon’twanttousetherealfiles.Inthiscase,wecreateaRenamingDelegatingContextclassthatspecifiesaprefix,andourunchangedActivitywillusethisprefixtocreateanyfiles.

Forexample,ifourActivitytriestoaccessafilenamedbirthdays.txt,andweprovideaRenamingDelegatingContextclassthatspecifiestheprefixtest,thenthissameActivitywillaccessthefiletestbirthdays.txtinsteadwhenitisbeingtested.

TheMockContentResolverclassTheMockContentResolverclassimplementsallmethodsinanon-functionalwayandthrowstheexceptionUnsupportedOperationExceptionifyouattempttousethem.Thereasonforthisclassistoisolatetestsfromtherealcontent.

Let’ssayyourapplicationusesaContentProviderclasstofeedyourActivityinformation.YoucancreateunittestsforthisContentProviderusingProviderTestCase2,whichwewillbeanalyzingshortly,butwhenwetrytoproducefunctionalorintegrationtestsfortheActivityagainstContentProvider,it’snotsoevidentastowhattestcasetouse.ThemostobviouschoiceisActivityInstrumentationTestCase2,mainlyifyourfunctionaltestssimulateuserexperiencebecauseyoumightneedthesendKeys()methodorsimilarmethods,whicharereadilyavailableonthesetests.

Thefirstproblemyoumightencounterthenisthatit’sunclearastowheretoinjectaMockContentResolverinyourtesttobeabletousetestdatawithyourContentProvider.There’snowaytoinjectaMockContexteither.

ThisproblemwillbesolvedinChapter3,BakingwithTestingRecipeswherefurtherdetailsareprovided.

TheTestCasebaseclassThisisthebaseclassofallothertestcasesintheJUnitframework.Itimplementsthebasicmethodsthatwewereanalyzinginthepreviousexamples(setUp()).TheTestCaseclassalsoimplementsthejunit.framework.Testinterface,meaningitcanberunasaJUnittest.

YourAndroidtestcasesshouldalwaysextendTestCaseoroneofitsdescendants.

ThedefaultconstructorAlltestcasesrequireadefaultconstructorbecause,sometimes,dependingonthetestrunnerused,thisistheonlyconstructorthatisinvoked,andisalsousedforserialization.

Accordingtothedocumentation,thismethodisnotintendedtobeusedby“meremortals”withoutcallingsetName(Stringname).

Therefore,toappeasetheGods,acommonpatternistouseadefaulttestcasenameinthisconstructorandinvokethegivennameconstructorafterwards:

publicclassMyTestCaseextendsTestCase{

publicMyTestCase(){

this("MyTestCaseDefaultName");

}

publicMyTestCase(Stringname){

super(name);

}

}

TipDownloadingtheexamplecode

Youcandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

ThegivennameconstructorThisconstructortakesanameasanargumenttolabelthetestcase.Itwillappearintestreportsandwouldbeofmuchhelpwhenyoutrytoidentifywherefailedtestshavecomefrom.

ThesetName()methodTherearesomeclassesthatextendTestCasethatdon’tprovideagivennameconstructor.Insuchcases,theonlyalternativeistocallsetName(Stringname).

TheAndroidTestCasebaseclassThisclasscanbeusedasabaseclassforgeneral-purposeAndroidtestcases.

UseitwhenyouneedaccesstoAndroidresources,databases,orfilesinthefilesystem.Contextisstoredasafieldinthisclass,whichisconvenientlynamedmContextandcanbeusedinsidethetestsifneeded,orthegetContext()methodcanbeusedtoo.

TestsbasedonthisclasscanstartmorethanoneActivityusingContext.startActivity().

TherearevarioustestcasesinAndroidSDKthatextendthisbaseclass:

ApplicationTestCase<TextendsApplication>

ProviderTestCase2<TextendsContentProvider>

ServiceTestCase<TextendsService>

WhenusingtheAndroidTestCaseJavaclass,youinheritsomebaseassertionmethodsthatcanbeused;let’slookattheseinmoredetail.

TheassertActivityRequiresPermission()methodThesignatureforthismethodisasfollows:

publicvoidassertActivityRequiresPermission(StringpackageName,String

className,Stringpermission)

DescriptionThisassertionmethodcheckswhetherthelaunchingofaparticularActivityisprotectedbyaspecificpermission.Ittakesthefollowingthreeparameters:

packageName:ThisisastringthatindicatesthepackagenameoftheactivitytolaunchclassName:Thisisastringthatindicatestheclassoftheactivitytolaunchpermission:Thisisastringwiththepermissiontocheck

TheActivityislaunchedandthenSecurityExceptionisexpected,whichmentionsthattherequiredpermissionismissingintheerrormessage.Theactualinstantiationofanactivityisnothandledbythisassertion,andthus,anInstrumentationisnotneeded.

ExampleThistestcheckstherequirementoftheandroid.Manifest.permission.WRITE_EXTERNAL_STORAGEpermission,whichisneededtowritetoexternalstorage,intheMyContactsActivityActivity:

publicvoidtestActivityPermission(){

Stringpkg="com.blundell.tut";

Stringactivity=PKG+".MyContactsActivity";

Stringpermission=android.Manifest.permission.CALL_PHONE;

assertActivityRequiresPermission(pkg,activity,permission);

}

TipAlwaysusetheconstantsthatdescribethepermissionsfromandroid.Manifest.permission,notthestrings,soiftheimplementationchanges,yourcodewillstillbevalid.

TheassertReadingContentUriRequiresPermissionmethodThesignatureforthismethodisasfollows:

publicvoidassertReadingContentUriRequiresPermission(Uriuri,String

permission)

DescriptionThisassertionmethodcheckswhetherreadingfromaspecificURIrequiresthepermissionprovidedasaparameter.

Ittakesthefollowingtwoparameters:

uri:ThisistheUrithatrequiresapermissiontoquerypermission:Thisisastringthatcontainsthepermissiontoquery

IfaSecurityExceptionclassisgenerated,whichcontainsthespecifiedpermission,thisassertionisvalidated.

ExampleThistesttriestoreadcontactsandverifiesthatthecorrectSecurityExceptionisgenerated:

publicvoidtestReadingContacts(){

UriURI=ContactsContract.AUTHORITY_URI;

StringPERMISSION=android.Manifest.permission.READ_CONTACTS;

assertReadingContentUriRequiresPermission(URI,PERMISSION);

}

TheassertWritingContentUriRequiresPermission()methodThesignatureforthismethodisasfollows:

publicvoidassertWritingContentUriRequiresPermission(Uriuri,String

permission)

DescriptionThisassertionmethodcheckswhetherinsertingintoaspecificUrirequiresthepermissionprovidedasaparameter.

Ittakesthefollowingtwoparameters:

uri:ThisistheUrithatrequiresapermissiontoquerypermission:Thisisastringthatcontainsthepermissiontoquery

IfaSecurityExceptionclassisgenerated,whichcontainsthespecifiedpermission,thisassertionisvalidated.

ExampleThistesttriestowritetoContactsandverifiesthatthecorrectSecurityExceptionisgenerated:

publicvoidtestWritingContacts(){

Uriuri=ContactsContract.AUTHORITY_URI;

Stringpermission=android.Manifest.permission.WRITE_CONTACTS;

assertWritingContentUriRequiresPermission(uri,permission);

}

InstrumentationInstrumentationisinstantiatedbythesystembeforeanyoftheapplicationcodeisrun,therebyallowingmonitoringofalltheinteractionsbetweenthesystemandtheapplication.

AswithmanyotherAndroidapplicationcomponents,instrumentationimplementationsaredescribedintheAndroidManifest.xmlunderthetag<instrumentation>.However,withtheadventofGradle,thishasnowbeenautomatedforus,andwecanchangethepropertiesoftheinstrumentationintheapp’sbuild.gradlefile.TheAndroidManifestfileforyourtestswillbeautomaticallygenerated:

defaultConfig{

testApplicationId'com.blundell.tut.tests'

testInstrumentationRunner"android.test.InstrumentationTestRunner"

}

Thevaluesmentionedintheprecedingcodearealsothedefaultsifyoudonotdeclarethem,meaningthatyoudon’thavetohaveanyoftheseparameterstostartwritingtests.

ThetestApplicationIdattributedefinesthenameofthepackageforyourtests.Asadefault,itisyourapplicationunderthetestpackagename+tests.YoucandeclareacustomtestrunnerusingtestInstrumentationRunner.Thisishandyifyouwanttohavetestsruninacustomway,forexample,paralleltestexecution.

Therearealsomanyotherparametersindevelopment,andIwouldadviseyoutokeepyoureyesupontheGoogleGradlepluginwebsite(http://tools.android.com/tech-docs/new-build-system/user-guide).

TheActivityMonitorinnerclassAsmentionedearlier,theInstrumentationclassisusedtomonitortheinteractionbetweenthesystemandtheapplicationortheActivitiesundertest.TheinnerclassInstrumentationActivityMonitorallowsthemonitoringofasingleActivitywithinanapplication.

ExampleLet’spretendthatwehaveaTextViewinourActivitythatholdsaURLandhasitsautolinkpropertyset:

<TextView

android:id="@+id/link

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="@string/home"

android:autoLink="web""/>

Ifwewanttoverifythat,whenclicked,thehyperlinkiscorrectlyfollowedandsomebrowserisinvoked,wecancreateatestlikethis:

publicvoidtestFollowLink(){

IntentFilterintentFilter=newIntentFilter(Intent.ACTION_VIEW);

intentFilter.addDataScheme("http");

intentFilter.addCategory(Intent.CATEGORY_BROWSABLE);

Instrumentationinst=getInstrumentation();

ActivityMonitormonitor=inst.addMonitor(intentFilter,null,

false);

TouchUtils.clickView(this,linkTextView);

monitor.waitForActivityWithTimeout(3000);

intmonitorHits=monitor.getHits();

inst.removeMonitor(monitor);

assertEquals(1,monitorHits);

}

Here,wewilldothefollowing:

1. CreateanIntentFilterforintentsthatwouldopenabrowser.2. AddamonitortoourInstrumentationbasedontheIntentFilterclass.3. Clickonthehyperlink.4. Waitfortheactivity(hopefullythebrowser).5. Verifythatthemonitorhitswereincremented.6. Removethemonitor.

Usingmonitors,wecantesteventhemostcomplexinteractionswiththesystemandotherActivities.Thisisaverypowerfultooltocreateintegrationtests.

TheInstrumentationTestCaseclassTheInstrumentationTestCaseclassisthedirectorindirectbaseclassforvarioustestcasesthathaveaccesstoInstrumentation.Thisisthelistofthemostimportantdirectandindirectsubclasses:

ActivityTestCase

ProviderTestCase2<TextendsContentProvider>

SingleLaunchActivityTestCase<TextendsActivity>

SyncBaseInstrumentation

ActivityInstrumentationTestCase2<TextendsActivity>

ActivityUnitTestCase<TextendsActivity>

TheInstrumentationTestCaseclassisintheandroid.testpackage,andextendsjunit.framework.TestCase,whichextendsjunit.framework.Assert.

ThelaunchActivityandlaunchActivityWithIntentmethodsTheseutilitymethodsareusedtolaunchActivitiesfromatest.IftheIntentisnotspecifiedusingthesecondoption,adefaultIntentisused:

publicfinalTlaunchActivity(Stringpkg,Class<T>activityCls,Bundle

extras)

NoteThetemplateclassparameterTisusedinactivityClsandasthereturntype,limitingitsusetoActivitiesofthattype.

IfyouneedtospecifyacustomIntent,youcanusethefollowingcodethatalsoaddstheintentparameter:

publicfinalTlaunchActivityWithIntent(Stringpkg,Class<T>activityCls,

Intentintent)

ThesendKeysandsendRepeatedKeysmethodsWhiletestingActivities’UI,youwillfacetheneedtosimulateinteractionwithqwerty-basedkeyboardsorDPADbuttonstosendkeystocompletefields,selectshortcuts,ornavigatethroughoutthedifferentcomponents.

ThisiswhatthedifferentsendKeysandsendRepeatedKeysareusedfor.

ThereisoneversionofsendKeysthatacceptsintegerkeysvalues.TheycanbeobtainedfromconstantsdefinedintheKeyEventclass.

Forexample,wecanusethesendKeysmethodinthisway:

publicvoidtestSendKeyInts(){

requestMessageInputFocus();

sendKeys(

KeyEvent.KEYCODE_H,

KeyEvent.KEYCODE_E,

KeyEvent.KEYCODE_E,

KeyEvent.KEYCODE_E,

KeyEvent.KEYCODE_Y,

KeyEvent.KEYCODE_DPAD_DOWN,

KeyEvent.KEYCODE_ENTER);

Stringactual=messageInput.getText().toString();

assertEquals("HEEEY",actual);

}

Here,wearesendingH,E,andYletterkeysandthentheENTERkeyusingtheirintegerrepresentationstotheActivityundertest.

Alternatively,wecancreateastringbyconcatenatingthekeyswedesiretosend,discardingtheKEYCODEprefix,andseparatingthemwithspacesthatareultimatelyignored:

publicvoidtestSendKeyString(){

requestMessageInputFocus();

sendKeys("H3*EYDPAD_DOWNENTER");

Stringactual=messageInput.getText().toString();

assertEquals("HEEEY",actual);

}

Here,wedidexactlythesameasintheprevioustestbutweusedaString"H3*EYDPAD_DOWNENTER".NotethateverykeyintheStringcanbeprefixedbyarepeatingfactorfollowedby*andthekeytoberepeated.Weused3*Einourpreviousexample,whichisthesameasEEE,thatis,threetimestheletterE.

Ifsendingrepeatedkeysiswhatweneedinourtests,thereisalsoanotheralternativethatispreciselyintendedforthesecases:

publicvoidtestSendRepeatedKeys(){

requestMessageInputFocus();

sendRepeatedKeys(

1,KeyEvent.KEYCODE_H,

3,KeyEvent.KEYCODE_E,

1,KeyEvent.KEYCODE_Y,

1,KeyEvent.KEYCODE_DPAD_DOWN,

1,KeyEvent.KEYCODE_ENTER);

Stringactual=messageInput.getText().toString();

assertEquals("HEEEY",actual);

}

Thisisthesametestimplementedinadifferentmanner.Therepetitionnumberprecedeseachkey.

TherunTestOnUiThreadhelpermethodTherunTestOnUiThreadmethodisahelpermethodusedtorunportionsofatestontheUIthread.WeusedthisinsidethemethodrequestMessageInputFocus();sothatwecansetthefocusonourEditTextbeforewaitingfortheapplicationtobeidle,usingInstrumentation.waitForIdleSync().Also,therunTestOnUiThreadmethodthrowsanexception,sowehavetodealwiththiscase:

privatevoidrequestMessageInputFocus(){

try{

runTestOnUiThread(newRunnable(){

@Override

publicvoidrun(){

messageInput.requestFocus();

}

});

}catch(Throwablethrowable){

fail("Couldnotrequestfocus.");

}

instrumentation.waitForIdleSync();

}

Alternatively,aswehavediscussedbefore,torunatestontheUIthread,wecanannotateitwith@UiThreadTest.However,sometimes,weneedtorunonlypartsofthetestontheUIthreadbecauseotherpartsofitarenotsuitabletorunonthatthread,forexample,databasecalls,orweareusingotherhelpermethodsthatprovidetheinfrastructurethemselvestousetheUIthread,forexampletheTouchUtilsmethods.

TheActivityTestCaseclassThisismainlyaclassthatholdscommoncodeforothertestcasesthataccessInstrumentation.

Youcanusethisclassifyouareimplementingaspecificbehaviorfortestcasesandtheexistingalternativesdon’tfityourrequirements.Thismeansyouareunlikelytousethisclassunlessyouwanttoimplementanewbaseclassforotherteststouse.Forexample,considerascenariowhereGooglebringsoutanewcomponentandyouwanttowritetestsaroundit(likeSuperNewContentProvider).

Ifthisisnotthecase,youmightfindthefollowingoptionsmoresuitableforyourrequirements:

ActivityInstrumentationTestCase2<TextendsActivity>

ActivityUnitTestCase<TextendsActivity>

Theabstractclassandroid.test.ActivityTestCaseextendsandroid.test.InstrumentationTestCaseandservesasabaseclassforotherdifferenttestcases,suchasandroid.test.ActivityInstrumentationTestCase,android.test.ActivityInstrumentationTestCase2,andandroid.test.ActivityUnitTestCase.

NoteTheandroid.test.ActivityInstrumentationTestCasetestcaseisadeprecatedclasssinceAndroidAPILevel3(Android1.5)andshouldnotbeusedinnewerprojects.Eventhoughitwasdeprecatedlongago,ithasagreatnameforautoimport,sobecareful!

ThescrubClassmethodThescrubClassmethodisoneoftheprotectedmethodsintheclass:

protectedvoidscrubClass(Class<?>testCaseClass)

ItisinvokedfromthetearDown()methodinseveralofthediscussedtestcaseimplementationsinordertocleanupclassvariablesthatmayhavebeeninstantiatedasnon-staticinnerclassessoastoavoidholdingreferencestothem.

Thisisinordertopreventmemoryleaksforlargetestsuites.

IllegalAccessExceptionisthrownifaproblemisencounteredwhileaccessingtheseclassvariables.

TheActivityInstrumentationTestCase2classTheActivityInstrumentationTestCase2classwouldprobablybetheoneyouusethemosttowritefunctionalAndroidtestcases.ItprovidesfunctionaltestingofasingleActivity.

ThisclasshasaccesstoInstrumentationandwillcreatetheActivityundertestusingthesysteminfrastructure,bycallingInstrumentationTestCase.launchActivity().TheActivitycanthenbemanipulatedandmonitoredaftercreation.

IfyouneedtoprovideacustomIntenttostartyourActivity,beforeinvokinggetActivity(),youmayinjectanIntentwithsetActivityIntent(Intentintent).

Thistestcasewouldbeveryusefultotestinteractionsthroughtheuserinterfaceaseventscanbeinjectedtosimulateuserbehavior.

TheconstructorThereisonlyonepublicnon-deprecatedconstructorforthisclass,whichisasfollows:

ActivityInstrumentationTestCase2(Class<T>activityClass)

ItshouldbeinvokedwithaninstanceoftheActivityclassforthesameActivityusedasaclasstemplateparameter.

ThesetUpmethodThesetUpmethodisthepreciseplacetoinitializethetestcasefieldsandotherfixturecomponentsthatrequireinitialization.

Thisisanexamplethatshowssomeofthepatternsthatyoumightrepeatedlyfindinyourtestcases:

@Override

protectedvoidsetUp()throwsException{

super.setUp();

//thismustbecalledbeforegetActivity()

//disablingtouchmodeallowsforsendingkeyevents

setActivityInitialTouchMode(false);

activity=getActivity();

instrumentation=getInstrumentation();

linkTextView=(TextView)activity.findViewById(R.id.main_text_link);

messageInput=(EditText)

activity.findViewById(R.id.main_input_message);

capitalizeButton=(Button)

activity.findViewById(R.id.main_button_capitalize);

}

Weperformthefollowingactions:

1. Invokethesupermethod.ThisisaJUnitpatternthatshouldbefollowedheretoensurecorrectoperation.

2. Disablethetouchmode.Totakeeffect,thisshouldbedonebeforetheActivityiscreated,byinvokinggetActivity().ItsetstheinitialtouchmodeoftheActivityundertestasdisabled.ThetouchmodeisafundamentalAndroidUIconcept,andisdiscussedinhttp://developer.android.com/guide/topics/ui/ui-events.html#TouchMode.

3. StarttheActivityusinggetActivity().4. Gettheinstrumentation.Wehaveaccesstotheinstrumentationbecause

ActivityInstrumentationTestCase2extendsInstrumentationTestCase.5. FindtheViewsandsetthefields.Intheseoperations,notethattheRclassusedis

fromthetargetpackageandnotfromthetests.

ThetearDownmethodUsually,thismethodcleansupwhatwasinitializedinsetUp.Forinstance,ifyouwerecreatinganintegrationtestthatsetsupamockwebserverbeforeyourtests,youwouldwanttotearitbackdownafterwardstofreeupresources.

Inthisexample,weensurethattheobjectweusedisdisposedof:

@Override

protectedvoidtearDown()throwsException{

super.tearDown();

myObject.dispose();

}

TheProviderTestCase2<T>classThisisatestcasedesignedtotesttheContentProviderclasses.

TheProviderTestCase2classalsoextendsAndroidTestCase.TheclasstemplateparameterTrepresentsContentProviderundertest.ImplementationofthistestusesIsolatedContextandMockContentResolver,whicharemockobjectsthatwedescribedbeforeinthischapter.

TheconstructorThereisonlyonepublicnon-deprecatedconstructorforthisclass.Thisisasfollows:

ProviderTestCase2(Class<T>providerClass,StringproviderAuthority)

ThisshouldbeinvokedwithaninstanceoftheContentProviderclassforthesameContentProviderclassusedasaclasstemplateparameter.

Thesecondparameteristheauthorityfortheprovider,whichisusuallydefinedastheAUTHORITYconstantintheContentProviderclass.

AnexampleThisisatypicalexampleofaContentProvidertest:

publicvoidtestQuery(){

Stringsegment="dummySegment";

Uriuri=Uri.withAppendedPath(MyProvider.CONTENT_URI,segment);

Cursorc=provider.query(uri,null,null,null,null);

try{

intactual=c.getCount();

assertEquals(2,actual);

}finally{

c.close();

}

}

Inthistest,weareexpectingthequerytoreturnaCursorthatcontainstworows(thisisjustanexamplethatusesthenumberofrowsthatappliesforyourparticularcase)andassertsthiscondition.

Usually,inthesetUpmethod,weobtainareferencetothemProviderproviderinthisexample,usinggetProvider().

WhatisinterestingtonoteisthatbecausethesetestsareusingMockContentResolverandIsolatedContext,thecontentoftherealdatabaseisnotaffected,andwecanalsorundestructivetestslikethisone:

publicvoidtestDeleteByIdDeletesCorrectNumberOfRows(){

Stringsegment="dummySegment";

Uriuri=Uri.withAppendedPath(MyProvider.CONTENT_URI,segment);

intactual=provider.delete(uri,"_id=?",newString[]{"1"});

assertEquals(1,actual);

}

Thistestdeletessomecontentfromthedatabase,butthedatabaseisrestoredtoitsinitialcontentafterwardsnottoaffectothertests.

TheServiceTestCase<T>Thisisatestcasespeciallycreatedtotestservices.Themethodstoexercisetheservicelifecycle,suchassetupService,startService,bindService,andshutDownService,arealsoincludedinthisclass.

TheconstructorThereisonlyonepublicnon-deprecatedconstructorforthisclass.Thisisasfollows:

ServiceTestCase(Class<T>serviceClass)

ItshouldbeinvokedwithaninstanceoftheServiceclassforthesameServiceusedasaclasstemplateparameter.

TheTestSuiteBuilder.FailedToCreateTestsclassTheTestSuiteBuilder.FailedToCreateTestsclassisaspecialTestCaseclassusedtoindicateafailureduringthebuild()step.Thatis,duringthetestsuitecreation,ifanerrorisdetected,youwillreceiveanexceptionlikethisone,whichindicatesthefailuretoconstructthetestsuite:

INFO/TestRunner(1):java.lang.RuntimeException:Exceptionduringsuite

construction

INFO/TestRunner(1):at

android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests.testSuiteCon

structionFailed(TestSuiteBuilder.java:239)

INFO/TestRunner(1):atjava.lang.reflect.Method.invokeNative(Native

Method)

[...]

INFO/TestRunner(1):at

android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.ja

va:520)

INFO/TestRunner(1):at

android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:

1447)

UsinglibrariesintestprojectsYourAndroidprojectmightrequireanexternalJavalibraryoranAndroidlibrary.Now,wewillexplainhowtoincorporatetheseinyourprojectthatisreadytobetested.NotethatthefollowingexplainstheusageofalocalmodulethatisanAndroidlibrary,butthesamerulescanbeappliedtoanexternalJAR(Javalibrary)fileoranexternalAAR(Androidlibrary)file.

Let’spretendthatinoneActivity,wearecreatingobjectsfromaclassthatispartofalibrary.Forthesakeofourexample,let’ssaythelibraryiscalleddummyLibrary,andthementionedclassisDummy.

SoourActivitywouldlooklikethis:

importcom.blundell.dummylibrary.Dummy;

publicclassMyFirstProjectActivityextendsActivity{

privateDummydummy;

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

finalEditTextmessageInput=(EditText)

findViewById(R.id.main_input_message);

ButtoncapitalizeButton=(Button)

findViewById(R.id.main_button_capitalize);

capitalizeButton.setOnClickListener(newOnClickListener(){

@Override

publicvoidonClick(Viewv){

Stringinput=messageInput.getText().toString();

messageInput.setText(input.toUpperCase());

}

});

dummy=newDummy();

}

publicDummygetDummy(){

returndummy;

}

publicstaticvoidmethodThatShouldThrowException()throwsException{

thrownewException("Thisisanexception");

}

}

ThislibraryisanAndroidAARmodule,andsoitshouldbeaddedtoyourbuild.gradledependenciesinthenormalway:

dependencies{

compileproject(':dummylibrary')

}

Ifthiswasanexternallibrary,youwouldreplaceproject(':dummylibrary')with'com.external.lib:name:version'.

Now,let’screateasimpletest.Fromourpreviousexperience,weknowthatifweneedtotestanActivity,weshoulduseActivityInstrumentationTestCase2,andthisispreciselywhatwewilldo.Oursimpletestwillbeasfollows:

publicvoidtestDummy(){

assertNotNull(activity.getDummy());

}

Thetestintheprecedingcoderunsandpassesinthefirstinstance!Notethatinthenot-so-distantpast(pre-Gradle),thetestwouldnothaveevencompiled.Wewouldhavehadtojumpthroughhoops,addingthetestlibrarytoourAndroidtestsproject,ormakingtheJAR/AARfileexportablefromourmainproject.It’sanicetimetostopandreflectonthepowerofGradleandAndroidStudiothatgiveusalotofmanualsetupforfree.

SummaryWeinvestigatedthemostrelevantbuildingblocksandreusablepatternstocreateourtests.Alongthisjourney,we:

UnderstoodthecommonassertionsfoundinJUnittestsExplainedthespecializedassertionsfoundintheAndroidSDKExploredAndroidmockobjectsandtheiruseinAndroidtestsExemplifiedtheuseofthedifferenttestcasesavailableintheAndroidSDK

Nowthatwehaveallthebuildingblocks,itistimetostartcreatingmoreandmoreteststoacquiretheexperienceneededtomasterthetechnique.

ThenextchapterwillprovideyouwithexamplesofwhenandwheretousedifferenttestcasesonAndroid.Thiswillgiveusagreatbreadthofexpertiseinknowingwhattestingmethodologytoapplywhenwehaveaspecificscenariototest.

Chapter3.BakingwithTestingRecipesThischapterprovidespracticalexamplesofmultiplecommonsituationsthatyouwillencounter,byapplyingthedisciplinesandtechniquesdescribedinthepreviouschapters.Theexamplesarepresentedinaneasy-to-followmanner,soyoucanadaptandusethemforyourownprojects.

Thefollowingarethetopicsthatwillbecoveredinthischapter:

AndroidunittestsTestingactivitiesandapplicationsTestingdatabasesandcontentprovidersTestinglocalandremoteservicesTestinguserinterfacesTestingexceptionsTestingparsersTestingformemoryleaksTestingwithEspresso

Afterthischapter,youwillhaveareferencetoapplydifferenttestingrecipestoyourprojectsfordifferentsituations.

AndroidunittestsTherearesomecaseswhereyoureallyneedtotestpartsoftheapplicationinisolationwithlittleconnectiontotheunderlyingsystem.InAndroid,thesystemistheActivityframework.Insuchcases,wehavetoselectabaseclassthatishighenoughinthetesthierarchytoremovesomeofthedependenciesbutnothighenoughforustoberesponsibleforsomeofthebasicinfrastructureofinstantiatingContext,forexample.

Insuchcases,thecandidatebaseclassisAndroidTestCasebecausethisallowstheuseofContextandResourceswithoutthinkingaboutActivities:

publicclassAccessPrivateDataTestextendsAndroidTestCase{

publicvoidtestAccessAnotherAppsPrivateDataIsNotPossible(){

StringfilesDirectory=getContext().getFilesDir().getPath();

StringprivateFilePath=filesDirectory+

"/data/com.android.cts.appwithdata/private_file.txt";

try{

newFileInputStream(privateFilePath);

fail("Wasabletoaccessanotherapp'sprivatedata");

}catch(FileNotFoundExceptione){

//expected

}

}

}

TipThisexampleisbasedontheAndroidCompatibilityTestSuite(CTS)athttp://source.android.com/compatibility/cts-intro.html.TheCTSisasuiteoftestsaimedatmakingtheAndroidhardwareandsoftwareenvironmentconsistentforapplicationdevelopers,irrespectiveoftheoriginalequipmentmanufacturer.

TheAccessPrivateDataTestclassextendsAndroidTestCasebecauseit’saunittestthatdoesn’trequirethesysteminfrastructure.Inthisparticularcase,wecouldnothaveusedTestCasedirectlybecauseweareusinggetContext()lateron.

Thistestmethod,testAccessAnotherAppsPrivateDataIsNotPossible(),teststheaccesstoanotherpackage’sprivatedataandfailsifaccessispossible.Toachievethis,theexpectedexceptionsarecaught,andifthisdoesn’thappen,fail()isinvokedwithacustommessage.Thetestseemsprettystraightforward,butyoucanseehowpowerfulthisistostopinadvertentsecuritymistakesfromcreepingin.

TestingactivitiesandapplicationsHere,wecoversomecommoncasesthatyouwillfindinyourday-to-daytesting,includingdealingwithIntents,Preferences,andContext.Youcanadaptthesepatternstosuityourspecificneeds.

MockingapplicationsandpreferencesInAndroidparlance,anapplicationreferstoabaseclassusedwhenitisneededtomaintainaglobalapplicationstate.Thefullpackageisandroid.app.Application.Thiscanbeutilizedwhendealingwithsharedpreferences.

Weexpectthattheteststhatalterthesepreferences’valueswillnotaffectthebehavioroftherealapplication.Withoutthecorrecttestingframework,thetestscoulddeleteuseraccountinformationforanapplicationthatstoresthesevaluesassharedpreferences.Thisdoesn’tsoundlikeagoodidea.SowhatwereallyneedistheabilitytomockaContextthatalsomockstheaccesstoSharedPreferences.

OurfirstattemptcouldbetouseRenamingDelegatingContext,butunfortunately,itdoesnotmockSharedPreferences,althoughitisclosebecauseitmocksthedatabaseandfilesystemaccess.Sofirst,weneedtomockaccesstooursharedpreferences.

TipWheneveryoucomeacrossanewclass(likeRenamingDelegatingContext),it’sagoodideatoreadtherelevantJavadoctogetanoverviewofhowtheframeworkdevelopersexpectittobeused.Formoreinformation,refertohttp://developer.android.com/reference/android/test/RenamingDelegatingContext.html.

TheRenamingMockContextclassLet’screatethespecializedContext.TheRenamingDelegatingContextclassisaverygoodpointtostartfrombecauseaswementionedbefore,databaseandfilesystemaccesswillbemocked.TheproblemishowtomocktheSharedPreferencesaccess.

RememberthatRenamingDelegatingContext,asitsnamesuggests,delegateseverythingtoaContext.SotherootofourproblemliesinthisContext.WhenyouaccessSharedPreferencesfromaContext,youusegetSharedPreferences(Stringname,intmode).Tochangethewaythismethodworks,wecanoverrideitinsideRenamingMockContext.Nowthatwehavecontrol,wecanprependthenameparameterwithourtestprefix,whichmeansthatwhenourtestsrun,theywillwritetoapreferencesfilethatisdifferentthanthatofourmainapplication:

publicclassRenamingMockContextextendsRenamingDelegatingContext{

privatestaticfinalStringPREFIX="test.";

publicRenamingMockContext(Contextcontext){

super(context,PREFIX);

}

@Override

publicSharedPreferencesgetSharedPreferences(Stringname,intmode){

returnsuper.getSharedPreferences(PREFIX+name,mode);

}

}

Now,wehavefullcontroloverhowpreferences,databases,andfilesarestored.

MockingcontextsWehavetheRenamingMockContextclass.Now,weneedatestthatusesit.Aswewillbetestinganapplication,thebaseclassforthetestwouldbeApplicationTestCase.Thistestcaseprovidesaframeworkinwhichyoucantestapplicationclassesinacontrolledenvironment.Itprovidesbasicsupportforthelifecycleofanapplication,andhookstoinjectvariousdependenciesandcontroltheenvironmentinwhichyourapplicationistested.UsingthesetContext()method,wecaninjecttheRenamingMockContextmethodbeforetheapplicationiscreated.

We’regoingtotestanapplicationcalledTemperatureConverter.ThisisasimpleapplicationthatconvertsCelsiustoFahrenheitandviceversa.WewilldiscussmoreaboutthedevelopmentofthisappinChapter6,PracticingTest-drivenDevelopment.Fornow,thedetailsaren’tnecessaryasweareconcentratingontestingscenarios.TheTemperatureConverterapplicationwillstorethedecimalplacesofanyconversionasasharedpreference.Consequently,wewillcreateatesttosetthedecimalplacesandthenretrieveittoverifyitsvalue:

publicclassTemperatureConverterApplicationTestsextends

ApplicationTestCase<TemperatureConverterApplication>{

publicTemperatureConverterApplicationTests(){

this("TemperatureConverterApplicationTests");

}

publicTemperatureConverterApplicationTests(Stringname){

super(TemperatureConverterApplication.class);

setName(name);

}

publicvoidtestSetAndRetreiveDecimalPlaces(){

RenamingMockContextmockContext=new

RenamingMockContext(getContext());

setContext(mockContext);

createApplication();

TemperatureConverterApplicationapplication=getApplication();

application.setDecimalPlaces(3);

assertEquals(3,application.getDecimalPlaces());

}

}

WeextendApplicationTestCaseusingtheTemperatureConverterApplicationtemplateparameter.

Then,weusethegivennameconstructorpatternthatwediscussedinChapter2,UnderstandingTestingwiththeAndroidSDK.

Here,wehavenotusedasetUp()methodsincethereisonlyonetestintheclass–youain’tgonnaneeditastheysay.Oneday,ifyoucometoaddanothertesttothisclass,thisiswhenyoucanoverridesetUp()andmovethebehavior.ThisfollowstheDRYprinciple,

meaningDon’tRepeatYourself,andleadstomoremaintainablesoftware.Soatthetopofthetestmethod,wecreatethemockcontextandsetthecontextforthistestusingthesetContext()method;wecreatetheapplicationusingcreateApplication().YouneedtoensureyoucallsetContextbeforecreateApplicationasthisishowyougetthecorrectinstantiationorder.Now,thecodethatactuallytestsfortherequiredbehaviorsettingthedecimalplaces,retrievingit,andverifyingitsvalue.Thisisit,usingRenamingMockContexttogiveuscontroloverSharedPreferences.WhenevertheSharedPreferenceisrequested,themethodwillinvokethedelegatingcontext,addingtheprefixforthename.TheoriginalSharedPreferencesclassusedbytheapplicationareunchanged:

publicclassTemperatureConverterApplicationextendsApplication{

privatestaticfinalintDECIMAL_PLACES_DEFAULT=2;

privatestaticfinalStringKEY_DECIMAL_PLACES=".KEY_DECIMAL_PLACES";

privateSharedPreferencessharedPreferences;

@Override

publicvoidonCreate(){

super.onCreate();

sharedPreferences=

PreferenceManager.getDefaultSharedPreferences(this);

}

publicvoidsetDecimalPlaces(intplaces){

Editoreditor=sharedPreferences.edit();

editor.putInt(KEY_DECIMAL_PLACES,places);

editor.apply();

}

publicintgetDecimalPlaces(){

returnsharedPreferences.getInt(KEY_DECIMAL_PLACES,

DECIMAL_PLACES_DEFAULT);

}

}

WecanverifythatourtestsdonotaffecttheapplicationbyfurnishingtheTemperatureConverterApplicationclasswithsomevalueinthesharedpreferences,runningtheapplication,thenrunningthetestsandeventuallyverifyingthatthisvaluewasnotaffectedbyexecutingthetests.

TestingactivitiesThenextexampleshowshowanactivitycanbetestedincompleteisolationusingtheActivityUnitTestCase<Activity>baseclass.AsecondchoicewouldbeActivityInstrumentationTestCase2<Activity>.However,theformerallowsyoutocreateanActivitybutnotattachittothesystem,meaningyoucannotlaunchotherActivities(youareanActivitysingleunit).ThischoiceoftheparentclassnotonlyrequiresmorecareandattentioninyoursetupbutalsoprovidesagreaterflexibilityandcontrolovertheActivityundertest.ThiskindoftestisintendedtotestgeneralActivitybehaviorandnotanActivityinstance’sinteractionwithothersystemcomponentsoranyUI-relatedtests.

Firstthingsfirst,hereistheclassundertest.ItisasimpleActivitywithonebutton.Whenthisbuttonispressed,itfiresanIntenttostarttheDialerandfinishesitself:

publicclassForwardingActivityextendsActivity{

privatestaticfinalintGHOSTBUSTERS=999121212;

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_forwarding);

Viewbutton=findViewById(R.id.forwarding_go_button);

button.setOnClickListener(newView.OnClickListener(){

@Override

publicvoidonClick(Viewv){

Intentintent=newIntent("tel:"+GHOSTBUSTERS);

startActivity(intent);

finish();

}

});

}

}

Forourtestcase,weextendActivityUnitTestCase<ForwardingActivity>,aswementionedearlier,asaunittestforanActivityclass.Thisactivityundertestwillbedisconnectedfromthesystem,soitisonlyintendedtotestinternalaspectsofitandnotitsinteractionwithothercomponents.InthesetUp()method,wecreatetheIntentthatwillstartourActivityundertest,thatis,ForwardingActivity.NotetheuseofgetInstrumentation().ThegetContextclass,asatthispointinthesetUp()methodoftheActivityContext,isstillnull:

publicclassForwardingActivityTestextends

ActivityUnitTestCase<ForwardingActivity>{

privateIntentstartIntent;

publicForwardingActivityTest(){

super(ForwardingActivity.class);

}

@Override

protectedvoidsetUp()throwsException{

super.setUp();

Contextcontext=getInstrumentation().getContext();

startIntent=newIntent(context,ForwardingActivity.class);

}

Nowthatthesetupisdone,wecanmoveontoourtests:

publicvoidtestLaunchingSubActivityFiresIntentAndFinishesSelf(){

Activityactivity=startActivity(startIntent,null,null);

Viewbutton=activity.findViewById(R.id.forwarding_go_button);

button.performClick();

assertNotNull(getStartedActivityIntent());

assertTrue(isFinishCalled());

}

ThefirsttestperformsaclickontheGobuttonoftheForwardingActivity.TheonClickListenerclassofthatbuttoninvokesstartActivity()withanIntentthatdefinesanewActivitythatwillbestarted.Afterperformingthisaction,weverifythattheIntentusedtolaunchthenewActivityisnotnull.ThegetStartedActivityIntent()methodreturnstheIntentthatwasusediftheActivityundertestsinvokedstartActivity(Intent)orstartActivityForResult(Intent,int).Next,weassertthatfinish()wascalled,andwedothatbyverifyingthereturnvalueofFinishCalled(),whichreturnstrueifoneofthefinishmethods(finish(),finishFromChild(Activity),orfinishActivity(int))wascalledintheActivityundertest:

publicvoidtestExampleOfLifeCycleCreation(){

Activityactivity=startActivity(startIntent,null,null);

//Atthispoint,onCreate()hasbeencalled,butnothingelse

//sowecompletethestartupoftheactivity

getInstrumentation().callActivityOnStart(activity);

getInstrumentation().callActivityOnResume(activity);

//Atthispointyoucouldtestforvariousconfigurationaspects

//oryoucoulduseaMockContext

//toconfirmthatyouractivityhasmade

//certaincallstothesystemandsetitselfupproperly.

getInstrumentation().callActivityOnPause(activity);

//Atthispointyoucouldconfirmthat

//theactivityhaspausedproperly,

//asifitisnolongerthetopmostactivityonscreen.

getInstrumentation().callActivityOnStop(activity);

//Atthispoint,youcouldconfirmthat

//theactivityhasshutitselfdownappropriately,

//oryoucoulduseaMockContexttoconfirmthat

//youractivityhasreleasedany

//systemresourcesitshouldnolongerbeholding.

//ActivityUnitTestCase.tearDown()isalwaysautomaticallycalled

//andwilltakecareofcallingonDestroy().

}

Thesecondtestisperhapsthemoreinterestingtestmethodinthistestcase.ThistestcasedemonstrateshowtoexercisetheActivitylifecycle.AfterstartingtheActivity,onCreate()iscalledautomatically,andwecanthenexerciseotherlifecyclemethodsbyinvokingthemmanually.Tobeabletoinvokethesemethods,weuseIntrumentationofthistest.Also,wedon’tmanuallyinvokeonDestroy()asitwillbeinvokedforusintearDown().

Let’swalkthroughthecode.ThismethodstartstheActivityinthesamewayasthepreviouslyanalyzedtest.Aftertheactivityisstarted,itsonCreate()methodiscalledautomaticallybythesystem.WethenuseInstrumentationtoinvokeotherlifecyclemethodstocompletetheActivityunderteststartup.ThesecorrespondtoonStart()andonResume()intheActivitylifecycle.

TheActivityisnowcompletelystarted,andit’stimetotestfortheaspectsweareinterestedin.Oncethisisachieved,wecanfollowotherstepsinthelifecycle.Notethatthissampletestdoesnotassertanythingherebutsimplypointsouthowtostepthroughthelifecycle.Tofinishthelifecycle,wecallthroughtoonPause()andonStop().Asweknow,onDestroy()isavoidedasitwillautomaticallybecalledbytearDown().

Thistestrepresentsatestskeleton.YoucanreuseittotestyourActivitiesinisolationandtotestlifecycle-relatedcases.TheinjectionofmockobjectscanalsofacilitatetestingofotheraspectsoftheActivity,suchasaccessingsystemresources.

Testingfiles,databases,andcontentprovidersSometestcaseshavetheneedtoexercisedatabasesorContentProvideroperations,andsooncomestheneedtomocktheseoperations.Forexample,ifwearetestinganapplicationonarealdevice,wedon’twanttointerferewiththenormaloperationofapplicationsonthesaiddevice,especiallyifweweretochangevaluesthatmaybesharedbymorethanoneapplication.

Suchcasescantakeadvantageofanothermockclassthatisnotapartoftheandroid.test.mockpackagebutofandroid.testinstead,namelyRenamingDelegatingContext.

Remember,thisclassletsusmockfileanddatabaseoperations.Aprefixsuppliedintheconstructorisusedtomodifythetargetoftheseoperations.AllotheroperationsaredelegatedtothedelegatingContextthatyouspecify.

SupposeourActivityundertestusessomefilesordatabasesthatwewanttocontrolinsomeway,probablytointroducespecializedcontenttodriveourtests,andwedon’twantto,orwecannotusetherealfilesordatabase.Insuchcases,wecreateRenamingDelegatingContext,whichspecifiesaprefix.Weprovidemockfilesusingthisprefixandintroduceanycontentweneedtodriveourtests,andtheActivityundertestcouldusethemwithnoalteration.

TheadvantageofkeepingourActivityunchanged,thatis,notmodifyingittoreadfromadifferentsource,isthatthisassuresthatallthetestsarevalid.Ifweintroduceachangeonlyintendedforourtests,wewillnotbeabletoassurethat,underrealconditions,theActivitybehavesthesame.

Todemonstratethiscase,wewillcreateanextremelysimpleActivity.

TheMockContextExampleActivityactivitydisplaysthecontentofafileinsideTextView.WhatweintendtodemonstrateishowitdisplaysdifferentcontentduringanormaloperationofActivity,ascomparedtowhenitisundertest:

publicclassMockContextExampleActivityextendsActivity{

privatestaticfinalStringFILE_NAME="my_file.txt";

privateTextViewtextView;

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_mock_context_example);

textView=(TextView)findViewById(R.id.mock_text_view);

try{

FileInputStreamfis=openFileInput(FILE_NAME);

textView.setText(convertStreamToString(fis));

}catch(FileNotFoundExceptione){

textView.setText("Filenotfound");

}

}

privateStringconvertStreamToString(java.io.InputStreamis){

Scanners=newScanner(is,"UTF-8").useDelimiter("\\A");

returns.hasNext()?s.next():"";

}

publicStringgetText(){

returntextView.getText().toString();

}

}

ThisisoursimpleActivity.Itreadsthecontentofthemy_file.txtfileanddisplaysitonTextView.Italsodisplaysanyerrorthatmightoccur.Obviously,inarealscenario,youwouldhavebettererrorhandlingthanthis.

Weneedsomecontentforthisfile.Probablytheeasiestwaytocreatethefilesisasshowninthefollowingcode:

$adbshell

$echo"Thisisrealdata">data/data/com.blundell.tut/files/my_file.txt

$echo"Thisis*MOCK*data">

/data/data/com.blundell.tut/files/test.my_file.txt

Wecreatedtwodifferentfiles,onenamedmy_file.txtandtheothertest.my_file.txt,withdifferentcontent.Thelatterindicatesthatitisamockcontent.Ifyourantheprecedingactivitynow,youwouldseeThisisrealdataasitisreadingfromtheexpectedfilemy_file.txt.

Thefollowingcodedemonstratestheuseofthismockdatainouractivitytests:

publicclassMockContextExampleTest

extendsActivityUnitTestCase<MockContextExampleActivity>{

privatestaticfinalStringPREFIX="test.";

privateRenamingDelegatingContextmockContext;

publicMockContextExampleTest(){

super(MockContextExampleActivity.class);

}

@Override

protectedvoidsetUp()throwsException{

super.setUp();

mockContext=new

RenamingDelegatingContext(getInstrumentation().getTargetContext(),PREFIX);

mockContext.makeExistingFilesAndDbsAccessible();

}

publicvoidtestSampleTextDisplayed(){

setActivityContext(mockContext);

startActivity(newIntent(),null,null);

assertEquals("Thisis*MOCK*data\n",getActivity().getText());

}

}

TheMockContextExampleTestclassextendsActivityUnitTestCasebecausewearelookingforisolatedtestingofMockContextExampleActivityandbecausewearegoingtoinjectamockedcontext;inthiscase,theinjectedcontextisRenamingDelegatingContextasadependency.

Ourfixtureconsistsofthemockcontext,mockContextandRenamingDelegatingContext,usingthetargetcontextobtainedbygetInstrumentation().getTargetContext().NotethatthecontextwheretheinstrumentationisrunisdifferentthanthecontextoftheActivityundertest.

Hereafundamentalstepfollows—sincewewanttomaketheexistingfilesanddatabasesaccessibletothistest,wehavetoinvokemakeExistingFilesAndDbsAccessible().

Then,ourtestnamedtestSampleTextDisplayed()injectsthemockcontextusingsetActivityContext().

TipYoumustinvokesetActivityContext()toinjectamockcontextbeforeyoustarttheActivityundertestbyinvokingstartActivity().

Then,theActivityisstartedbystartActivity()usingablankIntentjustcreated.

WeobtainthetextvalueheldbytheTextViewbyusingagetterthatweaddedtotheActivity.Iwouldneverrecommendchangingproductioncode(thatis,exposinggetters)justforyourtestsinarealproject,asthiscanleadtobugs,incorrectusagepatternsbyotherdevelopers,andsecurityissues.However,here,wearedemonstratingtheuseofRenamingDelegatingContextratherthantestcorrectness.

Finally,thetextvalueobtainedischeckedagainsttheThisisMOCK*datastring.Itisimportantheretonoticethatthevalueusedforthistestisthetestfilecontentandnottherealfilecontent.

TheBrowserProvidertestsThesetestsarebasedontheBrowsermoduleoftheAndroidOpenSourceProject(AOSP).TheAOSPhaslotsofgreattestexamples,andusingthemasanexampleherestopsyoufromwritingalotofboilerplatecodetosetupthescenarioforthetest.TheyareintendedtotestsomeaspectsoftheBrowserbookmarks,contentprovider,whichispartofthestandardBrowserincludedwiththeAndroidplatform(nottheChromeappbutthedefaultBrowserapp):

publicclassBrowserProviderTestsextendsAndroidTestCase{

privateList<Uri>deleteUris;

@Override

protectedvoidsetUp()throwsException{

super.setUp();

deleteUris=newArrayList<Uri>();

}

@Override

protectedvoidtearDown()throwsException{

for(Uriuri:deleteUris){

deleteUri(uri);

}

super.tearDown();

}

}

NoteAOSPtestsarenotavailablefromtheexampleprojectwiththischapterbutareavailableonlineathttps://github.com/android/platform_packages_apps_browser/blob/master/tests/src/com/android/browser/BrowserProviderTests.java

ThissnippetincludesthetestcasedefinitionthatextendsAndroidTestCase.TheBrowserProviderTestsclassextendsAndroidTestCasebecauseaContextisneededtoaccesstheprovidercontent.

ThefixturecreatedinthesetUp()methodcreatesalistofUristhatareusedtokeeptrackoftheinsertedUristobedeletedattheendofeachtestinthetearDown()method.Thedeveloperscouldhavesavedthishassleusingamockcontentprovider,maintainingtheisolationbetweenourtestsandthesystem.Anyway,tearDown()iteratesoverthislistanddeletesthestoredUris.ThereisnoneedtooverridetheconstructorhereasAndroidTestCaseisnotaparameterizedclass,andwedon’tneedtodoanythingspecialinit.

Nowcomesthetest:

publicvoidtestHasDefaultBookmarks(){

Cursorc=getBookmarksSuggest("");

try{

assertTrue("Nodefaultbookmarks",c.getCount()>0);

}finally{

c.close();

}

}

ThetestHasDefaultBookmarks()methodisatesttoensurethatthereareanumberofdefaultbookmarksalwayspresentinthedatabase.Onstartup,acursoriteratesoverthedefaultbookmarksobtainedbyinvokinggetBookmarksSuggest(""),whichreturnsanunfilteredcursorofbookmarks;thisiswhythecontentproviderqueryparameteris"":

publicvoidtestPartialFirstTitleWord(){

assertInsertQuery(

"http://www.example.com/rasdfe","nfgjrasdfywe","nfgj");

}

ThetestPartialFirstTitleWord()methodandthreeotherslikeitnotshownheretestFullFirstTitleWord(),testFullFirstTitleWordPartialSecond(),andtestFullTitle()testfortheinsertionofbookmarks.Toachievethis,theyinvokeassertInsertQuery()usingthebookmarkedURL,itstitle,andthequery.ThemethodassertInsertQuery()addsthebookmarkstothebookmarkprovider,insertingtheURLissuedasaparameterwiththespecifiedtitle.TheUrireturnedisverifiedtobenotnullandnotexactlythesameasthedefaultone.Finally,theUriisinsertedinthelistofUriinstancestobedeletedintearDown().Thecodeforthiscanbeseenintheutilitymethodsshownasfollows:

publicvoidtestFullTitleJapanese(){

Stringtitle="\u30ae\u30e3\u30e9\u30ea\u30fc\u30fcGoogle\u691c\u7d22";

assertInsertQuery("http://www.example.com/sdaga",title,title);

}

NoteUnicodeisacomputingindustrystandarddesignedtoconsistentlyanduniquelyencodecharactersusedinwrittenlanguagesthroughouttheworld.TheUnicodestandarduseshexadecimalstoexpressacharacter.Forexample,thevalue\u30aerepresentstheKatakanaletterGI().

WehaveseveralteststhatareintendedtoverifytheutilizationofthisbookmarkproviderforlocalesandlanguagesotherthanjustEnglish.TheseparticularcasescovertheJapaneselanguageutilizationinbookmarktitles.TheteststestFullTitleJapanese(),andtwoothersthatarenotshownhere,thatis,testPartialTitleJapanese()andtestSoundmarkTitleJapanese()aretheJapaneseversionsofthetestsintroducedbeforeusingUnicodecharacters.Itisrecommendedtotesttheapplication’scomponentsunderdifferentconditions,likeinthiscase,whereotherlanguageswithdifferentcharactersetsareused.

Severalutilitymethodsfollow.Thesearetheutilitiesusedinthetests.WebrieflylookedatassertInsertQuery()before,sonow,let’slookattheothermethodsaswell:

privatevoidassertInsertQuery(Stringurl,Stringtitle,Stringquery){

addBookmark(url,title);

assertQueryReturns(url,title,query);

}

privatevoidaddBookmark(Stringurl,Stringtitle){

Uriuri=insertBookmark(url,title);

assertNotNull(uri);

assertFalse(BOOKMARKS_URI.equals(uri));

deleteUris.add(uri);

}

privateUriinsertBookmark(Stringurl,Stringtitle){

ContentValuesvalues=newContentValues();

values.put("title",title);

values.put("url",url);

values.put("visits",0);

values.put("date",0);

values.put("created",0);

values.put("bookmark",1);

returngetContext().getContentResolver().insert(BOOKMARKS_URI,

values);

}

privatevoidassertQueryReturns(Stringurl,Stringtitle,Stringquery){

Cursorc=getBookmarksSuggest(query);

try{

assertTrue(title+"notmatchedby"+query,c.getCount()>0);

assertTrue("Morethanoneresultfor"+query,c.getCount()==1);

while(c.moveToNext()){

Stringtext1=getCol(c,SearchManager.SUGGEST_COLUMN_TEXT_1);

assertNotNull(text1);

assertEquals("Badtitle",title,text1);

Stringtext2=getCol(c,SearchManager.SUGGEST_COLUMN_TEXT_2);

assertNotNull(text2);

Stringdata=getCol(c,SearchManager.SUGGEST_COLUMN_INTENT_DATA);

assertNotNull(data);

assertEquals("BadURL",url,data);

}

}finally{

c.close();

}

}

privateStringgetCol(Cursorc,Stringname){

intcol=c.getColumnIndex(name);

Stringmsg="Column"+name+"notfound,"

+"columns:"+Arrays.toString(c.getColumnNames());

assertTrue(msg,col>=0);

returnc.getString(col);

}

privateCursorgetBookmarksSuggest(Stringquery){

UrisuggestUri=

Uri.parse("content://browser/bookmarks/search_suggest_query");

String[]selectionArgs={query};

Cursorc=getContext().getContentResolver().query(suggestUri,null,"url

LIKE?",selectionArgs,null);

assertNotNull(c);

returnc;

}

privatevoiddeleteUri(Uriuri){

intcount=getContext().getContentResolver().delete(uri,null,null);

assertEquals("Failedtodelete"+uri,1,count);

}

ThemethodassertInsertQuery()invokesassertQueryReturns(url,title,andquery),afteraddBookmark(),toverifythattheCursorreturnedbygetBookmarksSuggest(query)containstheexpecteddata.Thisexpectationcanbesummarizedas:

Thenumberofrowsreturnedbythequeryisgreaterthan0Thenumberofrowsreturnedbythequeryisequalto1ThetitleinthereturnedrowisnotnullThetitlereturnedbythequeryisexactlythesameasthemethodparameterThesecondlineforthesuggestionisnotnullTheURLreturnedbythequeryisnotnullThisURLmatchesexactlytheURLissuedasthemethodparameter

Thisstrategyprovidesaninterestingpatterntofollowinourtests.Someoftheutilitymethodsthatweneedtocreatetocompleteourtestscanalsocarrytheirownverificationofseveralconditionsandimproveourtestquality.

Creatingassertmethodsinourclassesallowsustointroduceadomain-specifictestinglanguagethatcanbereusedwhentestingotherpartsofthesystem.

TestingexceptionsWehavementionedthisbeforeinChapter1,GettingStartedwithTesting,wherewestatedthatyoushouldtestforexceptionsandwrongvaluesinsteadofjusttestingpositivecases:

@Test(expected=InvalidTemperatureException.class)

publicfinalvoidtestExceptionForLessThanAbsoluteZeroF(){

TemperatureConverter.

fahrenheitToCelsius(TemperatureConverter.ABSOLUTE_ZERO_F-1);

}

@Test(expected=InvalidTemperatureException.class)

publicfinalvoidtestExceptionForLessThanAbsoluteZeroC(){

TemperatureConverter.

celsiusToFahrenheit(TemperatureConverter.ABSOLUTE_ZERO_C-1);

}

Wehavealsopresentedthesetestsbefore,buthere,wearediggingdeeperintoit.ThefirstthingtonoticeisthattheseareJUnit4tests,meaningwecantestforexceptionsusingtheexpectedannotationparameter.Whenyoudownloadthechapter’ssampleproject,youwillbeabletoseethatitissplitintotwomodules,oneofthembeingcore,whichisapureJavamodule,andso,wehavethechancetouseJUnit4.Atthetimeofwritingthis,AndroidhasannouncedJUnit4supportbutnotyetreleasedit,sowearestillonJUnit3forInstrumentedAndroidtests.

Everytimewehaveamethodthatissupposedtogenerateanexception,weshouldtestthisexceptionalcondition.ThebestwayofdoingitisbyusingJUnit4’sexpectedparameter.Thisdeclaresthatthetestshouldthrowtheexception,ifitdoesnotthrowtheexceptionorthrowsadifferentexception,thetestwillfail.ThiscanalsobedoneinJUnit3byinvokingthemethodundertestinsideatry-catchblock,catchingtheexpectedexception,andfailingotherwise:

publicvoidtestExceptionForLessThanAbsoluteZeroC(){

try{

TemperatureConverter.celsiusToFahrenheit(ABSOLUTE_ZERO_C-1);

fail();

}catch(InvalidTemperatureExceptionex){

//donothingweexpectthisexception!

}

}

TestinglocalandremoteservicesWhenyouwanttotestanandroid.app.Service,theideaistoextendtheServiceTestCase<Service>classtotestinacontrolledenvironment:

publicclassDummyServiceTestextendsServiceTestCase<DummyService>{

publicDummyServiceTest(){

super(DummyService.class);

}

publicvoidtestBasicStartup(){

IntentstartIntent=newIntent();

startIntent.setClass(getContext(),DummyService.class);

startService(startIntent);

}

publicvoidtestBindable(){

IntentstartIntent=newIntent();

startIntent.setClass(getContext(),DummyService.class);

bindService(startIntent);

}

}

Theconstructor,asinothersimilarcases,invokestheparentconstructorthatpassestheAndroidserviceclassasaparameter.

ThisisfollowedbytestBasicStartup().WestarttheserviceusinganIntentthatwecreatehere,settingitsclasstotheclassoftheserviceundertest.WealsousetheinstrumentedContextforthisIntent.Thisclassallowsforsomedependencyinjection,aseveryservicedependsontheContextinwhichitruns,andtheapplicationwithwhichitisassociated.Thisframeworkallowsyoutoinjectmodified,mock,orisolatedreplacementsforthesedependencies,andthusperformsatrueunittest.

NoteDependencyInjection(DI)isasoftwaredesignpatternthatdealswithhowcomponentsgetholdoftheirdependencies.Youcandothisyourselfmanuallyoruseoneofthemanydependencyinjectionlibraries.

Sincewesimplyrunourtestsasis,theservicewillbeinjectedwithafullyfunctionalContextandagenericMockApplicationobject.Then,westarttheserviceusingthestartService(startIntent)method,inthesamewayasifitwerestartedbyContext.startService(),providingtheargumentsitsupplied.Ifyouusethismethodtostarttheservice,itwillautomaticallybestoppedbytearDown().

Anothertest,testBindable(),willtestwhethertheservicecanbebound.ThistestusesbindService(startIntent),whichstartstheserviceundertestinthesamewayasifitwerestartedbyContext.bindService(),providingtheargumentsitsupplied.Itreturnsthecommunicationchanneltotheservice.Itmayreturnnullifclientscannotbindtotheservice.Mostprobably,thistestshouldcheckforthenullreturnvalueintheservicewithanassertionlikeassertNotNull(service)toverifythattheservicewasboundcorrectly,

butitdoesn’t,sowecanfocusontheframeworkclassesinuse.Besuretoincludethistestwhenyouwritecodeforsimilarcases.

ThereturnedIBinderisusuallyforacomplexinterfacethathasbeendescribedusingAIDL.Inordertotestwiththisinterface,yourservicemustimplementagetService()method,asshowninDummServiceintheexampleprojectforthischapter;whichhasthisimplementationofthatmethod:

publicclassLocalBinderextendsBinder{

DummyServicegetService(){

returnDummyService.this;

}

}

ExtensiveuseofmockobjectsInthepreviouschapters,wedescribedandusedthemockclassesthatarepresentintheAndroidSDK.Whiletheseclassescancoveragreatnumberofcases,thereareotherAndroidclassesandyourowndomainclassestoconsider.Youmighthavetheneedforothermockobjectstofurnishyourtestcases.

Severallibrariesprovidetheinfrastructuretosatisfyourmockingneeds,butwearenowconcentratingonMockito,whichisperhapsthemostwidelyusedlibraryinAndroid.

NoteThisisnotaMockitotutorial.WewilljustbeanalyzingitsuseinAndroid,soifyouarenotfamiliarwithit,Iwouldrecommendthatyoutakealookatthedocumentationavailableonitswebsiteathttps://code.google.com/p/mockito/.

MockitoisanopensourcesoftwareprojectavailableundertheMITlicense,andprovidestestdoubles(mockobjects).ItisaperfectmatchforTest-drivenDevelopmentduetothewayitverifiesexpectationsandduetoitsdynamicallygeneratedmockobjectsbecausetheysupportrefactoring,andthetestcodewillnotbreakwhenrenamingmethodsorchangingitssignature.

Summarizingitsdocumentation,themostrelevantbenefitsofMockitoareasfollows:

AskquestionsaboutinteractionsafterexecutionItisnotexpect-run-verify–avoidsexpensivesetupOnewaytomockthatisasimpleAPIEasyrefactoringwithtypesusedItmocksconcreteclassesaswellasinterfaces

Todemonstrateitsusageandtoestablishastylethatcanbelaterreproducedforothertests,wearecompletingsomeexampletestcases.

NoteThelatestversionofMockitosupportedbyAndroidasofthiswritingisDexmakerMockito1.1.Youmightwanttotryoutadifferentone,butyouwillmostprobablyencounterproblems.

ThefirstthingweshoulddoisaddMockitoasadependencyforyourAndroidinstrumentationtests.ThisisassimpleasaddingtheandroidTestCompilereferencetoyourdependenciesclosure.Gradlewilldotherest,thatis,downloadtheJARfileandaddittoyourclasspath:

dependencies{

//othercompiledependencies

androidTestCompile('com.google.dexmaker:dexmaker-mockito:1.1')

}

InordertouseMockitoinourtests,weonlyneedtostaticallyimportitsmethodsfrom

org.mockito.Usually,yourIDEwillgiveyoutheoptiontostaticallyimportthese,butifitdoesnot,youcantrytoaddthemmanually(ifthecodeisredwhenmanuallyadded,thenyouhaveaproblemwiththelibrarybeingavailable):

importstaticorg.mockito.Matchers.*;

importstaticorg.mockito.Mockito.*;

Itispreferabletousespecificimportsinsteadofusingthewildcard.Thewildcardsareherejustforbrevity.ItismostlikelythatwhenyourIDEautosaves,itwillexpandthemintotheimportsneeded(orremovethemifyouaren’tusingthem!).

ImportinglibrariesWehaveaddedtheMockitolibrarytotheproject’sJavaBuildPath.Usually,thisisnotaproblem,butsometimes,rebuildingtheprojectleadsustothefollowingerrorthatstopstheprojectbeingbuilt:Error:duplicatefilesduringpackagingofAPK.

Thisdependsonhowmanylibrariesareincludedbytheprojectandwhattheyare.

MostoftheavailableopensourcelibrarieshaveasimilarcontentasproposedbyGNUandincludefilessuchasLICENSE,NOTICE,CHANGES,COPYRIGHT,andINSTALL,amongothers.WewillfindthisproblemassoonaswetrytoincludemorethanoneinthesameprojecttoultimatelybuildasingleAPK.Thiscanberesolvedinyourbuild.gradle:

packagingOptions{

exclude'META-INF/LICENSE'

exclude'folder/duplicatedFileName'

}

MockitousageexampleLet’screateEditText,whichonlyacceptssigneddecimalnumbers.We’llcallitEditNumber.EditNumberusesInputFiltertoprovidethisfeature.Inthefollowingtests,wewillbeexercisingthisfiltertoverifythatthecorrectbehaviorisimplemented.

Tocreatethetest,wewillbeusingapropertythatEditNumberinheritsfromEditText,soitcanaddalistener,actuallyaTextWatcher.ThiswillprovidemethodsthatarecalledwheneverthetextofEditNumberchanges.ThisTextWatcherisacollaboratorforthetest,andwecouldhaveimplementeditasitsownseparateclassandverifiedtheresultsofcallingitsmethods,butthisistedious,andmightintroducemoreerrors,sotheapproachtakenistouseMockitoinordertoavoidtheneedofwritinganexternalTextWatcher.

ThisispreciselyhowweareintroducingamockTextWatchertocheckmethodinvocationswhenthetextchanges.

TheEditNumberfiltertestsThissuiteoftestswillexerciseInputFilterbehaviorofEditNumber,checkingthemethodcallsontheTextWatchermockandverifyingtheresults.

WeareusinganAndroidTestCasebecauseweareinterestedintestingEditNumberinisolationofothercomponentsorActivities.

Wehaveseveralinputsthatneedtobetested(weallowdecimalnumbers,butdonotallowmultipledecimals,letters,andsoon),andsowecanhaveonetestwithanarrayofexpectedinputandanarrayofexpectedoutput.However,thetestcangetverycomplicatedandwouldbeawfultomaintain.AbetterapproachistohaveonetestforeachtestcaseofInputFilter.Thisallowsustogivemeaningfulnamestoourtestsandanexplanationofwhatweareaimingtotest.Wewillfinishupwithalistlikethis:

testTextChangedFilter*

*WorksForBlankInput

*WorksForSingleDigitInput

*WorksForMultipleDigitInput

*WorksForZeroInput

*WorksForDecimalInput

*WorksForNegativeInput

*WorksForDashedInput

*WorksForPositiveInput

*WorksForCharacterInput

*WorksForDoubleDecimalInput

Now,wewillrunthroughtheuseofmocksforoneoftheseteststestTextChangedFilterWorksForCharacterInput(),andifyouchecktheexampleproject,youwillseethatalltheothertestsfollowthesamepattern,andwehaveactuallyextractedoutahelpermethodthatactsasacustomassertionforalltests:

publicvoidtestTextChangedFilterWorksForCharacterInput(){

assertEditNumberTextChangeFilter("A1A","1");

}

/**

*@paraminputthetexttobefiltered

*@paramoutputtheresultyouexpectoncetheinputhasbeenfiltered

*/

privatevoidassertEditNumberTextChangeFilter(Stringinput,Stringoutput)

{

intlengthAfter=output.length();

TextWatchermockTextWatcher=mock(TextWatcher.class);

editNumber.addTextChangedListener(mockTextWatcher);

editNumber.setText(input);

verify(mockTextWatcher)

.afterTextChanged(editableCharSequenceEq(output));

verify(mockTextWatcher)

.onTextChanged(charSequenceEq(output),eq(0),eq(0),eq(lengthAfter));

verify(mockTextWatcher)

.beforeTextChanged(charSequenceEq(""),eq(0),eq(0),eq(lengthAfter));

}

Asyoucansee,thetextcaseisprettystraightforward;itassertsthatwhenyouenterA1AintothetextoftheEditNumberview,thetextisactuallychangedinto1.ThismeansthatourEditNumberhasfilteredoutthecharacters.AninterestingthinghappenswhenwelookattheassertEditNumberTextChangeFilter(input,output)helpermethod.WithinourhelpermethodiswhereweverifythattheInputFilterisdoingitsjobanditishereweuseMockito.TherearefourcommonstepstotakewhenusingMockitomockobjects:

1. Instantiatetheintendedmocksthatarereadyforuse.2. Determinewhatbehaviorisexpectedandstubittoreturnanyfixturedata.3. Exercisethemethods,usuallybyinvokingmethodsoftheclassundertest.4. Verifythebehaviorofyourmockobjecttopassthetest.

Accordingtostepone,wecreateamockTextWatcherusingmock(TextWatcher.class)andsetitasourTextChangedListeneronEditNumber.

Weskipsteptwointhisinstanceaswehavenofixturedata,inthattheclasswearemockingdoesnothaveanymethodsthatareexpectedtoreturnavalue.We’llcomebacktothisinanothertestlateron.

Instepthree,wehaveourmockinplace,andwecanexercisethemethodundertesttoperformitsintendedaction.Inourcase,themethodiseditNumber.setText(input),andtheintendedactionistosetthetextandthuspromptourInputFiltertorun.

Stepfouriswhereweverifythatthetextwasactuallychangedbyourfilter.Let’sbreakstepfourdownalittle.Hereareourverificationsagain:

verify(mockTextWatcher)

.afterTextChanged(editableCharSequenceEq(output));

verify(mockTextWatcher)

.onTextChanged(charSequenceEq(output),eq(0),eq(0),eq(lengthAfter));

verify(mockTextWatcher)

.beforeTextChanged(charSequenceEq(""),eq(0),eq(0),eq(lengthAfter));

Wewillbeusingtwocustomwrittenmatchers(editableCharSequenceEq(String)andcharSequenceEq(String))becauseweareinterestedincomparingthestringcontentfordifferentclassesusedbyAndroid,suchasEditableandCharSequence.Whenyouuseaspecialmatcher,itmeansallcomparisonsdoneforthatverificationmethodcallneedaspecialwrappermethod.

Theothermatcher,eq(),expectsintthatisequaltothegivenvalue.ThelatterisprovidedbyMockitoforallprimitivetypesandobjects,butweneedtoimplementeditableCharSequenceEq()andcharSequenceEq()asitisanAndroid-specificmatcher.

MockitohasapredefinedArgumentMatcherthatwouldhelpuscreateourmatcher.Youextendtheclassanditgivesyouonemethodtooverride:

abstractbooleanmatches(Tt);

Thematchesargumentmatchermethodexpectsanargumentthatyoucanusetocompare

againstapredefinedvariable.Thisargumentisthe“actual”resultofyourmethodinvocation,andthepredefinedvariableisthe“expected”one.Youthendecidetoreturntrueorfalsewhethertheyarethesameornot.

Asyoumighthavealreadyrealized,thecustomArgumentMatcherclass’sfrequentuseinatestcouldbecomereallycomplexandmightleadtoerrors,sotosimplifythisprocess,wewillbeusingahelperclassthatwecallCharSequenceMatcher.WealsohaveEditableCharSequenceMatcher,whichcanbefoundintheexampleprojectofthischapter:

classCharSequenceMatcherextendsArgumentMatcher<CharSequence>{

privatefinalCharSequenceexpected;

staticCharSequencecharSequenceEq(CharSequenceexpected){

returnargThat(newCharSequenceMatcher(expected));

}

CharSequenceMatcher(CharSequenceexpected){

this.expected=expected;

}

@Override

publicbooleanmatches(Objectactual){

returnexpected.toString().equals(actual.toString());

}

@Override

publicvoiddescribeTo(Descriptiondescription){

description.appendText(expected.toString());

}

}

Weimplementmatchesbyreturningtheresultofthecomparisonoftheobjectpassedasargumentswithourpredefinedfieldaftertheyareconvertedtoastring.

WealsooverridethedescribeTomethod,andthisallowsustochangetheerrormessagewhentheverificationfails.Thisisalwaysagoodtiptoremember:takealookattheerrormessagesbeforeandafterdoingthis:

Argument(s)aredifferent!Wanted:

textWatcher.afterTextChanged(<Editablecharsequencematcher>);

Actualinvocationhasdifferentarguments:

textWatcher.afterTextChanged(1);

Argument(s)aredifferent!Wanted:

textWatcher.afterTextChanged(1XX);

Actualinvocationhasdifferentarguments:

textWatcher.afterTextChanged(1);

Whenthestaticinstantiationmethodforourmatcherisusedandweimportthisasastaticmethod,inourtest,wecansimplywrite:

verify(mockTextWatcher).onTextChanged(charSequenceEq(output),…

TestingviewsinisolationThetestthatweareanalyzinghereisbasedontheFocus2AndroidTestfromtheAndroidSDKApiDemosproject.ItdemonstrateshowsomepropertiesoftheViewsthatconformtoalayoutcanbetestedwhenthebehavioritselfcannotbeisolated.Thetestingfocusabilityofaviewisoneofthesesituations.

Weareonlytestingindividualviews.InordertoavoidcreatingthefullActivity,thistestextendsAndroidTestCase.YoumayhavethoughtaboutusingjustTestCase,butunfortunately,thisisnotpossibleasweneedaContexttoinflatetheXMLlayoutviaLayoutInflater,andAndroidTestCasewillprovideuswiththiscomponent:

publicclassFocusTestextendsAndroidTestCase{

privateFocusFinderfocusFinder;

privateViewGrouplayout;

privateButtonleftButton;

privateButtoncenterButton;

privateButtonrightButton;

@Override

protectedvoidsetUp()throwsException{

super.setUp();

focusFinder=FocusFinder.getInstance();

//inflatethelayout

Contextcontext=getContext();

LayoutInflaterinflater=LayoutInflater.from(context);

layout=(ViewGroup)inflater.inflate(R.layout.view_focus,null);

//manuallymeasureit,andlayitout

layout.measure(500,500);

layout.layout(0,0,500,500);

leftButton=(Button)layout.findViewById(R.id.focus_left_button);

centerButton=(Button)layout.findViewById(R.id.focus_center_button);

rightButton=(Button)layout.findViewById(R.id.focus_right_button);

}

Thesetuppreparesourtestasfollows:

1. WerequestaFocusFinderclass.ThisisaclassthatprovidesthealgorithmusedtofindthenextfocusableView.Itimplementsthesingletonpatternandthat’swhyweuseFocusFinder.getInstance()toobtainareferencetoit.Thisclasshasseveralmethodstohelpusfindfocusableandtouchableitems,givenvariousconditionsasthenearestinagivendirectionorsearchingfromaparticularrectangle.

2. Then,wegettheLayoutInflaterclassandinflatethelayoutundertest.Onethingweneedtotakeintoaccount,asourtestisisolatedfromotherpartsofthesystem,isthatwehavetomanuallymeasureandlayoutthecomponents.

3. Then,weusethefindviewspatternandassignthefoundviewstothefields.

Inapreviouschapter,weenumeratedalltheavailableassertsinourarsenal,andyoumayrememberthattotestaView’sposition,wehadacompletesetofassertionsintheViewAssertsclass.However,thisdependsonhowthelayoutisdefined:

publicvoidtestGoingRightFromLeftButtonJumpsOverCenterToRight(){

ViewactualNextButton=

focusFinder.findNextFocus(layout,leftButton,View.FOCUS_RIGHT);

Stringmsg="rightshouldbenextfocusfromleft";

assertEquals(msg,this.rightButton,actualNextButton);

}

publicvoidtestGoingLeftFromRightButtonGoesToCenter(){

ViewactualNextButton=

focusFinder.findNextFocus(layout,rightButton,View.FOCUS_LEFT);

Stringmsg="centershouldbenextfocusfromright";

assertEquals(msg,this.centerButton,actualNextButton);

}

ThemethodtestGoingRightFromLeftButtonJumpsOverCenterToRight(),asitsnamesuggests,teststhefocusgainedbytherightbuttonwhenthefocusmovesfromthelefttotherightbutton.Toachievethissearch,theinstanceofFocusFinderobtainedduringthesetUp()methodisemployed.ThisclasshasafindNextFocus()methodtoobtaintheViewthatreceivesfocusinagivendirection.Thevalueobtainedischeckedagainstourexpectations.

Inasimilarway,thetestGoingLeftFromRightButtonGoesToCenter()testteststhefocusthatgoesintheotherdirection.

TestingparsersTherearemanyoccasionswhereyourAndroidapplicationreliesonexternalXML,JSONmessages,ordocumentsobtainedfromwebservices.Thesedocumentsareusedfordatainterchangebetweenthelocalapplicationandtheserver.TherearemanyusecaseswhereXMLorJSONdocumentsareobtainedfromtheserverorgeneratedbythelocalapplicationtobesenttotheserver.Ideally,methodsinvokedbytheseactivitieshavetobetestedinisolationtohaverealunittests,andtoachievethis,weneedtoincludesomemockfilessomewhereinourAPKtorunthetests.

Butthequestioniswherecanweincludethesefiles?

Let’sfindout.

AndroidassetsTobegin,abriefreviewoftheassets’definitioncanbefoundintheAndroidSDKdocumentation:

Thedifferencebetween“resources”and“assets”isn’tmuchonthesurface,butingeneral,you’lluseresourcestostoreyourexternalcontentmuchmoreoftenthanyou’lluseassets.TherealdifferenceisthatanythingplacedintheresourcesdirectorywillbeeasilyaccessiblefromyourapplicationfromtheRclass,whichiscompiledbyAndroid.Whereas,anythingplacedintheassetsdirectorywillmaintainitsrawfileformatand,inordertoreadit,youmustusetheAssetManagertoreadthefileasastreamofbytes.Sokeepingfilesanddatainresources(res/)makesthemeasilyaccessible.

Clearly,assetsarewhatweneedtostorethefilesthatwillbeparsedtotesttheparser.

SoourXMLorJSONfilesshouldbeplacedintheassetsfoldertopreventmanipulationatcompiletimeandtobeabletoaccesstherawcontentwhiletheapplicationortestsarerun.

Butbecareful,weneedtoplacethemintheassetsofourandroidTestfolderbecausethen,thesearenotpartoftheapplication,andwedon’twantthempackedwithourcodewhenwereleasealiveapplication.

TheparsertestThistestimplementsanAndroidTestCaseasallweneedisaContexttobeabletoreferenceourassetsfolder.Also,wehavewrittentheparsinginsideofthetest,asthepointofthistestisnothowtoparsexmlbuthowtoreferencemockassetsfromyourtests:

publicclassParserExampleActivityTestextendsAndroidTestCase{

publicvoidtestParseXml()throwsIOException{

InputStreamassetsXml=getContext().getAssets()

.open("my_document.xml");

Stringresult=parseXml(assetsXml);

assertNotNull(result);

}

}

}

TheInputStreamclassisobtainedbyopeningthemy_document.xmlfilefromtheassetsbygetContext().getAssets().NotethattheContextandthustheassetsobtainedherearefromthetestspackageandnotfromtheActivityundertest.

Next,theparseXml()methodisinvokedusingtherecentlyobtainedInputStream.IfthereisanIOException,thetestwillfailandspitouttheerrorfromthestacktrace,andifeverythinggoeswell,wetestthattheresultisnotnull.

WeshouldthenprovidetheXMLwewanttouseforthetestinanassetnamedmy_document.xml.Youwanttheassettobeunderthetestprojectfolder;bydefault,thisisandroidTest/assets.

Thecontentcouldbe:

<?xmlversion="1.0"encoding="UTF-8"?>

<records>

<record>

<name>Paul</name>

</record>

</records>

TestingformemoryusageSometimes,memoryconsumptionisanimportantfactortomeasurethegoodbehaviorofthetesttarget,beitanActivity,Service,ContentProvider,oranothercomponent.

Totestforthiscondition,wecanuseautilitytestthatyoucaninvokefromothertestsmainlyafterhavingrunatestloop:

publicvoidassertNotInLowMemoryCondition(){

//Verification:checkifitisinlowmemory

ActivityManager.MemoryInfomi=newActivityManager.MemoryInfo();

((ActivityManager)getActivity()

.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(mi);

assertFalse("Lowmemorycondition",mi.lowMemory);

}

Thisassertioncanbecalledfromothertests.Atthebeginning,itobtainsMemoryInfofromActivityManagerusinggetMemoryInfo(),aftergettingtheinstanceusinggetSystemService().ThelowMemoryfieldissettotrueifthesystemconsidersitselftocurrentlybeinalowmemorysituation.

Insomecases,wewanttodiveevendeeperintotheresourceusageandcanobtainmoredetailedinformationfromtheprocesstable.

Wecancreateanotherhelpermethodtoobtainprocessinformationanduseitinourtests:

privateStringcaptureProcessInfo(){

InputStreamin=null;

try{

Stringcmd="ps";

Processp=Runtime.getRuntime().exec(cmd);

in=p.getInputStream();

Scannerscanner=newScanner(in);

scanner.useDelimiter("\\A");

returnscanner.hasNext()?scanner.next():"scannererror";

}catch(IOExceptione){

fail(e.getLocalizedMessage());

}finally{

if(in!=null){

try{

in.close();

}catch(IOExceptionignore){

}

}

}

return"captureProcessInfoerror";

}

Toobtainthisinformation,acommand(inthiscase,psisused,butyoucanadaptittoyourneeds)isexecutedusingRuntime.exec().Theoutputofthiscommandisconcatenatedinastringthatislaterreturned.Wecanusethereturnvaluetoprintittothelogsinourtest,orwecanfurtherprocessthecontenttoobtainsummaryinformation.

Thisisanexampleofloggingtheoutput:

Log.d(TAG,captureProcessInfo());

Whenthistestisrun,weobtaininformationabouttherunningprocesses:

D/ActivityTest(1):USERPIDPPIDVSIZERSSWCHANPCNAME

D/ActivityTest(1):root10312220c009b74c0000ca4cS

/init

D/ActivityTest(1):root2000c004e72c00000000S

kthreadd

D/ActivityTest(1):root3200c003fdc800000000S

ksoftirqd/0

D/ActivityTest(1):root4200c004b2c400000000S

events/0

D/ActivityTest(1):root5200c004b2c400000000S

khelper

D/ActivityTest(1):root6200c004b2c400000000S

suspend

D/ActivityTest(1):root7200c004b2c400000000S

kblockd/0

D/ActivityTest(1):root8200c004b2c400000000S

cqueue

D/ActivityTest(1):root9200c018179c00000000S

kseriod

Theoutputwascutforbrevity,butifyourunit,youwillgetthecompletelistofprocessesthatrunonthesystem.

Abriefexplanationoftheinformationobtainedisasfollows:

Column Description

USER ThisisthetextualuserID.

PID ThisistheprocessIDnumberoftheprocess.

PPID ThisistheparentprocessID.

VSIZE ThisisthevirtualmemorysizeoftheprocessinKB.Thisisthevirtualmemorytheprocessreserves.

RSSThisistheresidentsetsize,thenon-swappedphysicalmemorythatataskhasused(inpages).Thisistheactualamountofrealmemorytheprocesstakesinpages.

Thisdoesnotincludepagesthathavenotbeendemand-loadedin.

WCHAN Thisisthe“channel”inwhichtheprocessiswaiting.Itistheaddressofasystemcall,andcanbelookedupinanamelistifyouneedatextualname.

PC ThisisthecurrentEIP(instructionpointer).

State(noheader)

Thisdenotestheprocessstates,whichareasfollows:

SisusedtoindicatesleepinginaninterruptiblestateRisusedtoindicaterunningTisusedtoindicateastoppedprocessZisusedtoindicateazombie

Column Description

NAME Thisdenotesthecommandname.TheapplicationprocessesinAndroidarerenamedafteritspackagename.

TestingwithEspressoTestingUIcomponentscanbedifficult.Knowingwhenaviewhasbeeninflatedorensuringyoudon’taccessviewsonthewrongthreadcanleadtostrangebehaviorandflakytests.ThisiswhyGooglehasreleasedahelperlibraryforUI-relatedinstrumentationtestscalledEspresso(https://code.google.com/p/android-test-kit/wiki/Espresso).

AddingtheEspressolibraryJARcanbeachievedbyaddingtothe/libsfolder,buttomakeiteasierforGradleusers,GooglereleasedaversiontotheirMavenrepository(consideryourselvesluckyusersbecausethiswasnotavailablebeforeversion2.0).WhenusingEspresso,youneedtousethebundledTestRunneraswell.Therefore,thesetupbecomes:

dependencies{

//otherdependencies

androidTestCompile('com.android.support.test.espresso:espresso-core:2.0')

}

android{

defaultConfig{

//otherconfiguration

testInstrumentationRunner

"android.support.test.runner.AndroidJUnitRunner"

}

//AnnoyinglythereisaoverlapwithEspressodependenciesatthemoment

//addthisclosuretofixinternaljarfilenameclashes

packagingOptions{

exclude'LICENSE.txt'

}

}

OncetheEspressodependencyhasbeenaddedtoyourproject,youhaveafluidinterfacetobeabletoassertthebehavioronyourUIelements.Inourexample,wehaveanActivitythatallowsyoutoorderEspressocoffee.Whenyoupresstheorderbutton,aniceEspressoimageappears.Wewanttoverifythisbehaviorinaninstrumentationtest.

ThefirstthingtodoistosetupourActivitytotest.WeuseActivityInstrumentationTestCase2sothatwecanhaveafulllifecycleActivityrunning.YouneedtocallgetActivity()atthestartofyourtestorinthesetup()methodtoallowtheactivitytobestartedandforEspressotofindtheActivityinaresumedstate:

publicclassExampleEspressoTestextends

ActivityInstrumentationTestCase2<EspressoActivity>{

publicExampleEspressoTest(){

super(EspressoActivity.class);

}

@Override

publicvoidsetUp()throwsException{

getActivity();

}

Oncethesetupisdone,wecanwriteatestusingEspressotoclickourbuttonandcheckwhethertheimagewasshown(madevisible)intheActivity:

publicvoidtestClickingButtonShowsImage(){

Espresso.onView(

ViewMatchers.withId(R.id.espresso_button_order))

perform(ViewActions.click());

Espresso.onView(

ViewMatchers.withId(R.id.espresso_imageview_cup))

.check(ViewAssertions.matches(ViewMatchers.isDisplayed()));

}

ThisexampleshowstheuseofEspressotofindourorderbutton,clickonthebutton,andcheckwhetherourorderedEspressoisshowntotheuser.Espressohasafluidinterface,meaningitfollowsabuilder-stylepattern,andmostmethodcallscanbechained.Intheprecedingexample,Ishowedthefullyqualifiedclassesforclarity,butthesecaneasilybechangedtostaticimportssothatthetestisevenmorehumanreadable:

publicvoidtestClickingButtonShowsImage(){

onView(withId(R.id.espresso_button_order))

.perform(click());

onView(withId(R.id.espresso_imageview_cup))

.check(matches(isDisplayed()));

}

Thiscannowbereadinamuchmoresentencestyle.ThisexampleshowstheuseofEspressotofindourorderbuttononView(withId(R.id.espresso_button_order)).Clickonperform(click()),thenwefindthecupimageonView(withId(R.id.espresso_imageview_cup)),andcheckwhetheritisvisibletotheusercheck(matches(isDisplayed())).

Thisshowsthattheonlyclassesyouneedtothinkaboutare:

Espresso:Thisistheentrypoint.AlwaysstartwiththistointeractwithaView.ViewMatchers:ThisisusedtolocateaViewwithinthecurrenthierarchy.ViewActions:Thisisusedtoclick,longclick,andsoon,onalocatedView.ViewAssertions:ThisisusedtocheckthestateofaViewafteranactionhasbeenperformed.

EspressohasareallypowerfulAPI,whichallowsyoutotestthepositionsofviewsnexttoeachother,matchdatainaListView,getdatastraightfromaheaderorfooter,andchecktheviewsinyourActionBar/ToolBarandmanymoreassertions.Anotherfeatureisitscapabilitytodealwiththreading;EspressowillwaitforasynchronoustaskstofinishbeforeitassertswhethertheUIhaschanged.Anexplanationofthesefeaturesandmuchmoreislistedonthewikipage(https://code.google.com/p/android-test-kit/w/list).

SummaryInthischapter,severalreal-worldexamplesofteststhatcoverawiderangeofcaseswerepresented.Youcanusethemasastartingpointwhilecreatingyourowntests.

Wecoveredavarietyoftestingrecipesthatyoucanextendforyourowntests.WeusedmockcontextsandshowedhowRenamingDelegatingContextcanbeusedinvarioussituationstochangethedataobtainedbythetests.Wealsoanalyzedtheinjectionofthesemockcontextsintotestdependencies.

Then,weusedActivityUnitTestCasetotestActivitiesincompleteisolation.WetestedViewsinisolationusingAndroidTestCase.WedemonstratedtheuseofMockitotomockobjectscombinedwithArgumentMatcherstoprovidecustommatchersonanyobject.Finally,wetreatedtheanalysisofpotentialmemoryleaksandtookapeekintothepoweroftestingUIwithEspresso.

Thenextchapterfocusesonmanagingyourtestenvironmenttoenableyoutoruntestsinaconsistent,fast,andalwaysdeterministicway,whichleadstoautomationandthosemischievousmonkeys!

Chapter4.ManagingYourAndroidTestingEnvironmentNowthatwehaveacompleteunderstandingoftheavailableAndroidtestingSDKandhaveanicerangeoftestingrecipesreadytoassertandverifyourapp’sbehavior,itistimetoprovidedifferentconditionstorunourtests,exploreothertests,orevenusetheapplicationmanuallytounderstandwhattheenduserexperiencewouldbe.

Inthischapter,wewillcover:

CreatingAndroidVirtualDevices(AVD)toprovidedifferentconditionsandconfigurationsforanapplicationUnderstandingthedifferentconfigurationsthatwecanspecifywhilecreatingAVDsHowtorunAVDsHowtocreateheadlessemulatorsUnlockingthescreentobeabletorunallthetestsSimulatingreal-lifenetworkconditionsSpeedingupyourAVDwithHAXMAlternativestotheAndroidVirtualDeviceRunningmonkeytogenerateeventstosendtotheapplication

CreatingAndroidVirtualDevicesTohavethebestchanceofdetectingproblemsrelatedtothedeviceonwhichtheapplicationisrunning,youneedthewidestpossiblecoverageofdevicefeaturesandconfigurations.

Whilefinalandconclusivetestsshouldalwaysberunonrealdevices,withtheever-increasingnumberofdevicesandformfactors,itisvirtuallyimpossiblethatyouwillhaveonedeviceofeachtotestyourapplication.Therearealsodevicefarmsinthecloudtotestonavarietyofdevices(Googleforclouddevicetesting),butsometimes,theircostisabovetheaveragedeveloperbudget.Androidprovidesawayofemulating,moreorlessverbatim,agreatvarietyoffeaturesandconfigurationjustfortheconvenienceofdifferentAVDconfigurations(anemulator).

NoteAlltheexamplesinthischapterarerunfromOSX10.9.4(Mavericks)32bitusingAndroidSDKTools23.0.5withplatform4.4.2(API20)installed.

TocreateanAVD,youcanusetheandroidavdcommandfromaterminal,orfrominsideAndroidStudio,usingTools|Android|AVDManageroritsshortcuticon.IfyouruntheAVDManagerfromaterminal,yougetaGUIthatisslightlydifferentthanwhatyougetbyrunningfromAndroidStudio,buttheybothdothesamejob.We’regoingtobeusingtheAVDManagerfromAndroidStudioasthisisthemostlikelyusecase.

Byclickingontheicon,youcanaccesstheAVDManager.Here,youpresstheCreateDevice…buttontocreateanewAVD,andthefollowingdialogboxispresented:

Now,youcanselectaprofilephoneforthehardware(let’spickNexus5),hitNext,and

selectanAndroidversion(KitKatx86).HitNextagain,andyougetasummaryofyourdevice.YoucanclickonFinishandyoucreatetheAVDusingthedefaultvalues.However,ifyouneedtosupportspecificconfigurations,youcanspecifydifferenthardwareproperties.Let’schangetheAVDnametotestdevice.EvenmorepropertiesareavailablebyusingtheShowAdvancedSettingsbutton.

Awiderangeofpropertiescanbeset.Somehighlightsare:

Ramsize/SDcardsizeEmulateoruseyourwebcamasfront/backcameraChangethenetworkspeed/simulatelatency

Settingthescaleisalsoveryusefultotestyourapplicationinawindowthatresemblesthesizeofarealdevice.ItisaverycommonmistaketotestyourapplicationinanAVDwithawindowsizethatisatleasttwicethesizeofarealdevice,andusingamousepointer,believingthateverythingisfine,tolaterrealizeonaphysicaldevicewithascreenof5or6inchesthatsomeitemsontheUIareimpossibletotouchwithyourfinger.

Finally,itisalsohelpfultotestyourapplicationunderthesameconditionsrepeatedly.Tobeabletotestunderthesameconditionsagainandagain,itissometimeshelpfultodeletealltheinformationthatwasenteredintheprevioussessions.Ifthisisthecase,ensureStoreasnapshotforfasterstartupisuntickedsoastostartafresheverytime.

RunningAVDsfromthecommandlineWouldn’titbeniceifwecouldrundifferentAVDsfromthecommandlineandperhapsautomatethewaywerunorscriptourtests?

ByfreeingtheAVDfromitsUIwindow,weopenawholenewworldofautomationandscriptingpossibilities.

Well,let’sexplorethesealternatives.

HeadlessemulatorAheadlessemulator(itsUIwindowisnotdisplayed)comesinveryhandywhenwerunautomatedtestsandnobodyislookingatthewindow,ortheinteractionbetweenthetestrunnerandtheapplicationissofastthatwehardlyseeanything.

Also,itisworthmentioningthat,sometimes,youcan’tunderstandwhysometestsfailuntilyouseetheinteractiononthescreen,souseyourownjudgmentwhenselectingarunningmodeforyouremulator.

OnethingthatwemayhavenoticedwhilerunningAVDsisthattheirnetworkcommunicationportsareassignedatruntime,incrementingthelastusedportby2andstartingwith5554.Thisisusedtonametheemulatorandsetitsserialnumber;forexample,theemulatorusingport5554becomesemulator-5554.ThisisveryusefulwhenwerunAVDsduringthedevelopmentprocessbecausewedon’thavetopayattentiontoportassignment.However,itcanbeveryconfusinganddifficulttotrackwhichtestrunsonwhichemulatorifwearerunningmorethanonesimultaneously.

Insuchcases,wewillbespecifyingmanualportstokeepthespecificAVDunderourcontrol.

Usually,whenwearerunningtestsonmorethanoneemulatoratthesametime,notonlydowewanttodetachthewindow,butalsoavoidsoundoutput.Wewilladdoptionsforthisaswell.

ThecommandlinetolaunchthetestAVDthatwejustcreatedisasfollows,andtheportmustbeanintegerbetween5554and5584:

$emulator-avdtestdevice-no-window-no-audio-no-boot-anim-port5580

Wecannowcheckwhetherthedeviceisinthedevicelist:

$adbdevices

Listofdevicesattached

emulator-5580device

Thenextstepistoinstalltheapplicationandthetests:

$adb-semulator-5580installYourApp.apk

347KB/s(16632bytesin0.046s):/data/local/tmp/YourApp.apk

Success

$adb-semulator-5580installYourAppTests.apk

222KB/s(16632bytesin0.072s)

pkg:/data/local/tmp/YourAppTests.apk

Success

Then,wecanusethespecifiedserialnumbertorunthetestsonit:

$adb-semulator-5580shellaminstrument-w\

com.blundell.tut.test/android.test.InstrumentationTestRunner

com.blundell.tut.test.MyTests:......

com.blundell.tut.test.MyOtherTests:..........

TestresultsforInstrumentationTestRunner=..................

Time:15.295

OK(20tests)

DisablingthekeyguardWecanseethetestsbeingrunwithoutthemrequiringanyinterventionandaccesstotheemulatorGUI.

Sometimes,youmightreceivesomeerrorsforteststhatarenotfailingifyouruninamorestandardapproach,likeinastandardemulatorlaunchedfromyourIDE.Insuchcases,oneofthereasonsisthattheemulatormightbelockedatthefirstscreen,andweneedtounlockittobeabletorunteststhatinvolvetheUI.

Tounlockthescreen,youcanusethefollowingcommand:

$adb-semulator-5580emueventsendEV_KEY:KEY_MENU:1EV_KEY:KEY_MENU:0

Thelockscreencanalsobedisabledprogrammatically.Intheinstrumentationtestclass,youshouldaddthefollowingcode,mostprobablyinsetup():

@Override

publicvoidsetUp()throwsException{

Activityactivity=getActivity();

Windowwindow=activity.getWindow();

window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

}

Thiswilldismissthekeyguardforthesetestsandhastheaddedadvantageofnotneedinganyextrasecuritypermissionsorchangestotheappundertest(whichthedeprecatedalternativedoes,seehttp://developer.android.com/reference/android/app/KeyguardManager.html).

CleaningupOncertainoccasions,youalsoneedtocleanupservicesandprocessesthatarestartedafterrunningtests.Thispreventstheresultsofthelatterfrombeinginfluencedbytheendingconditionsoftheprevioustests.Inthesecases,itisalwaysbettertostartfromaknowncondition,freeingalltheusedmemory,stoppingservices,reloadingresources,andrestartingprocesses,whichisachievablebywarm-bootingtheemulator:

$adb-semulator-5580shell'stop';sleep5;start'

Thiscommandlineopenstheemulatorshellforouremulator,andrunsthestopandstartcommands,oraspeoplesay,turningitoffandonagain.

Theoutputofthesecommandscanbemonitoredusingthelogcatcommand:

$adb-semulator-5580logcat

Youwillseemessageslikethese:

D/AndroidRuntime(1):

D/AndroidRuntime(1):>>>>>>>>>>AndroidRuntimeSTART<<<<<<<<<<

D/AndroidRuntime(1):CheckJNIisON

D/AndroidRuntime(1):---registeringnativefunctions---

I/SamplingProfilerIntegration(1):Profilerisdisabled.

I/Zygote(1):Preloadingclasses…

I/ServiceManager(2):service'connectivity''connectivity''connectivity'''

died

I/ServiceManager(2):service'throttle''throttle''throttle'''died

I/ServiceManager(2):service

'accessibility''accessibility''accessibility'''died

TerminatingtheemulatorOncewefinishworkingwithoneoftheheadlessemulatorinstances,westartusingthecommandmentionedearlier.Weusethefollowingcommandlinetokillit:

$adb-semulator-5580emukill

Thiswillstoptheemulatorfromfreeingtheusedresourcesandterminatingtheemulatorprocessonthehostcomputer.

AdditionalemulatorconfigurationsSometimes,whatweneedtotestisoutsidethereachoftheoptionsthatcanbesetwhentheAVDiscreatedorconfigured.

Oneofthecasescouldbetheneedtotestourapplicationunderdifferentlocales.Let’ssaywewanttotestourapplicationonaJapanesephone—anemulator,withthelanguageandcountrysettoJapaneseandJapanrespectively.

Wehavetheabilitytopassthesepropertiesintheemulatorcommandline.The-propcommandlineoptionallowsustosetanyofthepropertieswecouldset:

$emulator-avdtestdevice-no-window-no-audio-no-boot-anim-port5580

-proppersist.sys.language=ja-proppersist.sys.country=JP

Toverifythatoursettingsweresuccessful,wecanusethegetpropcommandtoverifythem,forexample:

$adb–semulator-5580shell"getproppersist.sys.language"

ja

$adb–semulator-5580shell"getproppersist.sys.country"

JP

Ifyouwanttoclearalltheuserdataafterplayingwiththepersistentsettings,youcanusethefollowingcommand:

$adb-semulator-5580emukill

$emulator-avdtestdevice-no-window-no-audio-no-boot-anim-port5580-

wipe-data

Afterthis,theemulatorwillstartafresh.

NoteMoreinformationandalistofavailablepropertiesforsettingtheemulatorhardwareoptionscanbefoundathttp://developer.android.com/tools/devices/managing-avds-cmdline.html#hardwareopts.

SimulatingnetworkconditionsItisextremelyimportanttotestunderdifferentnetworkconditions,butitisneglectedmoreoftenthannot.Thiscanleadtomisconceptionsthattheapplicationbehavesdifferentlybecauseweusethehostnetworkthatpresentsadifferentspeedandlatency.

TheAndroidemulatorsupportsnetworkthrottling,forexample,tosupportslowernetworkspeedsandhigherconnectionlatencies.ThiscanbeselectedwhenyoufirstcreateyourAVD,butcanalsobedoneintheemulatoratanytimefromthecommandlineusingthe-netspeed<speed>and-netdelay<delay>options.

Thecompletelistofsupportingoptionsisasfollows:

Fornetworkspeed:

Option Description Speeds[kbits/s]

-netspeedgsm GSM/CSD Up:14.4,down:14.4

-netspeedhscsd HSCSD Up:14.4,down:43.2

-netspeedgprs GPRS Up:40.0,down:80.0

-netspeededge EDGE/EGPRS Up:118.4,down:236.8

-netspeedumts UMTS/3G Up:128.0,down:1920.0

-netspeedhsdpa HSDPA Up:348.0,down:14400.0

-netspeedfull Nolimit Up:0.0,down:0.0

-netspeed<num> Selectboththeuploadanddownloadspeed Up:asspecified,down:asspecified

-netspeed<up>:<down> Selecttheindividualupanddownspeed Up:asspecified,down:asspecified

Forlatency:

Option Description Delay[msec]

-netdelaygprs GPRS Min150,max550

-netdelayedge EDGE/EGPRS Min80,max400

-netdelayumts UMTS/3G Min35,max200

-netdelaynone Nolatency Min0,max0

-netdelay<num> Selectexactlatency Latencyasspecified

-netdelay<min>:<max> Selectminandmaxlatencies Minimumandmaximumlatenciesasspecified

Ifthevaluesarenotspecified,theemulatorusesthefollowingdefaultvalues:

ThedefaultnetworkspeedisfullThedefaultnetworklatencyisnone

ThisisanexampleofanemulatorusingtheseoptionstoselecttheGSMnetworkspeedof14.4kbits/secandaGPRSlatencyof150to500msecs:

$emulator-avdtestdevice-port5580-netspeedgsm-netdelaygprs

Oncetheemulatorisrunning,youcanverifythesenetworksettingsorchangetheminteractivelyusingtheAndroidconsoleinsideaTelnetclient:

$telnetlocalhost5580

Trying127.0.0.1…

Connectedtolocalhost.

Escapecharacteris'^]'.

AndroidConsole:type'help'foralistofcommands

OK

Afterweareconnected,wecantypethefollowingcommand:

networkstatus

Currentnetworkstatus:

downloadspeed:14400bits/s(1.8KB/s)

uploadspeed:14400bits/s(1.8KB/s)

minimumlatency:150ms

maximumlatency:550ms

OK

Youcanusetheemulatortotestapplicationsusingnetworkserviceseithermanuallyorinanautomatedway.

Insomecases,thisnotonlyinvolvesthrottlingthenetworkspeedbutalsochangingthestateoftheGPRSconnectiontoinvestigatehowtheapplicationbehavesandcopeswiththesesituations.Tochangethisstatus,wecanalsousetheAndroidconsoleinarunningemulator.

Forexample,tounregistertheemulatorfromthenetwork,wecanuse:

$telnetlocalhost5580

AfterreceivingtheOKsubprompt,wecansetthedatanetworkmodeasunregisteredbyissuingthefollowingcommand.Thiswillturnoffalldata:

gsmdataunregistered

OK

quit

Connectionclosedbyforeignhost.

Aftertestingtheapplicationunderthiscondition,youcanconnectitagainbyusingthefollowingcommandline:

gsmdatahome

OK

Toverifythestatus,youcanusethefollowingcommandlines:

gsmstatus

gsmvoicestate:home

gsmdatastate:home

OK

SpeedingupyourAVDwithHAXMWhenusingAndroidVirtualDevices,you’llnoticethattheyaren’tthemostresponsiveofemulators.ThisisduetothefactthattheAVDemulatordoesnotsupporthardwareGL,andsotheGLcodegetstranslatedintoARMsoftware,whichgetsrunonhardwareemulatedbyQEMU(QEMUisthehostedvirtualmachinemonitorthatAVDsrunontopof).Googlehasbeenworkingonthisproblem,andnow,efficientuseofthehostGPUisboostingspeed(SDK17).Responsivenesshasimprovedonthisandabovelevelsofemulator.

AnotherspeedboostcanbegainedbyusingIntel’sHardwareAcceleratedExecutionManager(HAXM).Youcangeta5to10timesspeedboostonyourAVDsthatrunx86asitwillexecutetheCPUcommandsnatively.

HAXMworksbyallowingtheCPUcommandstoberunonyourhardware(thatisyourIntelCPU),whereasearlier,QEMUwouldbesimulatingtheCPU,andallcommandswouldbethroughsoftware,whichiswhytheoriginalarchitectureiscumbersome.

Aspertherequirements,youneedtohaveanIntel-basedprocessorwithVT(VirtualizationTechnology)supportandanx86-basedemulatorwithminimumSDK10(Gingerbread).IntelclaimsthatmostIntelprocessorsfrom2005onwardswillsupportVToffloadingasstandard.

Installationisstraightforward;downloadHAXMfromtheextrassectionoftheAndroidSDKManager,locatethedownloadedfile,andfollowtheinstallerinstructions.Youcanclarifyasuccessfulinstallationbyrunningthiscommandfromaterminal:

kextstat|grepintel

Ifyougetamessagethatcontainscom.intel.kext.intelhaxm,you’veinstalledandcannowrunyourspeedyx86emulator.Thereisnothingelseyouhavetodo,justensuretheCPU/ABIofyourAndroidemulatorisx86andHAXMwillberunninginthebackgroundforyou.

AlternativestotheAVDTheAndroidVirtualDeviceisnotyouronlywayofrunningAndroidapps.Therearenowafewsolutionsyoucanchoosefrom.AquicksearchonGooglecanbringupthislist(Iwon’twriteithereastheycanquicklygetoutofdate).OneofthesethatIpersonallyrecommendistheGenyMotionemulator.ThisisanAndroidemulatorthatusesx86architecturevirtualizationtomakeitmuchmoreefficient.ItrunsmuchfasterandsmootherthantheAVD.Thedownsidebeingitisonlyfreeforpersonaluse,andasofthiswriting,itdoesnotemulateallthesensorsofadevice,butIknowtheyarebusyworkingonthis.

RunningmonkeyYoumightknowabouttheinfinitemonkeytheorem.Thistheoremstatesthatamonkeythathitskeysatrandomonatypewriterkeyboardforaninfiniteamountoftimewilleventuallytypeagiventext,suchasthecompleteworksofWilliamShakespeare.TheAndroidversionofthistheoremstatesthatamonkeythatproducesrandomtouchesonadevicecouldcrashyourapplicationin,well,muchlessthananinfiniteamountoftime.

Withthis,Androidfeaturesamonkeyapplication(http://goo.gl/LSWg85)thatwillgeneratetherandomeventsinsteadofarealmonkey.

Thesimplestwaytorunmonkeyagainstourapplicationtogeneraterandomeventsis:

$adb-eshellmonkey-pcom.blundell.tut-v-v1000

Youwillbereceivingthisoutput:

Eventsinjected:1000

:Sendingrotationdegree=0,persist=false

:Dropped:keys=0pointers=4trackballs=0flips=0rotations=0

##Networkstats:elapsedtime=2577ms(0msmobile,0mswifi,2577msnot

connected)

//Monkeyfinished

Themonkeywillsendeventsonlytothespecifiedpackage(-p),inthiscasecom.blundell.tut,inaveryverbosemanner(-v-v).Thecountofeventssentwillbe1000.

Theclient-servermonkeyThereisanotherwayofrunningmonkey.Italsopresentsaclient-servermodelthatultimatelyallowsforthecreationofscriptsthatcontrolwhateventsaresentanddoesnotrelyonlyonrandomgeneration.

Usually,theportusedbymonkeyis1080,butyoucanuseanotheroneifitbettersuitsyourpreferences:

$adb-eshellmonkey-pcom.blundell.tut--port1080&

Then,weneedtoredirecttheemulatorport:

$adb-eforwardtcp:1080tcp:1080

Now,wearereadytosendevents.Todoitmanually,wecanuseaTelnetclient:

$telnetlocalhost1080

Aftertheconnectionisestablished,wecantypethespecificmonkeycommand:

tap150200

OK

Tofinish,exitthetelnetcommand.

Ifweneedtoexercisetheapplicationrepeatedly,itismuchmoreconvenienttocreateascriptwiththecommandswewanttosend.Amonkeyscriptcouldlooklikethis:

#monkey

tap200200

typeHelloWorld

tap200350

tap200200

pressDEL

pressDEL

pressDEL

pressDEL

pressDEL

typeMonkey

tap200350

NoteTheAPIformonkeytapistap<xpixelposition><ypixelposition>.

Therefore,ifyouarenotrunninganemulatorwiththesame§resolutionastheoneyourmonkeycommandwasrecordedwith,youcouldgetincorrecttoucheventsfromyourmonkey.

Afterhavingstartedtheexampleapplicationforthischapter,wecanrunthisscripttoexercisetheuserinterface.Tostarttheapplication,youcanusetheemulatorwindowandclickonitslaunchericonorusethecommandlinethatstatestheactivityyouwanttostart,whichistheonlyalternativeiftheemulatorisheadless,asfollows:

$adbshellamstart-ncom.blundell.tut/.MonkeyActivity

Thisisinformedinthelogbythisline:

Starting:Intent{cmp=com.blundell.tut/.MonkeyActivity}

Oncetheapplicationhasstarted,youcansendtheeventsusingthescriptandthenetcatutility:

$nclocalhost1080<ch_4_code_ex_10.txt

Thiswillsendtheeventscontainedinthescriptfiletotheemulator.Thesearethefollowingevents:

1. Touchandselecttheedittextinput.2. TypeHelloWorld.3. Tapthebuttontoshowthetoast.4. Touchandselecttheedittextagain.5. Deleteitscontent.6. TypeMonkey.7. TapthebuttontoshowthetoastHelloMonkey.

Inthismanner,simplescriptsthatconsistoftoucheventsandkeypressescanbecreated.

TestscriptingwithmonkeyrunnerThepossibilitiesofmonkeyarefairlylimited,andthelackofflowcontrolrestrictsitsusetoverysimplecases.Tocircumventtheselimitations,anewprojectwascreated,whichwasnamedmonkeyrunner.Notwithstandingthis,thenameisalmostthesameandleadstoahugeamountofconfusionbecausetheyarenotrelatedinanyway.

Monkeyrunner,whichisalreadyincludedinthelatestversionsoftheAndroidSDK,isatoolthatprovidesanAPIforthepurposeofwritingscriptsthatexternallycontrolanAndroiddeviceoremulator.

MonkeyrunnerisbuiltontopofJython(http://jython.org/),aversionofthePythonprogramminglanguage(http://python.org/),whichisdesignedtorunontheJavaplatform.

Accordingtoitsdocumentation,themonkeyrunnertoolprovidestheseuniquefeaturesforAndroidtesting.Thesearejustthehighlightsofthecompletelistoffeatures,examples,andreferencedocumentationthatcanbeobtainedfromthemonkeyrunnerhomepage(http://developer.android.com/tools/help/monkeyrunner_concepts.html):

Multipledevicecontrol:ThemonkeyrunnerAPIcanapplyoneormoretestsuitesacrossmultipledevicesoremulators.Youcanphysicallyattachallthedevicesorstartupalltheemulators(orboth)atonce,connecttoeachoneinturnprogrammatically,andthenrunoneormoretests.Youcanalsostartupanemulatorconfigurationprogrammatically,runoneormoretests,andthenshutdowntheemulator.Functionaltesting:monkeyrunnercanrunanautomatedstart-to-finishtestofanAndroidapplication.Youprovideinputvalueswithkeystrokesortouchevents,andviewtheresultsasscreenshots.Regressiontesting:monkeyrunnercantesttheapplicationstabilitybyrunninganapplicationandcomparingitsoutputscreenshotstoasetofscreenshotsthatareknowntobecorrect.Extensibleautomation:SincemonkeyrunnerisanAPItoolkit,youcandevelopanentiresystemofPython-basedmodulesandprogramstocontrolAndroiddevices.BesidesusingthemonkeyrunnerAPIitself,youcanusethestandardPythonOSandsubprocessmodulestocallAndroidtoolssuchasAndroidDebugBridge.YoucanalsoaddyourownclassestothemonkeyrunnerAPI.ThisisdescribedinmoredetailintheonlinedocumentationunderExtendingmonkeyrunnerwithplugins.

GettingtestscreenshotsCurrently,oneofthemostevidentusesofmonkeyrunnerisgettingscreenshotsoftheapplicationundertesttobefurtheranalyzedorcompared.

Thesescreenshotscanbeobtainedwiththehelpofthefollowingsteps:

1. Importtherequiredmodules.2. Createtheconnectionwiththedevice.3. Checkwhetherthedeviceisconnected.4. Starttheactivity.5. Addsomedelayfortheactivitystartup.6. Type‘hello’.7. Addsomedelaytoallowfortheeventstobeprocessed.8. Obtainthescreenshots.9. Saveittoafile.10. PressBACKtoexittheActivity.

Thefollowingisthecodeforthescriptneededtoperformtheprecedingsteps:

#!/usr/bin/envmonkeyrunner

importsys

#Importsthemonkeyrunnermodulesusedbythisprogram

fromcom.android.monkeyrunnerimportMonkeyRunner,MonkeyDevice,

MonkeyImage

#Connectstothecurrentdevice,returningaMonkeyDeviceobject

device=MonkeyRunner.waitForConnection()

ifnotdevice:

print>>sys.stderr,"Couldn't""getconnection"

sys.exit(1)

device.startActivity(component='com'.blundell.tut/.MonkeyActivity')

MonkeyRunner.sleep(3.0)

device.type("hello")

#Takesascreenshot

MonkeyRunner.sleep(3.0)

result=device.takeSnapshot()

#Writesthescreenshottoafile

result.writeToFile('/tmp/device.png')

device.press('KEYCODE_BACK','DOWN'_AND_UP')

Oncethisscriptruns,youwillfindthescreenshotoftheactivityin/tmp/device.png.

RecordandplaybackIfyouneedsomethingsimpler,thereisnoneedtomanuallycreatethesescripts.Tosimplifytheprocess,themonkey_recorder.pyscript,whichisincludedintheAndroidsourcerepositoryintheSDKproject(http://goo.gl/6Qv0z0),canbeusedtorecordeventdescriptionsthatarelaterinterpretedbyanotherscriptcalledmonkey_playback.py.

Runmonkey_recorder.pyfromthecommandline,andyouwillbepresentedwiththisUI:

Thisinterfacehasatoolbarwithbuttonstoinsertdifferentcommandsintherecordedscript:

Button Description

WaitThisdenoteshowmanysecondstowait.

Thisnumberisrequestedbyadialogbox.

PressaButton ThissendstheMENU,HOME,RECENTS,andSEARCHbuttons.PresstheDownorUpevent.

TypeSomething Thissendsastring.

Fling Thissendsaflingeventinthespecifieddirection,distance,andnumberofsteps.

ExportActions Thissavesthescript.

RefreshDisplay Thisrefreshesthecopyofthescreenshotthatisdisplayed.

Oncethescriptiscompleted,saveit,let’ssayasscript.mr,andthen,youcanre-runitbyusingthiscommandline:

$monkey_playback.pyscript.mr

Now,alltheeventswillbereplayed.

SummaryInthischapter,wecoveredallthealternativeswehadtoexposeourapplicationanditsteststoawiderangeofconditionsandconfigurations,rangingfromdifferentscreensizes,theavailabilityofdevicessuchascamerasorkeyboards,tosimulatingreal-lifenetworkconditionstodetectproblemsinourapplication.

Wealsoanalyzedalloftheoptionswehaveinordertobeabletocontrolemulatorsremotelywhentheyaredetachedfromitswindow.Thispreparesthefoundationofdoingtestfirstdevelopment,andwewillcomebacktothistopicinChapter6,PracticingTest-drivenDevelopment.

WediscussedthespeedoftheAVDandsawhowwecanimprovethis,aswellaslookedatemulatorchoicesinGenyMotionandHAXM.Finally,somescriptingalternativeswereintroduced,andexamplestogetyoustartedwereprovided.

Inthenextchapter,wewilldiscovercontinuousintegration—awayofworkingthatreliesontheabilitytoautomaticallyrunallthetestsuitesandconfigure,start,andstopemulatorsinordertoautomatethecompletebuildprocess.

Chapter5.DiscoveringContinuousIntegrationContinuousintegrationisoneagiletechniqueforsoftwareengineeringthataimstoimprovesoftwarequalityandreducethetimetakentointegratechangesbycontinuouslyapplyingintegrationandtestingfrequently,asopposedtothemoretraditionalapproachofintegratingandtestingattheendofthedevelopmentcycle.

Continuousintegrationhasreceivedabroadadoption,andaproliferationofcommercialtoolsandopensourceprojectsisacleardemonstrationofitssuccess.Thatisnotverydifficulttounderstand,asanybodywhoduringtheirprofessionalcareerhasparticipatedinasoftwaredevelopmentprojectusingatraditionalapproachisverylikelytohaveexperiencedtheso-calledintegrationhell,wherethetimeittakestointegratethechangesexceedsthetimeittooktomakethechanges.Doesthisremindyouofanything?Onthecontrary,continuousintegrationisthepracticeofintegratingchangesfrequentlyandinsmallsteps.Thesestepsarenegligibleand,ifanerrorisnoticed,itissosmallthatitcanbefixedimmediately.Themostcommonpracticeistotriggerthebuildprocessaftereverycommittothesourcecoderepository.

Thispracticealsoimpliesotherrequirements,besidethesourcecodebeingmaintainedbyaversioncontrolsystem(VCS):

Buildsshouldbeautomatedbyrunningasinglecommand.Thisfeaturehasbeensupportedforaverylongtimebytoolssuchasmakeandant,andmorerecentlybymavenandgradle.Buildsshouldbeself-testingtoconfirmthatthenewlybuiltsoftwaremeetstheexpectationsofthedevelopers.Buildartifactsandresultsofthetestsshouldbeeasytofindandview.

WhenwewritetestsforourAndroidprojects,wewouldliketotakeadvantageofcontinuousintegration.Toachievethis,wewanttocreateamodelthatcoexistswiththetraditionalIDEenvironmentandAndroidbuildtools,sowecanrunandinstallourappnomattertheenvironmentsuchasCIbox,IDEormanually.

Inthischapter,wearegoingtodiscuss:

AutomatingthebuildprocessIntroducingversioncontrolsystemstotheprocessContinuousintegrationwithJenkinsAutomatingtests

Afterthischapter,youwillbeabletoapplycontinuousintegrationtoyourownprojectnomatteritssize,whetheritisamediumorlargesoftwareprojectemployingdozensofdevelopersoritisjustyouprogrammingsolo.

NoteTheoriginalarticleoncontinuousintegrationwaswrittenbyMartinFowlerbackin2000

(http://www.martinfowler.com/articles/continuousIntegration.html),anddescribestheexperienceofputtingtogethercontinuousintegrationonalargesoftwareproject.

BuildingAndroidapplicationsmanuallyusingGradleIfweaimtoincorporatecontinuousintegrationintoourdevelopmentprocess,thefirststepwillbetobuildAndroidapplicationsmanually,aswecancombineanintegrationmachinewiththemanualbuildingtechniquetoautomatetheprocedure.

Indoingthis,weintendtokeepourprojectcompatiblewiththeIDEandcommand-linebuildingprocess,andthisiswhatwearegoingtodo.Automatedbuildingisagreatadvantageandspeedsupthedevelopmentprocessbybuildingandeventuallyshowingtheerrorsthatmayexistinyourprojectimmediately.Wheneditingresourcesorotherfilesthatgenerateintermediateclasses,aCIisaninvaluabletool;otherwise,somesimpleerrorswouldbediscoveredtoolateinthebuildingprocess.Followingthemantraoffailoften,failfastisarecommendedpractice.

Fortunately,AndroidsupportsmanualbuildingwiththeexistingtoolsandnotmucheffortisneededtomergemanualIDEbuildsandautomaticCIbuildsinthesameproject.Insuchcases,buildingmanuallyinsideyourIDEwithGradleissupported.However,otheroptionssuchasAntexisttoothatarenolongersupportedbydefault,andMavenorMakethatarenotsupportedoutofthebox.

NoteGradleisbuildautomationevolved.GradlecombinesthepowerandflexibilityofAntwiththedependencymanagementandconventionsofMavenintoamoreeffectivewaytobuild.

Moreinformationcanbefoundatitshomepage,http://gradle.org/.

Atthetimeofwriting,projectsbasedonAndroidGradlerequireatleastGradle2.2ornewerversions.

ItisworthnotingherethattheentireAndroidopensourceprojectisnotbuiltbyGradlebutbuiltbyanincrediblycomplexstructureofmakefiles,andthismethodisusedeventobuildtheapplicationsthatareincludedbytheplatformsuchasCalculator,Contacts,Settings,andsoon.

WhencreatinganewprojectwithAndroidStudio,thetemplateprojectwillalreadybebeingbuiltwithGradle.Thismeansyoucanalreadybuildtheprojectmanuallyfromthecommandline.Executing./gradlewtasksfromthebaseofyourprojectwillgiveyouafulllistoftasksthatcanberun.Themostcommonlyusedtasksareasshowninthefollowingtable:

Target Description

build Assemblesandteststhisproject

clean Deletesthebuilddirectory

tasksDisplaysthetasksrunnablefromrootprojectx(someofthedisplayedtasksmaybelongtosubprojects)

installDebug InstallstheDebugbuild

installDebugTest InstallstheTestbuildfortheDebugbuild

connectedAndroidTest InstallsandrunsthetestsforBuilddebugonconnecteddevices

uninstallDebug UninstallstheDebugbuild

Thecommandsprefixedwith./gradlewuseaninstallationofGradlethatisactuallyshippedinsideyourprojectsourcecode.Thisisknownasthegradlewrapper.Therefore,youdonotneedGradleinstalledonyourlocalmachine!However,ifyoudohaveGradleinstalledlocally,allcommandsusingthewrappercanbereplacedwith./gradle.Ifthereareseveraldevicesoremulatorsconnectedtothebuildmachine,thesecommandswillrun/installonthemall.ThisisgreatforourCIsetup,meaningwecanrunourtestsonalltheprovideddevicessothatwecanhandleanumberofconfigurationsandAndroidversions.Ifyoudowanttoinstallonjustoneforsomeotherreason,thisispossiblethroughtheDeviceProvidersAPIbutisoutofthescopeofthisbook.Iencourageyoutoreadmoreathttp://tools.android.comandalsocheckoutthewiderangeofGradlepluginsavailabletohelpyouwiththis.

Nowwecanrunthiscommandtoinstallourapplication:

$./gradlewinstallDebug

Thisisthestartandendoftheoutputgenerated:

Configuring>3/3projects

:app:assembleDebug

:app:installDebug

InstallingAPK'app'-debug.'apk'on'emulator-5554'InstallingAPK'app'-

debug.'apk'on'Samsung'Galaxy'S4'

Installedon2devices.

BUILDSUCCESSFUL

Totaltime:11.011secs

Runningtheprecedingcommandmentioned,thefollowingstepsareexecuted:

Compilationofthesources,includingresource,AIDL,andJavafilesConversionofthecompiledfilesintothenativeAndroidformatPackagecreationandsigningInstallationontothegivendeviceoremulator

OncewehavetheAPKinstalled,andbecausewearenowdoingeverythingfromthecommandline,wecanevenstartanActivitysuchasEspressoActivity.UsingtheamstartcommandandanIntentusingtheMAINactionandtheActivityweareinterestedtolaunchasthecomponent,wecancreateacommandlineasfollows:

adb-semulator-5554shellamstart-aandroid.intent.action.MAIN-n

com.blundell.tut/.EspressoActivity

TheActivityisstartedasyoucanverifyintheemulator.Nowthenextthingtodowouldbetoinstallthetestprojectforourapplication,andthenusethecommandlinetorunthesetests(asdiscussedinpreviouschapters).Finally,whentheyarecompleted,weshoulduninstalltheapplication.Ifyoureadthecommandlistcarefully,youmayhavenoticedthatluckilythishasbeendoneforuswiththeconnectedAndroidTestGradletask.

Afterrunningthecommand,wewillobtainthetestsresults.Iftheypass,theoutputissimplyasfollows:

:app:connectedAndroidTest

BUILDSUCCESSFUL

Totaltime:9.812secs

Howeveriftheyfail,theoutputismoredetailedandalinktothefilewhereyoucanseethefullstacktraceandthereasonswhyeachtestfailedispresented:

:app:connectedAndroidTest

com.blundell.tut.ExampleEspressoTest>

testClickingButtonShowsImage[emulator-5554]FAILED

android.view.ViewRootImpl$CalledFromWrongThreadException:Onlytheoriginal

threadthatcreatedaviewhierarchycantouchitsviews.

atandroid.view.ViewRootImpl.checkThread(ViewRootImpl.java:6024)

FAILURE:Buildfailedwithanexception.

*Whatwentwrong:

Executionfailedfortask':app:connectedAndroidTest.

>Therewerefailingtests.Seethereportat:

file:///AndroidApplicationTestingGuide/app/build/outputs/reports/androidTes

ts/connected/index.html

BUILDFAILED

Totaltime:15.532secs.

Wehavedoneeverythingfromthecommandlinebyjustinvokingsomesimplecommands,whichiswhatwewerelookingforinordertofeedthisintoacontinuousintegrationprocess.

Git–thefastversioncontrolsystemGitisafreeandopensource,distributedversioncontrolsystemdesignedtohandleeverythingfromsmalltoverylargeprojectswithspeedandefficiency.ItisverysimpletosetupsoIstronglyrecommenditsuseevenforpersonalprojects.Thereisnoprojectsimpleenoughthatcouldnotbenefitfromtheapplicationofthistool.Youcanfindinformationanddownloadsathttp://git-scm.com/.

AversioncontrolsystemorVCS(alsoknownassourcecodemanagementorSCM)isanunavoidableelementfordevelopmentprojectswheremorethanonedeveloperisinvolvedandthebestpracticeevenifcodingsolo.Furthermore,eventhoughitispossibletoapplycontinuousintegrationwithnoVCSinplace(asaVCSisnotarequisiteofCI),itisnotareasonableorrecommendedpracticetoavoidit.

Otherandprobablymoretraditional(seelegacy),optionsexistintheVCSarenasuchasSubversionorCVS,whichyouarefreetouseifyoufeelmorecomfortable.Otherwise,GitisusedextensivelybytheAndroidprojecttohostGoogle’sowncodeandexamplessoitisworthinvestingsometimetoatleastunderstandthebasics.

Havingsaidthatandrememberingthatthisisaverybroadsubjecttojustifyabookinitself(andcertainlytherearesomegoodbooksaboutit),wearediscussingherethemostbasictopicsandsupplyingexamplestogetyoustartedifyouhaven’tembracedthispracticeyet.

CreatingalocalGitrepositoryThesearethesimplestpossiblecommandstocreatealocalrepositoryandpopulateitwiththeinitialsourcecodeforourprojects.InthiscaseweareagainusingtheAndroidApplicationTestingGuideprojectcreatedandusedinpreviouschapters.Wecopythecodeweusedintheprevioussection,wherewebuiltmanually:

$mkdirAndroidApplicationTestingGuide

$cdAndroidApplicationTestingGuide

$gitinit

$cp-a<path/to/original>/AndroidApplicationTestingGuide/

$gradlewclean

$rmlocal.properties

$gitadd.

$gitcommit-m"Initialcommit"

Wecreatethenewprojectdirectory,initializetheGitrepository,copytheinitialcontent,cleananddeleteourpreviousautogeneratedfiles,removethelocal.propertiesfile,addeverythingtotherepository,andcommit.

TipThelocal.propertiesfilemustneverbecheckedinaversioncontrolsystemasitcontainsinformationspecifictoyourlocalconfiguration.Youmightalsowanttolookatcreatinga.gitignorefile.Thisfileallowsyoutodefinewhatfilesarenotcheckedin(suchasauto-generatedfiles).Anexampleofthe.gitignorefilecanbefoundathttps://github.com/github/gitignore.

Atthispoint,wehaveourprojectrepositorycontainingtheinitialsourcecodeforourapplicationandallofitstests.Wehaven’talteredthestructuresotheprojectisstillcompatiblewithourIDEandGradleforwhenwecontinuedeveloping,locallybuilding,andcontinuouslyintegrating.

Thenextstepistohaveourprojectbuiltandtestedautomaticallyeverytimewecommitachangetothesourcecode.

ContinuousintegrationwithJenkinsJenkinsisanopensource,extensiblecontinuousintegrationserverthathastheabilitytobuildandtestsoftwareprojectsormonitortheexecutionofexternaljobs.Jenkinsiseasytoinstallandconfigure,andisthuswidelyused.Thatmakesitidealasanexampletolearncontinuousintegration.

InstallingandconfiguringJenkinsWementionedeasyinstallationasoneoftheadvantagesofJenkinsandinstallationcouldnotbeanyeasier.Downloadthenativepackagefortheoperatingsystemofyourchoicefromhttp://jenkins-ci.org/.Therearenativepackagesforallmajorserveranddesktopoperatingsystems.Inthefollowingexamples,wewillbeusingversion1.592.Wewillrunthe.warfileafterdownloadingit,sinceitdoesnotrequireadministrativeprivilegestodoso.

Oncefinished,copythewarintoaselecteddirectory,~/jenkins,andthenrunthefollowingcommand:

$java-jar~/jenkins/jenkins-1.592.war

ThisexpandsandstartsJenkins.

Thedefaultconfigurationusesport8080astheHTTPlistenerport,sopointingyourbrowserofchoicetohttp://localhost:8080shouldpresentyouwiththeJenkinshomepage.YoucanverifyandchangeJenkins’operatingparameterifrequired,byaccessingtheManageJenkinsscreen.WeshouldaddtothisconfigurationthepluginsneededforGitintegration,buildingwithGradle,checkingtestresults,andsupportforAndroidemulatorduringbuilds.ThesepluginsarenamedGitplugin,Gradleplugin,JUnitplugin,andAndroidEmulatorplugin,respectively.

ThisfollowingscreenshotdisplaystheinformationyoucanobtainaboutthepluginsfollowingthelinkavailableontheJenkinspluginadministrationpage:

AfterinstallingandrestartingJenkins,thesepluginswillbeavailableforuse.Ournextstepistocreatethejobsnecessarytobuildtheprojects.

CreatingthejobsLet’sstartbycreatingtheAndroidApplicationTestingGuidejobusingNewItemontheJenkinshomepage.Nameitaftertheproject.Differentkindsofjobscanbecreated;inthiscase,weselectFreestyleproject,allowingyoutoconnectanySCMwithanybuildsystem.

AfterclickingontheOKbutton,youwillbepresentedwiththespecificjoboptions,whicharedescribedinthefollowingtable.Thisisatthetopofthejobproperties’pageasfollows:

AlloftheoptionsintheNewItemscreenhaveahelptextassociated,sohereweareonlyexplainingtheonesweenter:

Option Description

Projectname Thenamegiventotheproject.

Description Optionaldescription.

DiscardOldBuilds Thishelpsyousaveondiskconsumptionbymanaginghowlongtokeeprecordsofthebuilds(suchasconsoleoutput,buildartifacts,andsoon).

ThisbuildisparameterizedThisallowsyoutoconfigureparametersthatarepassedtothebuildprocesstocreateparameterizedbuilds,forinstance,using$ANDROID_HOMEinsteadofhardcodingapath.

SourceCodeManagement

AlsoknownasVCS,whereisthesourcecodefortheproject?Inthiscase,weuseGitandarepositorywheretheURListheabsolutepathoftherepositorywecreatedearlier.Forexample,/git-repo/AndroidApplicationTestingGuide.

Howthisprojectisautomaticallybuilt.Inthiscase,wewanteverychangeinthesourcecodetotriggertheautomaticbuild,soweselectPollSCM.

TheotheroptionistouseBuildperiodically.Thisfeatureisprimarilyto

BuildTriggers

useJenkinsasacronreplacement,anditisnotidealtocontinuouslybuildsoftwareprojects.Whenpeoplefirststartcontinuousintegration,theyareoftensousedtotheideaofregularlyscheduledbuildssuchasnightly/weeklythattheyusethisfeature.However,thepointofcontinuousintegrationistostartabuildassoonasachangeismade,toprovideaquickfeedbacktothechange.

Thisoptioncanbeusedforlongerrunningbuildsliketestsuitesthatperhapstestperformancewhenthebuildrunsfor1hourforexample(configureittorunatmidnight).Italsocanbeusedtoreleasenewversions,nightly,orweekly.

Schedule

ThisfieldfollowsthesyntaxofCron(withminordifferences).Specifically,eachlineconsistsoffivefieldsseparatedbyTABorwhitespace:

MINUTEHOURDOMMONTHDOW.

Forexample,ifwewanttopollcontinuouslyat30minutespastthehour,specify:30****

Checkthedocumentationforacompleteexplanationofalltheoptions.

Buildenvironment ThisoptionletsyouspecifydifferentoptionsforthebuildenvironmentandfortheAndroidemulatorthatmayrunduringthebuild.

Build

Thisoptiondescribesthebuildsteps.WeselectInvokeGradlescriptaswereproducethestepswedidbeforetomanuallybuildandtesttheproject.

WewillselectUseGradleWrappersothatourprojectdoesn’trelyontheCIboxesbuiltintheGradleversion.

Then,intheTasksbox,wewanttowritecleanconnectedAndroidTest.

Postbuildactions

Theseareaseriesofactionswecandoafterthebuildisdone.WeareinterestedinsavingtheAPKssoweenableArchivetheartifactsandthendefinethepathforthemasFilestoarchive;inthisprecisecase,itis**/*-debug.apk.

Save Savesthechangeswejustmadeandcompletesthebuildjobcreation.

NowthatourCIbuildissetup,therearefollowingtwooptions:

YoucanforceabuildusingBuildNowOrintroducesomechangestothesourcecode,pushwithGit,andwaitforthemtobedetectedbyourpollingstrategy

Eitherway,wewillgetourprojectbuiltandourartifactsreadytobeusedforotherpurposes,suchasdependencyprojectsorQA.Unfortunately,ifyoudidruntheCIbuild,itwouldfailspectacularlyastherearenodevicesattached.Yourchoicesare,attacharealdeviceorusetheAndroidEmulatorpluginthatwejustinstalled.Let’susetheplugin.FromJenkins,gotothejobwejustcreatedandclickonConfigure.

Option Description

Ourintentionistoinstallandrunthetestsonanemulator.Soforourbuildenvironment,weusethefacilitiesprovidedbytheAndroidEmulatorPlugin.ThiscomesinhandyifyouwishtoautomaticallystartanAndroidemulatorofyourchoicebeforethebuildstepsexecute,withtheemulatorbeingstopped

Buildenvironment

afterthebuildingiscomplete.

Youcanchoosetostartapredefined,existingAndroidemulatorinstance(AVD).

Alternatively,theplugincanautomaticallycreateanewemulatoronthebuildslavewithpropertiesyouspecifyhere.

Inanycase,thelogcatoutputwillautomaticallybecapturedandarchived.

SelectRunemulatorwithproperties.

Then,select4.4fortheAndroidOSversion,320DPIfortheScreendensityandWQVGAforScreenresolution.

Feelfreetoexperimentandselecttheoptionsthatbettersuityourneeds.

Commonemulatoroptions

WewouldliketoResetemulatorstateatstart-uptowipeuserdataanddisableShowemulatorwindow,sotheemulatorwindowisnotdisplayed.

Afterconfiguringandbuildingthisproject,wehavetheAPKinstalledonthetargetemulatorandthetestsarerunning.

ObtainingAndroidtestresultsOncethetestsarerun,theresultsaresavedasXMLfilesinsidetheproject’sbuildfolderat/AndroidApplicationTestingGuides/app/build/outputs/androidTest-results/connected/.

Theyarenogoodtousthere.ItwouldbeniceifwecouldreadtheresultsofourtestsinJenkinsandhavethemdisplayedinaniceHTMLformat;anotherJenkinsplugintotherescue.JUnitPluginenablesapostbuildactionthatasksyouwhereyourJUnitreportsarestoredandwillthenretrievethemforeasyviewingintheprojectscreenofJenkinsastestresults.Inthisscenario,weusethePost-buildActionsalsointhejobconfiguration’spage.

Havingdoneallofthestepspreviouslydescribed,onlyforcingabuildislefttoseetheresults.Option

Description

PublishJUnittestresultreport

Whenthisoptionisconfigured,theJUnitpluginonJenkinscanprovideusefulinformationabouttestresults,suchashistoricaltestresulttrends,awebUItoviewtestreports,trackingfailures,andsoon.

ItrequiresaregextolookuptheJUnitresultfiles.Iwouldrecommend**/TEST*.xml.ThisregexshouldmatchallJUnittestresults,includingthoseoftheAndroidconnectedtests;praiseinresearchheregoestoAdamBrown.Ifyouchangetheregex,besurenottoincludeanynon-reportfilesintothispattern.

Onceafewbuildshaverunwithtestresults,youshouldstartseeingsometrendchartsdisplayingtheevolutionoftests.

ClickonBuildNowandafterafewmoments,youwillseeyourtestresultsandstatisticsdisplayedinasimilarwayasthefollowingscreenshotdepicts:

Fromhere,wecaneasilyunderstandourprojectstatus.ClickingonLatestTestResultshowsyouhowmanytestsfailedandwhy.YoucansearchthroughthefailedtestsandcanalsofindtheextensiveErrormessageandStacktraceoptions.

Itisalsoreallyhelpfultounderstandtheevolutionofaprojectthroughtheevaluationof

differenttrendsandJenkinsisabletoprovidesuchinformation.Everyprojectpresentsthecurrenttrendsusingweather-likeiconsfromsunny,whenthehealthoftheprojectincreasesby80percent,andtothunderstormwhenthehealthliesbelow20percent.Inaddition,foreveryproject,theevolutionofthetrendofthetestssuccessversusfailureratioisdisplayedinachart.Afailingtestchartisreproducedhere:

Inthiscase,wecanseehowatbuild9,fourtestswherebroken,threewherefixedinbuild10,andthefinaloneinbuild11.

Toseehowaprojectstatuschangesbyforcingafailure,let’saddafailingtestsuchasthefollowing.Don’tforgettopushyourcommittotriggertheCIbuildasfollows:

publicfinalvoidtestForceFailure(){

fail("failtestisfail");

}

YetanotherveryinterestingfeaturethatisworthmentioningistheabilityofJenkinstokeepanddisplaythetimelineandbuildthetimetrend,asshowninthefollowingscreenshot:

Thispagepresentsthebuildhistorywithlinkstoeveryparticularbuildthatyoucanfollowtoseethedetails.Nowwehavelesstobeworriedaboutandeverytimesomebodyinthedevelopmentteamcommitschangestotherepository,weknowthatthesechangeswillbeimmediatelyintegratedandthewholeprojectwillbebuiltandtested.IfwefurtherconfigureJenkins,wecanevenreceivethestatusbye-mail.Toachievethis,enableE-mailNotificationinthejobconfigurationpageandenterthedesiredRecipients.

SummaryThischapterintroducedcontinuousintegrationinpracticeprovidingvaluableinformationtostartapplyingitsoontoyourprojectsnomatterwhattheirsize,whetheryouaredevelopingsoloorapartofabigcompanyteam.

ThetechniquespresentedfocusontheparticularitiesofAndroidprojectsmaintainingandsupportingwidelyuseddevelopmenttoolssuchasAndroidStudioandtheAndroidGradleplugin.

Weintroducedreal-worldexampleswithreal-worldtoolsavailablefromthevastopensourcearsenal.WeemployedGradletoautomatethebuildingprocess,Gittocreateasimpleversioncontrolsystemrepositorytostoreoursourcecodeandmanagethechanges,andfinally,installedandconfiguredJenkinsasthecontinuousintegrationofchoice.

WithinJenkins,wedetailedthecreationofjobstoautomatethecreationofourAndroidapplicationanditstests,andweemphasizedtherelationshipbetweenthecontinuousintegrationboxanditsdevices/emulators.

Finally,webecameawareoftheAndroid-connectedtestsresultsandimplementedastrategytoobtainanattractiveinterfacetomonitortherunningoftests,theirresults,andtheexistingtrends.

ThenextchaptertakesusthroughtheroadofTest-drivenDevelopment;you’llfinallystarttounderstandwhyIkeeptalkingaboutthetemperatureinalltheexamplessofarwithareal-worldproject.Thus,havingacontinuousintegrationsetupisperfecttoempowerustowritegreatcodeandhavefaithinourCIbuiltAPKsbeingreadytorelease.

Chapter6.PracticingTest-drivenDevelopmentThischapterintroducestheTest-drivenDevelopment(TDD)discipline.WewillstartwithTDDpracticesinthegeneralsense,andlateronmovetotheconceptsandtechniquesmorecloselyrelatedtotheAndroidplatform.

Thisisacode-intensivechapter,sobepreparedtotypeasyouread,whichwouldhelpyougetthemostoutoftheexamplesprovided.

Inthischapter,wewilllearnthefollowingtopics:

IntroducingandexplainingTest-drivenDevelopmentAnalyzingitsadvantagesIntroducingareal-lifeexampleUnderstandingprojectrequirementsbywritingtestsEvolvingthroughtheprojectbyapplyingTDDGettinganapplicationthatfullycomplieswiththerequirements

GettingstartedwithTDDBriefly,Test-drivenDevelopmentisthestrategyofwritingtestsinparallelwiththedevelopmentprocess.Thesetestcasesarewritteninadvanceofthecodethatissupposedtosatisfythem.

Asingletestiswritten,andthenthecodeneededtosatisfythecompilationofthistestiswritten,thenthebehaviorthatthetestdecreesshouldexistiswritten.Wecontinuewritingtestsandimplementationuntilthefullsetofdesiredbehaviorsischeckedbythetests.

Thiscontrastswithotherapproachestothedevelopmentprocess,wherethetestsarewrittenattheendwhenallthecodinghasbeendone.

Writingthetestsinadvanceofthecodethatsatisfiesthemhasthefollowingadvantages:

Testsgetwrittenonewayoranother,whileifthetestsarelefttilltheenditishighlyprobablethattheywillneverbewrittenDeveloperstakemoreresponsibilityforthequalityoftheirwork,whenhavingtoconsiderthetestsastheycode

Designdecisionsaretakeninsmallerstepsandafterwardsthecodesatisfyingthetestsisimprovedbyrefactoring.Remember,thisiswhilehavingthetestsrunning,sothattherearenoregressionsinexpectedbehavior.

Test-drivenDevelopmentisoftenexplainedinadiagramlikethefollowing,tohelpusunderstandtheprocess:

ThefollowingsectionsexpandontheindividualactionsassociatedwiththeTDD,red,

green,refactorcycle.

WritingatestcaseWestartourdevelopmentprocesswithwritingatestcase.Thisapparentlyisasimpleprocessthatwillputsomemachinerytoworkinsideourheads.Afterall,itisnotpossibletowritesomecode,testitornot,ifwedon’thaveaclearunderstandingoftheproblemdomain,anditsdetails.Usually,thisstepwillgetyoufacetofacewiththeaspectsoftheproblemyoudon’tunderstand,andyouneedtograsptheseifyouwanttomodelandwritethecode.

RunningalltestsOncethetestiswrittenthenextstepistorunit,alongwithallothertestswehavewrittensofar.Here,theimportanceofanIDEwithbuilt-insupportofthetestingenvironmentisperhapsmoreevidentthaninothersituations,cuttingthedevelopmenttimebyagoodfraction.Itisexpectedthat,firstly,ournewlywrittentestfailsaswestillhaven’twrittenanycode.

Tobeabletocompleteourtest,wewriteadditionalcodeandtakedesigndecisions.Theadditionalcodewrittenistheminimumpossibletogetourtesttocompile.Considerhere,thatnotcompilingisfailing.

Whenwegetthetesttocompileandrun,andifthetestfails,thenwetrytowritetheminimumamountofcodenecessarytomakethetestsucceed.Thismaysoundawkwardatthispoint,butthefollowingcodeexampleinthischapterwillhelpyouunderstandtheprocess.

Optionally,insteadofrunningalltestsagainyoucanjustrunthenewlyaddedtestsfirst,tosavesometimeassometimesrunningthetestsontheemulatorcouldberatherslow.Thenrunthewholetestsuitetoverifythateverythingisstillworkingproperly.Wedon’twanttoaddanewfeaturebybreakinganyfeaturesalreadyexistinginourcode.

RefactoringthecodeWhenthetestsucceeds,werefactorthecodeaddedtokeepittidy,clean,andtheminimalneededforamaintainableandextensibleapplication.

Werunallthetestsagain,toverifythatourrefactoringhasnotbrokenanything,andifthetestsareagainsatisfiedandnomorerefactoringisneeded,wefinishourtask.

Runningthetestsafterrefactoringisanincrediblesafetynetthathasbeenputinplacebythismethodology.Ifwemadeamistakerefactoringanalgorithm,extractingvariables,introducingparameters,changingsignatures,orwhatevertherefactoringmechanism,thistestinginfrastructurewilldetecttheproblem.Furthermore,ifsomerefactoringoroptimizationcouldnotbevalidforeverypossiblecase,wecanverifyitforeverycaseusedbytheapplicationexpressingthisasatestcase.

AdvantagesofTDDPersonally,themainadvantageI’veseensofaristhatitquicklyfocusesyouonyourprogramminggoal,anditishardertogetdistractedoreager,andimplementoptionsinyoursoftwarethatwillneverbeused(sometimesknownasgoldplating).Thisimplementationofunneededfeaturesisawasteofyourpreciousdevelopmenttimeandasyoumayalreadyknow,judiciouslyadministeringtheseresourcesmaybethedifferencebetweensuccessfullyreachingtheendoftheprojectornot.

Theotheradvantageisthatyoualwayshaveasafetynetforyourchanges.Everytimeyouchangeapieceofcode,youcanbeabsolutelysurethatotherpartsofthesystemarenotaffected,aslongastherearetestsverifyingthattheconditionshaven’tchanged.

Don’tforget,TDDcannotbeindiscriminatelyappliedtoanyproject.Ithinkthat,aswellasanyothertechnique;youshoulduseyourjudgmentandexpertisetorecognizewhereitcanbeappliedandwherenot.Alwaysremember:therearenosilverbullets.

UnderstandingtherequirementsTobeabletowriteatestaboutanysubject,weshouldfirstunderstandthesubjectundertest,thismeansbreakingaparttherequirementyouareattemptingtoimplement.

Wementionedthatoneoftheadvantagesisthatyoufocusuponagoalquickly,insteadofrevolvingaroundtherequirementsasabig,unconquerablewhole.

Translatingrequirementsintotestsandcrossreferencingthemisperhapsthebestwaytounderstandtherequirements,andtobesurethatthereisalwaysanimplementationandverificationforallofthem.Also,whentherequirementschange(somethingthatisveryfrequentinsoftwaredevelopmentprojects),wecanchangethetestsverifyingtheserequirements,andthenchangetheimplementationtobesurethateverythingwascorrectlyunderstoodandmappedtothecode.

Creatingasampleproject–thetemperatureconverterYoumighthaveguesseditfromsomeofthecodesnippetssofar,thatourTDDexampleswillrevolvearoundanextremelysimpleAndroidsampleproject.Itdoesn’ttrytoshowallthefancyAndroidfeatures,butfocusesontestingandgraduallybuildingtheapplicationfromthetest,applyingtheconceptslearnedbefore.

Let’spretendthatwehavereceivedalistofrequirementstodevelopanAndroidtemperatureconverterapplication.Thoughoversimplified,wewillbefollowingthestepsyounormallywould,todevelopsuchanapplication.However,inthiscasewewillintroducetheTest-drivenDevelopmenttechniquesintheprocess.

ListofrequirementsUsually(let’sbehonest),thelistofrequirementsareveryvague,andthereareahighnumberofdetailsnotfullycovered.

Asanexample,let’spretendthatwereceivethislist:

TheapplicationconvertstemperaturesfromCelsiustoFahrenheitandvice-versaTheuserinterfacepresentstwofieldstoenterthetemperatures;oneforCelsiustheotherforFahrenheitWhenatemperatureisenteredinonefield,theotheroneisautomaticallyupdatedwiththeconversionIfthereareerrors,theyshouldbedisplayedtotheuser,possiblyusingthesamefieldsSomespaceintheuserinterfaceshouldbereservedfortheon-screenkeyboard,toeasetheapplicationoperationwhenseveralconversionsareenteredEntryfieldsshouldstartemptyValuesenteredaredecimalvalueswithtwodigitsafterthepointDigitsarerightalignedLastenteredvaluesshouldberetainedevenaftertheapplicationispaused

UserinterfaceconceptdesignLet’sassumethatwereceivethisconceptualuserinterfacedesignfromtheuserinterfacedesignteam(Iapologizerightnowtoalldesignersformylackofimaginationandskill):

CreatingtheprojectOurfirststepistocreatetheproject.Now,sincewehavedonethisforfivechaptersalreadyIdon’tthinkIneedtogiveyouastep-by-stepguide.JustrunthroughtheAndroidStudionewprojectwizard,andselectanAndroidmobileprojectwithyourpackagename,plusotherboilerplate,andnoActivitytemplate.AndroidStudiowillautomaticallycreateyouanexampleAndroidApplicationTestCase.Bearinmind,ifyougetstuck,youcanrefertothecodeaccompanimentforthisbook.Whencreated,itshouldlooksomethinglikethis:

Nowlet’squicklycreateanewActivitycalledTemperatureConverterActivity(wedidn’tusethetemplategenerator,becauseitaddsloadsofcodethatisnotneededrightnow),don’tforgettoaddtheActivitytoyourAndroidManifestfile.FanaticTDD’ersmightbeshakingtheirfistrightnow,asreallyyoushouldmakethisActivityonlywhenneededinyourtests,butI’mtryingtoguideyouwithsomefamiliarityatthesametime.

CreatingaJavamoduleOntopofthistemplateproject,wewanttoaddanothermoduleofcode.ThiswillbeaJava-onlymoduleandwillactasadependencyorlibrary,ifyouwill,forourmainAndroidmodule.

Theideahereistwo-fold.First,itallowsyoutoseparatecodethatisJavaonly,anddoesnothaveadependencyonAndroid,inabigprojectthiscanbeyourcoredomain;thebusinesslogicthatrunsyourapp,anditisimportantthatyoumodularizethis,soyoucanworkonitwithouthavingtothinkaboutAndroidaswell.

Secondly,havingaJava-onlymoduleaswe’vesaidbefore,allowsyoutocallonthevasthistoryofJavaasanestablishedprogramminglanguagewhenitcomestotesting.TestingoftheJavamoduleisfast,simple,andeasy.YoucanwriteJUnittestsfortheJVMandhavethemrunninginmilliseconds(whichwewilldo!).

FromAndroidStudio,navigatetoFile|NewModule,thisgivesyoutheCreatenewmoduledialog.UnderMoreModules,selectJavaLibrary,andhitNext.Nameyourlibrarycore,andensurethepackagenameisthesameasyourAndroidapplication,andpressonFinish.Thelastscreenshouldhavelookedsomethinglikethis:

Oncecreated,youneedtoaddtheone-waydependencyfromyourAndroid:appmoduletothe:coremodule.Within,/app/build.gradle,addthedependencyoncore:

dependencies{

compilefileTree(dir:'libs',include:['*.jar'])

compile'com.android.support:appcompat-v7:21.0.2'

compileproject(':core')

}

ThisallowsustoreferencefilesfromourcoremoduleinsideofourAndroidapplication.

CreatingtheTemperatureConverterActivityTestsclassProceedwithcreatingthefirsttestbyselectingthemaintestpackagename,com.blundell.tut.Thisisundersrc/androidTest/JavaintheAndroidStudioprojectview,orunderapp/java/(androidTest)intheAndroidStudioAndroidview.Thenright-clickhere,andselectNew|JavaClass,callit,TemperatureConverterActivityTests.

Oncetheclassiscreated,weneedtogoaboutturningitintoatestclass.Weshouldselectoursuperclassdependingonwhatandhowwearegoingtotest.InChapter2,UnderstandingTestingwiththeAndroidSDK,wereviewedtheavailablealternatives.Useitasareferencewhenyoutrytodecidewhatsuperclasstouse.

Inthisparticularcase,wearetestingasingleActivityandusingthesysteminfrastructure,therefore,weshoulduseActivityInstrumentationTestCase2.AlsonotethatasActivityInstrumentationTestCase2isagenericclass,weneedthetemplateparameteraswell.ThisistheActivityundertest,whichinourcase,isTemperatureConverterActivity.

Wenownoticethatourclasshassomeerrorsweneedtofixbeforerunning.Otherwisetheerrorswillpreventthetestfromrunning.

TheproblemweneedtofixhasbeendescribedbeforeinChapter2,UnderstandingTestingwiththeAndroidSDK,undertheTheno-argumentconstructorsection.Asthispatterndictates,weneedtoimplement:

publicTemperatureConverterActivityTests(){

this("TemperatureConverterActivityTests");

}

publicTemperatureConverterActivityTests(Stringname){

super(TemperatureConverterActivity.class);

setName(name);

}

Sofarwehaveperformedthefollowingsteps:

WeaddedthenoargumentconstructorTemperatureConverterActivityTests().Fromthisconstructor,weinvoketheconstructorthattakesanameasaparameter.Finally,inthisgivennameconstructor,weinvokethesuperconstructorandsetthename.

Toverifythateverythinghasbeensetupandisinplace,youmayrunthetestsbyrightclickingontheclass,andselectingRun|TheNameoftheTestClass.Therearenoteststorunyet,butatleastwecanverifythattheinfrastructuresupportingourtestsisalreadyinplace.ItshouldfailwithaNotestsfoundwarning.Hereishowtorunthetestclass,incaseyoumissedit:

CreatingthefixtureWecanstartcreatingourtestfixturebypopulatingthesetup()methodwiththeelementsweneedinourtests.Almostunavoidable,inthiscase,istheuseoftheActivityundertest,solet’sprepareforthesituationandaddittothefixture:

@Override

publicvoidsetUp()throwsException{

super.setUp();

activity=getActivity();

}

Afterintroducingthepreviouscode,createtheactivityfieldusingAndroidStudio’srefactoringtoolstosaveyoutime.(F2fornexterror,Alt+Enterforquickfix,Enteragaintocreatefield,Enteragaintoconfirmthefieldstype,done!)

TheActivityInstrumentationTestCase2.getActivity()methodhasasideeffect.IftheActivityundertestisnotrunning,itwillbestarted.ThismaychangetheintentionofatestifweusegetActivity()asasimpleaccessorseveraltimesinatest,andforsomereasontheActivityfinishesorcrashesbeforetestcompletion.WewillbeinadvertentlyrestartingtheActivity,thatiswhyinourtestswediscouragetheuseofgetActivity()infavorofhavingitinthefixture,sothatweareimplicitlyrestartingtheactivityforeverytest.

CreatingtheuserinterfaceBacktoourTest-drivenDevelopmenttrack,weseefromourconciselistofrequirementsthattherearetwoentriesforCelsiusandFahrenheittemperatures,respectively.Solet’saddthemtoourtestfixture.

Theydon’texistyet,andwehaven’tevenstarteddesigningtheuserinterfacelayout,butweknowthatthereshouldbetwoentriesliketheseforsure.

ThisisthecodeyoushouldaddtothesetUp()method:

celsiusInput=(EditText)

activity.findViewById(R.id.converter_celsius_input);

fahrenheitInput=(EditText)

activity.findViewById(R.id.converter_fahrenheit_input);

Therearesomeimportantthingstonotice:

Wechoosethenamesconverter_celsius_inputbecause,converter_isthelocationofthisfield(intheTemperatorConverterActivity)celsius_iswhatthefieldrepresents,andfinallyinputishowthefieldsbehaveWedefinethefieldsforourfixtureusingEditTextWeusethepreviouslycreatedActivitytofindtheViewsbyIDWeusetheRclassforthemainprojecteventhoughtheseIDsdonotexist

TestingtheexistenceoftheuserinterfacecomponentsOncewehaveaddedthemtothesetUp()method,asindicatedintheprevioussection,wecanwriteourfirsttestandchecktheviewsexistence:

publicfinalvoidtestHasInputFields(){

assertNotNull(celsiusInput);

assertNotNull(fahrenheitInput);

}

Wearenotabletorunthetestsyetbecausewemustfixsomecompilationproblemsfirst.WeshouldfixthemissingIDsintheRclass.

HavingcreatedourtestfixturethatreferenceselementsandIDsintheuserinterfacethatwedon’thaveyet,it’smandatedbytheTest-drivenDevelopmentparadigmthatweaddtheneededcodetosatisfyourtests.Thefirstthingweshoulddoisgetthetesttocompile,soifwehavesometeststestingunimplementedfeatures,theywillfail.

GettingtheIDsdefinedOurfirststopwouldbetohavetheIDsfortheuserinterfaceelementsdefinedintheRclass,sotheerrorsgeneratedbyreferencingundefinedconstantsR.id.converter_celsius_inputandR.id.converter_fahrenheit_inputgoaway.

You,asanexperiencedAndroiddeveloper,willknowhowtodoit.I’llgiveyouarefresheranyway.Createanactivity_temperature_converter.xmllayoutinthelayouteditor,andaddtherequireduserinterfacecomponentstogetsomethingthatresemblesthedesignpreviouslyintroducedintheUserInterfaceconceptdesignsection,asshowninthefollowingcode:

<?xmlversion="1.0"encoding="utf-8"?>

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

<TextView

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_marginBottom="@dimen/margin"

android:text="@string/message"/>

<<TextView

android:id="@+id/converter_celsius_label"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginStart="@dimen/margin"

android:text="@string/celsius"/>

<EditText

android:id="@+id/converter_celsius_input"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="@dimen/margin"/>

<TextView

android:id="@+id/converter_fahrenheit_label"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginStart="@dimen/margin"

android:text="@string/fahrenheit"/>

<EditText

android:id="@+id/converter_fahrenheit_input"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="@dimen/margin"/>

</LinearLayout>

Doingso,wegetourteststocompile(don’tforgettoaddthestringsanddimensions),runthetests,dotheypass?Theyshouldn’t!Youneedtohookupyournewactivitylayout(I

betyoubeatmetoit):

publicclassTemperatureConverterActivityextendsActivity{

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_temperature_converter);

}

}

Runthetestsoncemore,andyoushouldgetthefollowingresult:

ThetestHasInputFieldstestsucceededEverythingisgreennow

Theoutputofthetestisseenasfollows:

ThisclearlymeansthatweareontrackwithapplyingTDD.

Youmayalsohavenoticedthatweaddedsomedecorativeandnon-functionalitemstoouruserinterfacethatwearenottesting(saypadding),mainlytokeepourexampleassimpleaspossible.Inareal-casescenario,youmaywanttoaddtestsfortheseelementstoo.

TranslatingrequirementstotestsTestshaveadoublefeature.Theyverifythecorrectnessofourcodebut,sometimes,andmoreprominentlyinTDD,theyhelpusunderstandthedesignanddigestwhatweareimplementing.Tobeabletocreatethetests,weneedtounderstandtheproblemwearedealingwith,andifwedon’t,weshouldatleasthavearoughoutlineoftheproblemtoallowustostarttohandleit.

Manytimes,therequirementsbehindtheuserinterfacearenotclearlyexpressed,butyoushouldbeabletounderstandthemfromthewireframedUIdesign.Ifwepretendthatthisisthecase,thenwecangraspthedesignbywritingourtestsfirst.

EmptyfieldsFromoneofourrequirements,weget:Entryfieldsshouldstartempty.

Toexpressthisinatest,wecanwritethefollowing:

publicvoidtestFieldsShouldStartEmpty(){

assertEquals("",celsiusInput.getText().toString());

assertEquals("",fahrenheitInput.getText().toString());

}

Here,wesimplycomparetheinitialcontentsofthefieldsagainsttheemptystring.

Thistestpassesstraightaway,great!AlthoughatenantofTDDalwaysstartswitharedtest,youmightwanttodoaquicksanitycheck,andaddsometexttotheXMLforEditTextandrunthetests,andwhenitgoesredandgreenagainwhenyouremovetheaddedtext,youknowyourtestisvalidatingthebehavioryouexpect(anditwasn’tgreenasasideeffectofsomethingyoudidnotexpect).Wesuccessfullyconvertedonerequirementtoatest,andvalidateditbyobtainingthetestresults.

ViewpropertiesIdentically,wecanverifyotherpropertiesoftheViewscomposingourlayout.Amongotherthings,wecanverify:

Fields(appearonthescreenasexpected)FontsizesMarginsScreenalignment

Let’sstartverifyingthatthefieldsareonthescreen:

publicvoidtestFieldsOnScreen(){

Vieworigin=activity.getWindow().getDecorView();

assertOnScreen(origin,celsiusInput);

assertOnScreen(origin,fahrenheitInput);

}

Asexplainedbefore,weuseanassertfromhere:ViewAsserts:assertOnScreen.

NoteStaticimportsandhowtomakethemostofthemwasexplainedinChapter2,UnderstandingTestingwiththeAndroidSDK.Ifyouhaven’tdoneitbefore,nowisthetime.

TheassertOnScreen()methodneedsanorigintostartlookingfortheotherViews.Inthiscase,becausewewanttostartfromthetop-mostlevel,weusegetDecorView(),whichretrievesthetop-levelwindowviewcontainingthestandardwindowframeanddecorations,withtheclient’scontentinside.

Byrunningthistest,wecanensurethattheentryfieldsareonthescreen,astheUIdesigndictates.Insomeway,wealreadyknewthatViews,withthesespecificIDs,existed.Thatis,wemadethefixturecompilebyaddingtheViewstothemainlayout,butwewerenotsuretheywereappearingonthescreenatall.So,nothingelseisneededbutthesolepresenceofthistest,toensurethattheconditionisnotchangedinthefuture.Ifweremoveoneofthefieldsforsomereason,thistestwilltellusthatitismissing,andnotcomplyingwiththeUIdesign.

Followingwithourlistofrequirements,weshouldtestthattheViewsarealignedinthelayoutasweexpect:

publicvoidtestAlignment(){

assertLeftAligned(celsiusLabel,celsiusInput);

assertLeftAligned(fahrenheitLabel,fahrenheitInput);

assertLeftAligned(celsiusInput,fahrenheitInput);

assertRightAligned(celsiusInput,fahrenheitInput);

}

WecontinueusingassertsfromViewAssert—inthiscase,assertLeftAlignedandassertRightAligned.ThesemethodsverifythealignmentofthespecifiedViews.TogetthistestrunningwehavetoaddthetwolookupsforthelabelTextView’sinthesetUp()method:

celsiusLabel=(TextView)

activity.findViewById(R.id.converter_celsius_label);

fahrenheitLabel=(TextView)

activity.findViewById(R.id.converter_fahrenheit_label);

TheLinearLayoutclassweareusingbydefaultarrangesthefieldsinthewayweareexpectingthem.Again,whilewedon’tneedtoaddanythingtothelayout,tosatisfythetest,thiswillactasaguardcondition.

Oncewe’veverifiedthattheyarecorrectlyaligned,weshouldverifythattheyarecoveringthewholescreenwidth,asspecifiedbytheschematicdrawing.Inthisexample,it’ssufficienttoverifyLayoutParamshavingthecorrectvalues:

publicvoidtestCelciusInputFieldCoversEntireScreen(){

LayoutParamslp;

intexpected=LayoutParams.MATCH_PARENT;

lp=celsiusInput.getLayoutParams();

assertEquals("celsiusInputlayoutwidthisnotMATCH_PARENT",

expected,lp.width);

}

publicvoidtestFahrenheitInputFieldCoversEntireScreen(){

LayoutParamslp;

intexpected=LayoutParams.MATCH_PARENT;

lp=fahrenheitInput.getLayoutParams();

assertEquals("fahrenheitInputlayoutwidthisnotMATCH_PARENT",

expected,lp.width);

}

Weusedacustommessagetoeasilyidentifytheproblem,incasethetestfails.

Byrunningthistest,weobtainthefollowingmessageindicatingthatthetestfailed:AssertionFailedError:celsiusInputlayoutwidthisnotMATCH_PARENTexpected:<-1>butwas:<-2>.

Thisleadsustothelayoutdefinition.Wemustchangelayout_widthtobematch_parentfortheCelsiusandFahrenheitfields:

<EditText

android:id="@+id/converter_celsius_input"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="@dimen/margin"

android:gravity="end|center_vertical"/>

SameforFahrenheit—afterthechangeisdone,werepeatthecycle,andbyrunningthetestagain,wecanverifythatitisnowsuccessful.

Ourmethodisstartingtoappear.Wecreatethetesttoverifyaconditiondescribedintherequirements.Ifit’snotmet,wechangethecauseoftheproblem,andrunningthetestsagain,weverifythatthelatestchangesolvestheproblem,andwhatisperhapsmoreimportantisthatthechangedoesn’tbreaktheexistingcode.

Next,let’sverifythatthefontsizesaredefinedasperourrequirements:

publicvoidtestFontSizes(){

floatpixelSize=24f;

assertEquals(pixelSize,celsiusLabel.getTextSize());

assertEquals(pixelSize,fahrenheitLabel.getTextSize());

}

Retrievingthefontsizeusedbythefieldisenoughinthiscase.

Thedefaultfontsizeisnot24px,soweneedtoaddthistoourlayout.It’sagoodpracticetoaddthecorrespondingdimensiontoaresourcefile,andthenuseitwhereit’sneededinthelayout.So,let’saddlabel_text_sizetores/values/dimens.xml,withavalueof24sp.ThenreferenceitintheTextsizepropertyofthelabels,celsius_labelandfahrenheit_label.

Now,thetestmaypassoritmaynot,dependingontheresolutionofyourdeviceoremulatoryouareusing.Thisisbecauseweareassertinginthetest,thepixelsize,butwehavedeclaredinthedimens.xml,tousesp(scaleindependentpixels).Let’shardenthis

test.Toresolvethiswecouldeitherconvertourpxtospinthetestclass,orusethespvalueinthetest.Ihavechosentousespinthetest,althoughyoucouldargueforeither:

publicvoidtestFontSizes(){

floatpixelSize=getFloatPixelSize(R.dimen.label_text_size);

assertEquals(pixelSize,celsiusLabel.getTextSize());

assertEquals(pixelSize,fahrenheitLabel.getTextSize());

}

privatefloatgetFloatPixelSize(intdimensionResourceId){

returngetActivity().getResources()

.getDimensionPixelSize(dimensionResourceId);

}

Finally,let’sverifythatmarginsareinterpretedasdescribedintheuserinterfacedesign:

publicvoidtestCelsiusInputMargins(){

LinearLayout.LayoutParamslp=

(LinearLayout.LayoutParams)celsiusInput.getLayoutParams();

assertEquals(getIntPixelSize(R.dimen.margin),lp.leftMargin);

assertEquals(getIntPixelSize(R.dimen.margin),lp.rightMargin);

}

publicvoidtestFahrenheitInputMargins(){

LinearLayout.LayoutParamslp=

(LinearLayout.LayoutParams)fahrenheitInput.getLayoutParams();

assertEquals(getIntPixelSize(R.dimen.margin),lp.leftMargin);

assertEquals(getIntPixelSize(R.dimen.margin),lp.rightMargin);

}

Thisisasimilarcaseasbefore(I’veskippedthestepoftestingtherawpixelvalue).Weneedtoaddthemargintoourlayout.Let’saddthemargindimensiontotheresourcefile,andthenuseitwhereit’sneededinthelayout.Setthemargindimensioninres/values/dimens.xmltoavalueof8dp.Then,referenceitinthelayout_margin_startpropertyofbothfields,celsiusandfahrenheit,andalsointhestartmarginofthelabels.

Thehelpermethodtogettheintegerpixelsizefromaresourcedimen,justwrapsthefloatmethodalreadydiscussed:

privateintgetIntPixelSize(intdimensionResourceId){

return(int)getFloatPixelSize(dimensionResourceId);

}

Onemorethingthatisleftistoverifythejustification(alignment)oftheenteredvalues.Wewillvalidatetheinputshortly,toallowonlythepermittedvalues,butfornowlet’sjustpayattentiontothejustification.Theintentionistohavevaluesthataresmallerthanthewholefield,justifiedtotherightandverticallycentered:

publicvoidtestCelsiusInputJustification(){

intexpectedGravity=Gravity.END|Gravity.CENTER_VERTICAL;

intactual=celsiusInput.getGravity();

StringerrorMessage=String.format(

"Expected0x%02xbutwas0x%02x",expectedGravity,actual);

assertEquals(errorMessage,expectedGravity,actual);

}

publicvoidtestFahrenheitInputJustification(){

intexpectedGravity=Gravity.END|Gravity.CENTER_VERTICAL;

intactual=fahrenheitInput.getGravity();

StringerrorMessage=String.format(

"Expected0x%02xbutwas0x%02x",expectedGravity,actual);

assertEquals(errorMessage,expectedGravity,actual);

}

Here,weverifythegravityvaluesasusual.However,weareusingacustommessagetohelpusidentifythevaluesthatcouldbewrong.AstheGravityclassdefinesseveralconstantswhosevaluesarebetteridentifiedifexpressedinhexadecimal,weareconvertingthevaluestothisbaseinthemessage.

Ifthistestisfailingduetothedefaultgravityusedforthefields,thenwhatisonlyleftistochangeit.Gotothelayoutdefinitionandalterthesegravityvalues,sothatthetestsucceeds.

Thisispreciselywhatweneedtoadd:

android:gravity="end|center_vertical"

ScreenlayoutWenowwanttoverifythattherequirementspecifyingthatenoughscreenspaceshouldbereservedtodisplaythekeyboard,isactuallyfulfilled.

Wecanwriteatestlikethis:

publicvoidtestVirtualKeyboardSpaceReserved(){

intexpected=getIntPixelSize(R.dimen.keyboard_space);

intactual=fahrenheitInput.getBottom();

StringerrorMessage=

"Spacenotreserved,expected"+expected+"actual"+actual;

assertTrue(errorMessage,actual<=expected);

}

Thisverifiesthattheactualpositionofthelastfieldinthescreen,whichisfahrenheitInput,isnotlowerthanasuggestedvalue.

Wecanrunthetestsagainverifyingthateverythingisgreenagain.Runupyourapplication,andyoushouldhaveacompleteuserinterfacebackedbytests,asshowninthefollowingscreenshot:

AddingfunctionalityTheuserinterfaceisinplace.Now,wecanstartaddingsomebasicfunctionality.Thisfunctionalitywillincludethecodetohandletheactualtemperatureconversion.

TemperatureconversionFromthelistofrequirements,wecanobtainthisstatement:Whenonetemperatureisenteredinonefield,theotheroneisautomaticallyupdatedwiththeconversion.

Followingourplan,wemustimplementthisasatesttoverifythatthecorrectfunctionalityisthere.Ourtestwouldlooksomethinglikethis:

@UiThreadTest

publicvoidtestFahrenheitToCelsiusConversion(){

celsiusInput.clear();

fahrenheitInput.clear();

fahrenheitInput.requestFocus();

fahrenheitInput.setText("32.5");

celsiusInput.requestFocus();

doublef=32.5;

doubleexpectedC=TemperatureConverter.fahrenheitToCelsius(f);

doubleactualC=celsiusInput.getNumber();

doubledelta=Math.abs(expectedC-actualC);

Stringmsg=""+f+"F->"+expectedC+"Cbutwas"

+actualC+"C(delta"+delta+")";

assertTrue(msg,delta<0.005);

}

Let’srunthroughthisstep-by-step:

1. Firstly,aswealreadyknow,tointeractwiththeUIchangingitsvaluesweshouldrunthetestontheUIthread,andthusbecauseweuseEditText.setText,thetestisannotatedwith@UiThreadTest.

2. Secondly,weareusingaspecializedclasstoreplaceEditTextprovidingsomeconveniencemethodssuchasclear()andsetNumber().Thiswillimproveourapplicationdesign.

3. Next,weinvokeaconverter,namedTemperatureConverter,autilityclassprovidingthedifferentmethodstoconvertbetweendifferenttemperatureunits,andusingdifferenttypesforthetemperaturevalues.

4. Finally,aswewillbetruncatingtheresultstoprovidetheminasuitableformatpresentedintheuserinterface,weshouldcompareagainstadeltatoassertthevalueoftheconversion.

Creatingthetestlikethiswillforceustofollowtheplannedpath.Ourfirstobjectiveistoaddtheneededmethodsandcodetogetthetesttocompile,andthentosatisfythetest’sneeds.

TheEditNumberclassInourmainpackage,notinthetestsone(whichisnottheoneunder/androidTest/),weshouldcreatetheEditNumberclassextendingEditText,asweneedtoextenditsfunctionality.Oncetheclassiscreated,weneedtochangethetypeofthefieldsinourtestclassmembertypes:

publicclassTemperatureConverterActivityTestsextends

ActivityInstrumentationTestCase2<TemperatureConverterActivity>{

privateTemperatureConverterActivityactivity;

privateEditNumbercelsiusInput;

privateEditNumberfahrenheitInput;

privateTextViewcelsiusLabel;

privateTextViewfahrenheitLabel;

Then,changeanycastthatispresentinthetests.YourIDEwillhighlightthese;pressF2tofindthemintheclass.

Therearestilltwoproblemsweneedtofixbeforebeingabletocompilethetest:

Westilldon’thavetheclear()andsetNumber()methodsinEditNumberWedon’thavetheTemperatureConverterutilityclass

Frominsideourtestclass,wecanusetheIDEtohelpuscreatethemethods.PressF2again,andyoushouldbetakentotheerrorforCannotresolvemethodclear().NowpressAlt+Entertocreatetheclear()methodintypeEditNumber.SameforgetNumber().

Finally,wemustcreatetheTemperatureConverterclass.ThisclasswillholdthemathematicalconversionsofCelsiusandFahrenheit,andnoAndroidcode.Therefore,wecancreatethispackageinsideofour/core/module.Aspreviouslydiscussed,itwillbeunderthesamepackagestructure,onlythismoduledoesnotknowaboutAndroidand,therefore,wecanwriteJVMteststhatrunmuchfaster.

TipBesuretocreateitinthecoremoduleunderthesamepackageasyourmaincode,andnotinthetestpackage.

Here’showtocreatethatclassinthecoremodule,andthecurrentstateofourapplication:

Havingdonethis,inourtest,itcreatesthefahrenheitToCelsiusmethod.

Thisfixesourlastproblem,andleadsustoatestthatwecannowcompileandrun.YesyouwillhaveredLinterrors,butthesearenot“compile”errors,andsothetestscanstillrun.(AndroidStudio’sclevernessistoodamnhigh.)

Surprisingly,ornot,whenwerunthetests,theywillfailwithanexception:

java.lang.ClassCastException:

android.widget.EditTextcannotbecasttocom.blundell.tut.EditNumber

atcom.blundell.tut.TemperatureConverterActivityTests.setUp(

TemperatureConverterActivityTests.java:36)

atandroid.test.AndroidTestRunner.runTest(

AndroidTestRunner.java:191)

ThatisbecauseweupdatedallofourJavafilestoincludeournewlycreatedEditNumberclass,butforgottochangethelayoutXML.

Let’sproceedtoupdateourUIdefinition:

<com.blundell.tut.EditNumber

android:id="@+id/converter_celsius_input"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_margin="@dimen/margin"

android:gravity="end|center_vertical"/>

Thatis,wereplacetheoriginalEditTextclassbycom.blundell.tut.EditNumber,whichisaViewextendingtheoriginalEditTextclass.

Now,werunthetestsagain,andwediscoverthatalltestspass.

Butwaitaminute;wehaven’timplementedanyconversionoranyhandlingofvaluesinthenewEditNumberclass,andalltestspassedwithnoproblem.Yes,theypassedbecausewedon’thaveenoughrestrictionsinoursystemandtheonesinplace,simplycancelthemselves.

Beforegoingfurther,let’sanalyzewhatjusthappened.OurtestinvokedthefahrenheitInput.setText("32.5")methodtosetthetemperatureenteredintheFahrenheitfield,butourEditNumberclassdoesn’tdoanythingwhentextisentered,andthefunctionalityisnotimplemented.So,theCelsiusfieldremainsempty.

ThevalueforexpectedC—theexpectedtemperatureinCelsius,iscalculatedinvokingTemperatureConverter.fahrenheitToCelsius(f),butthisisanemptymethod.Inthiscasebecauseweknewthereturntypeofthemethodwemadeitreturntoaconstant0.So,expectedCbecomes0.

Then,theactualvaluefortheconversionisobtainedfromtheUI.InthiscaseinvokinggetNumber()fromEditNumber.Butthismethodwasautomaticallygenerated,andtosatisfytherestrictionimposedbyitssignature,itmustreturnavalue,namely0.

Thedeltavalueisagain0,ascalculatedbyMath.abs(expectedC–actualC).

AndfinallyourassertionassertTrue(msg,delta<0.005)istrue,becausedelta=0satisfiesthecondition,andthetestpasses.

So,isourmethodologyflawed,asitcannotdetectasimplesituationlikethis?

No,notatall,theproblemhereisthatwedon’thaveenoughrestrictions,andtheyaresatisfiedbythedefaultvaluesusedbyauto-generatedmethods.Onealternativecouldbetothrowexceptionsatalloftheauto-generatedmethods,somethinglikeRuntimeException("notyetimplemented")todetectitsusewhennotimplemented.Wewillbeaddingenoughrestrictionsinoursystemtoeasilytrapthisdoublezerocondition.

TheTemperatureConverterunittestsItseems,fromourpreviousexperience,thatthedefaultconversionimplementedalwaysreturns0,soweneedsomethingmorerobust.Otherwise,ourconverterwillonlybereturningavalidresult,whentheparametertakesthevalueof32F(32F==0C).

TheTemperatureConverterclassisautilityclassnotrelatedwiththeAndroidinfrastructure,soastandardunittestwillbeenoughtotestit.

Asthisisthefirstcoretestwearegoingtowrite,weneedtodosomesetup.Firstly,fromtheprojectview;inyourprojectstructurecreateatestfolderunder/core/srcbyselectingNew|Directoryandusingthenametest.Insidethis,createajavafolderbyselectingNew|Directory,andusingthenamejava.WithGradlebeingmagic,itwillnowunderstandthatthisisaplaceyouwanttoaddtests,andthefoldershouldturngreen(greenmeansthatthefolderisapartofthetestclasspath).Nowaddanewpackage,technicallyitisnotnewbecausewearegoingtousecom.blundell.tutagain,byselectingNew|Package|andusingthenamecom/blundell/tut.

Now.wecreateourtestsinournewfolderandpackage.WecreateourtestsbyselectingNew|JavaClass,andcallingitTemperatureConverterTests.Yourprojectshouldnowlooklikethis:

Let’smakeourfirsttest,insideofTemperatureConverterTests,pressCtrl+EntertobringuptheGeneratemenu,asshowninthefollowingscreenshot:

SelectingtheTestMethodtest,thenJUnit4willgenerateusatemplatemethodofatestthatwewant,nameittestFahrenheitToCelsius().Rememberthisshortcutasit’shandywhenevercreatinganewtest.Onceyou’vegeneratedthistest,you’llnoticewehavecompileerrorsontheJUnit4importedlinesofcode.Oops!weforgottoaddtheJUnitlibrarytothetestclasspathofourcoremodule.Openupthebuildfilein/core/build.gradle,andaddtheJUnitdependency.Yourcorebuild.gradlewillnowlooklikethis:

applyplugin:'java''java'

dependencies{

compilefileTree(dir:'libs''libs',include:[''''*.jar'])

testCompile'junit'junit:junit:4.+''''

}

NoteNotice,herewehavejumpedfromJUnit3toJUnit4themaindifferencebeingwecannowuseannotationstotellourtestrunner,whichofthemethodsintheclassaretests.Therefore,technicallywedon’tneedtostartthemethodswithtestasintestFooBar()anymore,butwewillforourownsanitywhenswoppingbetweenthetwo(AndroidJUnit4supportiscomingsoon!).

DoaprojectsyncbyselectingProjectSync,andwearecompilingandreadytocode.Let’sstartwritingthetest:

@Test

publicvoidtestFahrenheitToCelsius(){

for(doubleknownCelsius:conversionTable.keySet()){

doubleknownFahrenheit=conversionTable.get(knownCelsius);

doubleresultCelsius=

TemperatureConverter.fahrenheitToCelsius(knownFahrenheit);

doubledelta=Math.abs(resultCelsius-knownCelsius);

Stringmsg=knownFahrenheit+"F->"+knownCelsius+"C"+"but

is"+resultCelsius;

assertTrue(msg,delta<0.0001);

}

}

Creatingaconversiontablewithvaluesfordifferenttemperatureconversion,weknowfromothersources,wouldbeagoodwaytodrivethistest:

Map<Double,Double>conversionTable=newHashMap<Double,Double>(){

//initialize(celsius,fahrenheit)pairs

put(0.0,32.0);

put(100.0,212.0);

put(-1.0,30.20);

put(-100.0,-148.0);

put(32.0,89.60);

put(-40.0,-40.0);

put(-273.0,-459.40);

}};

Toruntestsinthecoremodule,wecanrightclickonthefileintheprojectview,andselectRun.Asthescreenshotalsoshows,youcanusetheshortcutCmd+Shift+F10:

Whenthistestruns,weverifythatitfails,givingusthistrace:

java.lang.AssertionError:-40.0F->-40.0Cbutis0.0

atorg.junit.Assert.fail(Assert.java:88)

atorg.junit.Assert.assertTrue(Assert.java:41)

at

com.blundell.tut.TemperatureConverterTests.testFahrenheitToCelsius(Temperat

ureConverterTests.java:31).

NoteSeehowfastthosecoretestsran!Aimformovingasmuchofyourapplicationlogicintoyourcoremoduleasyoucan,soyoucantakeadvantageofthisspeedwhendoingTest-drivenDevelopment.

Well,thiswassomethingwewereexpectingasourconversionalwaysreturns0.Implementingourconversion,wediscoverthatweneedsomeABSOLUTE_ZERO_Fconstant:

privatestaticfinaldoubleABSOLUTE_ZERO_F=-459.67d;

privatestaticfinalStringERROR_MESSAGE_BELOW_ZERO_FMT=

"Invalidtemperature:%.2f%cbelowabsolutezero";

privateTemperatureConverter(){

//non-instantiablehelperclass

}

publicstaticdoublefahrenheitToCelsius(doublefahrenheit){

if(fahrenheit<ABSOLUTE_ZERO_F){

Stringmsg=String.format(ERROR_MESSAGE_BELOW_ZERO_FMT,

fahrenheit,'F''F');

thrownewInvalidTemperatureException(msg);

}

return((fahrenheit-32)/1.8d);

}

Absolutezero,isthetheoreticaltemperatureatwhichentropywouldreachitsminimumvalue.Tobeabletoreachthisabsolutezerostate,accordingtothelawsofthermodynamics,thesystemshouldbeisolatedfromtherestoftheuniverse.Thus,itisanunreachablestate.However,byinternationalagreement,absolutezeroisdefinedas0KontheKelvinscale,andas-273.15°ContheCelsiusscaleorto-459.67°FontheFahrenheitscale.

Wearecreatingacustomexception,InvalidTemperatureException,toindicateafailureprovidingavalidtemperaturetotheconversionmethod.Thisexceptiondoesn’tknowanythingaboutAndroid,andsocanalsositinourcoremodule.CreateitbyextendingRuntimeException:

publicclassInvalidTemperatureExceptionextendsRuntimeException{

publicInvalidTemperatureException(Stringmsg){

super(msg);

}

}

Runningthecoretestsagain,wediscoverthattestFahrenheitToCelsiussucceeds.Therefore,wemovebacktoourAndroidtests,andrunningtheseshowussuchthattestFahrenheitToCelsiusConversiontestfails.Thistellsus,thatnowtheconverterclasscorrectlyhandlesconversions,buttherearestillsomeproblemswiththeUIhandlingthisconversion.

NoteDon’tdespairaboutrunningtwoseparatetestclasses.Itiscommonforyoutobeselectiveaboutwhatteststorun;thisispartlyalearntskillwhendoingTDD.However,ifyousowish,youcanwritecustomtestrunnersthatwillrunallofyourtests.Also,usingGradletorunbuildconnectedAndroidTestwillrunallyourtestsatonce,whichisadvisedwheneveryouconsideryouhavecompletedafeature,orwanttocommittoyourupstreamversioncontrol.

AcloserlookatthetestFahrenheitToCelsiusConversionfailuretracerevealsthatthere’ssomethingstillreturning0,whenitshouldn’t.

ThisremindsusthatwearestilllackingaproperEditNumberimplementation.Before

proceedingtoimplementthementionedmethods,let’screatethecorrespondingteststoverifywhatweareimplementingiscorrect.

TheEditNumbertestsFromthepreviouschapter,wecannowdeterminethatthebestbaseclassforourcustomViewtestsisAndroidTestCase,asweneedamockContextclasstocreatethecustomView,butwedon’tneedsysteminfrastructure.

CreatethetestsforEditNumber,let’scallitEditNumberTests,andextendAndroidTestCase.Reminder,thisisundertheappmoduleintheandroidTestpath.

Weneedtoaddtheconstructorstoreflectthepatternweidentifiedbeforewiththegivennamepattern:

publicEditNumberTests(){

this("EditNumberTests");

}

publicEditNumberTests(Stringname){

setName(name);

}

Thenextstepistocreatethefixture.Inthiscase,thisisasimpleEditNumberclassthatwewillbetesting:

@Override

protectedvoidsetUp()throwsException{

super.setUp();

editNumber=newEditNumber(mContext);

editNumber.setFocusable(true);

}

ThemockcontextisobtainedfromtheprotectedfieldmContext(http://developer.android.com/reference/android/test/AndroidTestCase.html#mContext),availableintheAndroidTestCaseclass.

AttheendofthesetUpmethod,weseteditNumberasafocusableView,meaningitwillbeabletogainfocus,asitwillbeparticipatinginabunchoftestssimulatingUIsthatmayneedtorequestitsfocusexplicitly.

Next,wetestthattherequiredclear()functionalityisimplementedcorrectlyinthetestClear()method:

@UiThreadTest

publicvoidtestClear(){

Stringvalue="123.45";

editNumber.setText(value);

editNumber.clear();

assertEquals("",editNumber.getText().toString());

}

Runningthetestweverifythatitfails:

junit.framework.ComparisonFailure:expected:<[]>butwas:<[123.45]>

atcom.blundell.tut.EditNumberTests.testClear(EditNumberTests.java:31)

atjava.lang.reflect.Method.invokeNative(NativeMethod)

atandroid.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)

WeneedtoimplementEditNumber.clear()correctly.

Thisisasimplecase,sojustbyaddingthisimplementationtoEditNumber,wesatisfythetest:

publicvoidclear(){

setText("");

}

Runthetestandproceed.WearegoingtoaddanewmethodtoEditNumber.Here,wealreadyhavegetNumber(),andweareaddingsetNumber()sothatwecanuseitlateron.Nowlet’scompletethetestSetNumber()implementation:

publicvoidtestSetNumber(){

editNumber.setNumber(123.45);

assertEquals("123.45",editNumber.getText().toString());

}

WhichfailsunlessweimplementEditNumber.setNumber(),similartothisimplementation:

privatestaticfinalStringDEFAULT_FORMAT="%."%.2f";";

publicvoidsetNumber(doublenumber){

super.setText(String.format(DEFAULT_FORMAT,number));

}

Weareusingaconstant,DEFAULT_FORMAT,toholdthedesiredformattoconvertthenumbers.ThiscanbelaterconvertedtoapropertythatcouldalsobespecifiedintheXMLlayoutdefinitionofthefield.

ThesamegoesforthetestGetNumber()andgetNumber()pair:

publicvoidtestGetNumber(){

editNumber.setNumber(123.45);

assertEquals(123.45,editNumber.getNumber());

}

AndthegetNumber()methodisasfollows:

publicdoublegetNumber(){

Stringnumber=getText().toString();

if(TextUtils.isEmpty(number)){

return0D;

}

returnDouble.valueOf(number);

}

Thesetestssucceed,sorunyourotherteststoseewhereweareupto;Ididthisonthe

commandlinerunningthegradlewbuildcATcommand.Thisrunsallofthetestswehavewrittensofar;buttestFahrenheitToCelsiusConversion()isfailing.Wehavealotofwelltestedcode,takeastepback,andreflect.

HereareourAndroidtestresults:

HereareourcoreJavatestresults:

WithtestFahrenheitToCelsiusConversion()ifyoucloselyanalyzethetestcase,canyoucandiscoverwheretheproblemis.

Gotit?

Ourtestmethodisexpectingtheconversiontohappenautomaticallywhenthefocuschanges,aswasspecifiedinourlistofrequirements:“whenonetemperatureisenteredinonefield,theotheroneisautomaticallyupdatedwiththeconversion”.

Remember,wedon’thavebuttonsoranythingelsetoconverttemperaturevalues,sotheconversionistobedoneautomatically,oncethevaluesareentered.

ThisleadsusbacktoourTemperatureConverterActivityclass,andthewayithandlestheconversions.

TheTemperatureChangeWatcherclassOnewayofimplementingtherequiredbehaviorofconstantlyupdatingtheothertemperaturevalue,isoncetheoriginalhaschangedisthroughaTextWatcher.Fromthedocumentation,wecanunderstandthataTextWatcherisanobjectofatypethatisattachedtoanEditable;itsmethodswillbecalled,whenthetextischanged(http://developer.android.com/reference/android/text/TextWatcher.html).

Itseemsthatiswhatweneed.

WeimplementthisclassasaninnerclassofTemperatureConverterActivity.Theideabehindthisis,becauseweactdirectlyontheViewsoftheActivity,havingitasaninnerclassshowsthisrelationship,andkeepsitobvious,shouldsomeonethinkofchangingthisActivity’slayout.IfyouimplementtheminimumTextWatcher,yourActivitywilllooklikethis:

publicclassTemperatureConverterActivityextendsActivity{

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_temperature_converter);

}

/**

*Changesfieldsvalueswhenthetextchanges;applyingthecorrelated

conversionmethod.

*/

staticclassTemperatureChangedWatcherimplementsTextWatcher{

@Override

publicvoidbeforeTextChanged(CharSequences,intstart,intcount,

intafter){

}

@Override

publicvoidonTextChanged(CharSequences,intstart,intbefore,

intcount){

}

@Override

publicvoidafterTextChanged(Editables){

}

}

}

Andnowthisisourcode,aftersomeadditionstotherecentlycreatedclass:

/**

*Changesfieldsvalueswhenthetextchanges;

*applyingthecorrelatedconversionmethod.

*/

staticclassTemperatureChangedWatcherimplementsTextWatcher{

privatefinalEditNumbersourceEditNumber;

privatefinalEditNumberdestinationEditNumber;

privatefinalOptionoption;

privateTemperatureChangedWatcher(Optionoption,

EditNumbersource,

EditNumberdestination){

this.option=option;

this.sourceEditNumber=source;

this.destinationEditNumber=destination;

}

staticTemperatureChangedWatchernewCelciusToFehrenheitWatcher(EditNumber

source,EditNumberdestination){

returnnewTemperatureChangedWatcher(Option.C2F,source,destination);

}

staticTemperatureChangedWatchernewFehrenheitToCelciusWatcher(EditNumber

source,EditNumberdestination){

returnnewTemperatureChangedWatcher(Option.F2C,source,destination);

}

@Override

publicvoidonTextChanged(CharSequenceinput,intstart,intbefore,int

count){

if(!destinationEditNumber.hasWindowFocus()

||destinationEditNumber.hasFocus()

||input==null){

return;

}

Stringstr=input.toString();

if("".equals(str)){

destinationEditNumber.setText("");

return;

}

try{

doubletemp=Double.parseDouble(str);

doubleresult=(option==Option.C2F)

?TemperatureConverter.celsiusToFahrenheit(temp)

:TemperatureConverter.fahrenheitToCelsius(temp);

StringresultString=String.format("%.2f",result);

destinationEditNumber.setNumber(result);

destinationEditNumber.setSelection(resultString.length());

}catch(NumberFormatExceptionignore){

//WARNINGthisisgeneratedwhilst

//numbersarebeingentered,

//forexamplejusta'-'

//sowedon''twanttoshowtheerrorjustyet

}catch(Exceptione){

sourceEditNumber.setError("ERROR:"+e.getLocalizedMessage());

}

}

@Override

publicvoidafterTextChanged(Editableeditable){

//notused

}

@Override

publicvoidbeforeTextChanged(CharSequences,intstart,intcount,int

after){

//notused

}

}

WewillbeusingthesameTemperatureChangeWatcherimplementationforbothfields,CelsiusandFahrenheit;thereforewekeepareferencetothefieldsusedassourceanddestination,aswellastheoperationneededtoupdatetheirvalues.Tospecifythisoperation,weareintroducingenum,whichispureJavaandsocangointothecoremodule:

/**

*C2F:celsiusToFahrenheit

*F2C:fahrenheitToCelsius

*/

publicenumOption{

C2F,F2C

}

Thisoperationisspecifiedinthecreationfactorymethods,andthedestinationandsourceEditNumberareselectedaccordingly.Thiswaywecanusethesamewatcherfordifferentconversions.

ThemethodoftheTextWatcherinterfaceweareinterestedin,isonTextChanged.Thiswillbecalledanytimethetextchanges.Atthebeginning,weavoidpotentialloops,checkingwhohasfocus,andreturningiftheconditionsarenotmet.

Wealsosetthedestinationfieldasanemptystring,ifthesourceisempty.

Finally,wetrytosettheresultingvalueofinvokingthecorrespondingconversionmethodtosetthedestinationfield.Weflagtheerrorasnecessary,avoidingshowingprematureerrors,whentheconversionwasinvokedwithapartiallyenterednumber.

WeneedtosetthelistenerontheinputfieldsinTemperatureConverterActivity.onCreate():

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_temperature_converter);

EditNumbercelsiusEditNumber=

(EditNumber)findViewById(R.id.converter_celsius_input);

EditNumberfahrenheitEditNumber=

(EditNumber)findViewById(R.id.converter_fahrenheit_input);

celsiusEditNumber

.addTextChangedListener(

newCelciusToFehrenheitWatcher(celsiusEditNumber,fahrenheitEditNumber));

fahrenheitEditNumber

.addTextChangedListener(

newFehrenheitToCelciusWatcher(fahrenheitEditNumber,

celsiusEditNumber));

}

Tobeabletorunthetests,weshouldcompilethem.Tocompile,weneedtoatleastdefinethecelsiusToFahrenheit()method,whichisnotyetdefined.

MoreTemperatureConvertertestsWeneedtoimplementcelsiusToFahrenheit,andasusualwestartfromthetest.

ThisisfairlyequivalenttotheotherconversionmethodfahrenheitToCelsius,andwecanusetheinfrastructurewedevisedwhilecreatingthistest:

@Test

publicvoidtestCelsiusToFahrenheit(){

for(doubleknownCelsius:conversionTable.keySet()){

doubleknownFahrenheit=conversionTable.get(knownCelsius);

doubleresultFahrenheit=

TemperatureConverter.celsiusToFahrenheit(knownCelsius);

doubledelta=Math.abs(resultFahrenheit-knownFahrenheit);

Stringmsg=knownCelsius+"C->"+knownFahrenheit+"F"

+"butis"+resultFahrenheit;

assertTrue(msg,delta<0.0001);

}

}

Weusetheconversiontabletoexercisethemethodthroughdifferentconversions,andweverifythattheerrorislessthanapredefineddelta.

Then,thecorrespondentconversionimplementationintheTemperatureConverterclassisasfollows:

staticfinaldoubleABSOLUTE_ZERO_C=-273.15d;

publicstaticdoublecelsiusToFahrenheit(doublecelsius){

if(celsius<ABSOLUTE_ZERO_C){

Stringmsg=String.format(

ERROR_MESSAGE_BELOW_ZERO_FMT,celsius,'C');

thrownewInvalidTemperatureException(msg);

}

return(celsius*1.8d+32);

}

Now,allthetestsarepassingbutwearestillnottestingallthecommonconditions.WhatImeanbythisisthatwehavebeencheckingthehappypathsofar.Youshouldcheckwhethererrorsandexceptionsarecorrectlygenerated,besidesallthenormalcaseswecreatedsofar.

Createthistesttocheckthecorrectgenerationofexceptions,whenatemperaturebelowabsolutezeroisusedinaconversion:

@Test(expected=InvalidTemperatureException.class)

publicvoidtestExceptionForLessThanAbsoluteZeroF(){

TemperatureConverter.fahrenheitToCelsius(ABSOLUTE_ZERO_F-1);

}

Inthistest,wedecrementtheabsolutezerotemperature,toobtainanevensmallervalue,andthenweattempttheconversion.Wewrotethistestinourcoremodule,andtherefore

usedJUnit4,whichallowsustouseannotationstoassertthatweexpectanexceptiontobethrown.IfyouwantedtodothesamethinginJUnit3,youwouldhavetouseatrycatchblock,andfailthetestifthecodedidnotenterthecatchblock:

@Test(expected=InvalidTemperatureException.class)

publicvoidtestExceptionForLessThanAbsoluteZeroC(){

TemperatureConverter.celsiusToFahrenheit(ABSOLUTE_ZERO_C-1);

}

Inasimilarmanner,wetestfortheexceptionbeingthrown,whentheattemptedconversioninvolvesatemperatureinCelsius,thatislowerthantheabsolutezero.

TheInputFiltertestsAnothererrorrequirementcouldbe:Wewanttofiltertheinputthatisreceivedbytheconversionutility,sonogarbagereachesthispoint.

TheEditNumberclassalreadyfiltersvalidinput,andgeneratesexceptionsotherwise.WecanverifythisconditionbycreatinganewtestinTemperatureConverterActivityTests.Wechoosethisclassbecausewearesendingkeystotheentryfields,justasarealuserwoulddo:

publicvoidtestInputFilter()throwsThrowable{

runTestOnUiThread(newRunnable(){

@Override

publicvoidrun(){

celsiusInput.requestFocus();

}

});

getInstrumentation().waitForIdleSync();

sendKeys("MINUS1PERIOD2PERIOD3PERIOD4");

doublenumber=celsiusInput.getNumber();

Stringmsg="-1.2.3.4shouldbefilteredto-1.234"

+"butis"+number;

assertEquals(msg,-1.234d,number);

}

ThistestrequeststhefocusontotheCelsiusfieldusingthepatternreviewedpreviously.ThisallowsustorunpartsofatestintheUIthread,andsendkeyinputtotheview.Thekeyssentareaninvalidsequencecontainingmorethanoneperiod,whichisnotacceptedforawell-formeddecimalnumber.Itisexpectedthatwhenthefilterisinplace,thissequencewillbefiltered,andonlythevalidcharactersreachthefield.AssertingthatthevaluereturnedbycelsiusInput.getNumber(),iswhatweexpectafterfiltering.

Toimplementthisfilter,weneedtoaddInputFiltertoEditNumber.Becausethisshouldbeaddedtoalloftheconstructors,wecreateanadditionalinit()method,whichweinvokefromeach.ToachieveourgoalweuseaninstanceofDigitsKeyListener,acceptingdigits,signs,anddecimalpointsasfollows:

publicEditNumber(Contextcontext){

super(context);

init();

}

publicEditNumber(Contextcontext,AttributeSetattrs){

super(context,attrs);

init();

}

publicEditNumber(Contextcontext,AttributeSetattrs,intdefStyle){

super(context,attrs,defStyle);

init();

}

privatevoidinit(){

//DigistKeyListener.getInstance(true,true)

//returnsaninstancethatacceptsdigits,signanddecimalpoint

InputFilter[]filters=

newInputFilter[]{DigitsKeyListener.getInstance(true,true)};

setFilters(filters);

}

Thisinitmethodisinvokedfromeachconstructor,sothatifthisviewisusedprogrammaticallyorfromXML,westillhaveourfilter.

Runningthetestsagain,wecanverifythatallhavepassed,andnoweverythingisgreenagain.

ViewingourfinalapplicationWelldone!Wenowhaveourfinalapplicationthatsatisfiesalltherequirements.

Inthefollowingscreenshotweareshowingoneoftheserequirements,whichisthedetectionofanattempttoconvertatemperaturebelowtheabsolutezerotemperatureinCelsius(-1000.00C):

TheUIrespectstheguidelinesprovided;thetemperaturescanbeconvertedbyenteringtheminthecorrespondingunitfield.

Torecap,thisisthelistofrequirementsthatwehaveimplemented:

TheapplicationconvertstemperaturesfromCelsiustoFahrenheit,andviceversaTheuserinterfacepresentstwofieldstoenterthetemperatures,oneforCelsiusandtheotherforFahrenheitWhenonetemperatureisenteredinonefield,theotheroneisautomaticallyupdatedwiththeconversionIfthereareerrors,theyshouldbedisplayedtotheuser,possiblyusingthesamefieldsSomespaceintheuserinterfaceshouldbereservedfortheon-screenkeyboard,toeasetheapplicationoperationwhenseveralconversionsareenteredEntryfieldsshouldstartemptyValuesenteredaredecimalvalueswithtwodigitsafterthepointDigitsarerightaligned

Whatismoreimportantisthatwecannowbecertainthattheapplicationnotonlysatisfies

therequirements,butalsohasnoevidentproblemsorbugs.Wetookeverystepbyanalyzingthetestresults,andfixingtheproblemsattheirfirstappearance.Thiswillensurethatanyindividualbug,oncediscovered,testedandfixed,willnotresurfaceagain.

SummaryWepresentedTest-drivenDevelopmentintroducingitsconcepts,andapplyingthemstep-by-stepinapotentialreal-lifeproblem.

Westartedwithaconciselistofrequirements,describingthetemperatureconverterapplication.

Weimplementedeverytestfollowedbythecodethatsatisfiesit.Inthismanner,weimplementedtheapplicationbehavioraswellasitspresentation,conductingteststoverifythattheUIwedesignedfollowsthespecifications.

Havingthetestsinplace,leadustoanalyzethedifferentpossibilitieswehaveinrunningthem.Evolvingfromthepreviouschapter,nowourcontinuousintegrationmachinecanruntheteststoguaranteeanychangesfromtheteamwillstillresultinawell-testedapplication.

ThenextchapterintroducesBehavior-drivenDevelopment,andcontinuesouraimforbug-freewell-testedcode,thistimewithafocusuponbehaviorandagreement,onwhatarequirementmeansthroughouttheteam.

Chapter7.Behavior-drivenDevelopmentBehavior-drivenDevelopment(BDD)canbeunderstoodastheevolutionandconfluenceofTest-drivenDevelopment(TDD)andacceptancetesting.Bothtechniqueswerediscussedinpreviouschapters,soyoumaywanttolookbackatChapter1,GettingStartedwithTesting,andChapter6,PracticingTest-drivenDevelopment,beforeproceeding.

Behavior-drivenDevelopmentintroducessomenewconcepts,suchastheuseofacommonvocabularytodescribethetests,andtheinclusionofbusinessparticipantsinthesoftwaredevelopmentproject,suchasproductownersorbusinessanalysts.

WehavevisitedTest-drivenDevelopmentbefore,andwefocusedonconvertinglow-levelrequirementsintoteststhatcoulddriveourdevelopmentprocess.Behavior-drivenDevelopmentforcesustoconcentrateonhigherlevelrequirements,andbyusingaspecificvocabulary,wecanexpresstheserequirementsinawaythatcanbefurtheranalyzedorevaluated.SomepeoplebelieveBDDisonlythephilosophyofTDDdoneright.

Wewillexploretheseconceptsthroughexamples,sothatyoucandrawyourownconclusions.

Given,When,andThenGiven/When/Thenwordsarethecommonvocabularythatspansthedividebetweenbusinessandtechnology,andasdescribedathttp://behaviour-driven.org,theycanalsobereferredtoastheubiquitouslanguageofBehavior-drivenDevelopment.Theframeworkisbasedonthefollowingthreecoreprinciplesthatwereproducehere,verbatim:

BusinessandtechnologyshouldrefertothesamesysteminthesamewayAnysystemshouldhaveanidentified,verifiablevaluetothebusinessUp-frontanalysis,design,andplanning,allhaveadiminishingreturn

Behavior-drivenDevelopmentreliesontheuseofthisspecificvocabulary.Additionally,theformatinwhichrequirementsareexpressedispredetermined,allowingtoolstointerpretandexecutethem:

Given:Thisistodescribetheinitialstatebeforeanexternalstimuliisreceived.When:Thisistodescribethekeyactiontheuserperforms.Then:Thisistoanalyzetheresultsoftheactions.Tobeobservable,theactionsperformedshouldhavesomekindofoutcome.

FitNesseFitNesseisasoftwaredevelopmentcollaborationtoolthatcanbeusedtomanageBDDscenarios.StrictlyspeakingFitNesseisasetoftools,describedasfollows:

Asasoftwaretestingtool,FitNesseisalightweight,opensourceframeworkthatallowsteamstocollaborateItisalsoaWikiwhereyoucaneasilycreate,editpages,andshareinformationAwebserver,soitdoesn’trequireadditionalconfigurationoradministrativeprivilegestosetup,orconfigure

DownloadtheFitNessedistributionfromhttp://www.fitnesse.org.ThedistributionisaJARfilethatinstallsitselfonfirstrun.Throughouttheseexamples,weusedFitNessestandalonerelease20140901butnewerversionsshouldalsowork.

RunningFitNessefromthecommandlineBydefault,whenFitNesseruns,itlistensonport80,sotorununprivileged,youshouldchangetheportonthecommandline.Inthisexample,weuse8900:

$java-jarfitnesse.jar-p8900

Thisistheoutputobtainedwhenwerunthecommand:

BootstrappingFitNesse,thefullyintegratedstandalonewikiandacceptance

testingframework.

rootpage:fitnesse.wiki.fs.FileSystemPageat./FitNesseRoot#latest

logger:none

authenticator:fitnesse.authentication.PromiscuousAuthenticator

pagefactory:fitnesse.html.template.PageFactory

pagetheme:fitnesse_straight

StartingFitNesseonport:8900

Oncerunning,youcandirectyourbrowsertothelocalFitNesseserverhomepage(http://localhost:8900/FrontPage),andyouwillbepresentedwiththiscontent:

CreatingaTemperatureConverterTestssubwikiOnceFitNesseisupandrunning,wecanstartbycreatingasubwikitoorganizeourtests.Youmayalreadybefamiliarwiththewikiconcept.Ifnot,wikiisawebsitethatallowspageeditingandcreationbyitsusers.Thiseditingprocessisdonefromwithinthebrowser,andusesamarkuplanguagethatgreatlysimplifiestheprocess.

NoteYoucanfindoutmoreaboutwikisinwhatcouldperhapsbethemostfamouswikiathttp://en.wikipedia.org/wiki/Wiki.

Thoughthissubwikiorganizationisnotmandatory,itishighlyrecommended,especiallyifyouplantouseFitNesseforacceptancetestingonmultipleprojects.

Oneofthemostsimplifiedprocessesishyperlinkcreation,whichisdoneonlybyusingCamelCaseorWikiWords;thatisawordthatstartswithacapitalletterandhasatleastoneormorecapitalletterinit.ThisWikiWordwillbeconvertedintoahyperlinktoapage,withthatname.

TocreatetheTemperatureConverterTestssubwiki,wesimplypresstheEditbuttontotherightoftheFitNesselogo,toeditthehomepage,addingthefollowing:

|'''MyTests'''|

|TemperatureConverterTests|''TemperatureConverterTests''|

Thisaddsanewtabletothepagebyusingthe“|”markupasthefirstcharacterandtodelimitthecolumns.

Wealsoaddacolumnwithadescriptivecommentaboutthetests.Thiscommentisturnedintoitalicsbysurroundingitwithtwosinglequotes(”).Thistextwillcreateawikilinknamed,TemperatureConverterTests.

PressSave,andthepagewillbemodified.

Oncethepageisdisplayed,wecanverifythatTemperatureConverterTestsisnowfollowedbya[?](questionmark)becausethepagehasnotbeencreatedyet,andwillbecreatedwhenweclickonit.Clickonitnow,thisputsusstraightintoeditmodeofthenewpage.Wecanaddsomecommentstoclearlyidentifythisnewlycreatedfrontpageofthesubwiki:

!contents-R2-g-p-f-h

Thisisthe!-TemperatureConverterTestsSubWiki-!.

Here,thetextTemperatureConverterTestsSubWikiisescapedusing!-and-!topreventitfrombeingconvertedtoanotherpagelink.

PressSaveagain.

AddingchildpagestothesubwikiNow,weaddanewchildpagebyusingthe[Add]linkthatappearsnexttothepagetitle.

Therearedifferentoptionsforcreatingthechildpage,andwecanselect:

Static:ThisisanormalWikipageSuite:ThisisapagecontainingothertestscomposingasuiteTest:Thisisapagethatcontainstests

WewillselecttoaddasuitepageandcallitTemperatureConverterTestSuiteasshowninthefollowingscreenshot:

AfterpressingSave,thispageiscreatedandhasbeenautomaticallyaddedasalinktotheTemperatureConverterTestssubwiki.

Let’sfollowthisnewlycreatedlinktoreachthetestsuitepage.

Onceyou’rehere,addanotherchildusing[Add]|[TestPage].Thisaddsatestpage,andwewillnameitTemperatureConverterCelsiusToFahrenheitFixture,asthiswillcontainourfixture.Thenaminghereisjustaconventiontoorganizeourwiki.

ClickonSavetofinishtheoperation.

AddingtheacceptancetestfixtureUpuntilnow,wewereonlycreatingwikipages.Nothingexcitingaboutthat!Now,wewillbeaddingouracceptancetestfixturedirectlytothepage.Besureyouhavenavigatedtothenewlyaddedpage,TemperatureConverterCelsiusToFahrenheitFixture,clickonEdit,andreplace<testpage>withthefollowing:

!contents

!|TemperatureConverterCelsiusToFahrenheitFixture|

|celsius|fahrenheit?|

|0.0|~=32|

|100.0|212.0|

|-1.0|30.2|

|-100.0|-148.0|

|32.0|89.6|

|-40.0|-40.0|

|-273.0|~=-459.4|

|-273|~=-459.4|

|-273|~=-459|

|-273|~=-459.40000000000003|

|-273|-459.40000000000003|

|-273|-459.41<_<-459.40|

|-274.0|Invalidtemperature:-274.00Cbelowabsolutezero|

Thistabledefinesseveralitemsforourtestfeature:

TemperatureConverterCelsiusToFahrenheitFixture:Thisisthetabletitleandthetestfixturename.celsius:Thisisthecolumnnameforthevalueweareprovidingasinputtothetest.fahrenheit?:Thisisthecolumnnameforthevalueexpectedastheresultoftheconversion.Thequestionmarkindicatesthatthisisaresultvalue.~=:Thisindicatesthattheresultisapproximatelythisvalue.<_<:Thisindicatesthattheexpectedvalueiswithinthisrange.Invalidtemperature:-274.00Cbelowabsolutezeroisthevalueexpectedbythefailedconversion.

SavethiscontentbyclickingonSave.

AddingthesupportingtestclassesIfwepresstheTestbutton,whichisbelowtheFitNesselogo(seethefollowingscreenshotfordetails),wewillreceiveanerror.Insomewaythisisexpectedbecausewehaven’tcreatedthesupportingtestfixtureyet.ThetestfixturewillbeaverysimpleclassthatinvokestheTemperatureConverterclassmethods.

FitNessesupportsthefollowingtwodifferenttestsystems:

fit:ThisistheolderofthetwomethodsandusesHTML,parsedjustpriortothefixturebeingcalledslim:Thisisnewer;allthetableprocessingisdoneinsideFitNessewithinslimrunners

Furtherinformationaboutthesetestsystemscanbefoundathttp://fitnesse.org/FitNesse.UserGuide.WritingAcceptanceTests.TestSystems.

Inthisexample,weuseslim,bysettingthevariableTEST_SYSTEMwithinthesamepageasfollows:

!defineTEST_SYSTEM{slim}

Now,wearegoingtocreatetheslimtestfixture.RememberthetextfixtureisasimpleclassthatallowsustorunouralreadywrittentemperatureconversioncodefromtheFitNessetestsuite.Wecreateanewpackage,namedcom.blundell.tut.fitnesse.fixture,insideofourexistingprojectTemperatureConverterandinsidethecoremodule.Wewillbecreatingthefixtureinsidethispackage.

Next,wehavetocreatetheTemperatureConverterCelsiusToFahrenheitFixtureclass,

whichwedefinedinouracceptancetesttable:

publicclassTemperatureConverterCelsiusToFahrenheitFixture{

privatedoublecelsius;

publicvoidsetCelsius(doublecelsius){

this.celsius=celsius;

}

publicStringfahrenheit()throwsException{

try{

doublefahrenheit=TemperatureConverter

.celsiusToFahrenheit(celsius);

returnString.valueOf(fahrenheit);

}catch(RuntimeExceptione){

returne.getLocalizedMessage();

}

}

}

Asareminderitshouldlooksomethinglikethiswhendone:

Thisfixtureshoulddelegatetoourrealcodeandnotdoanythingbyitself.WedecidedtoreturnStringfromfahrenheit(),sowecanreturntheExceptionmessageinthesamemethod.

Atthispoint,runthecoremoduleteststoensureyouhavenotbrokenanything(andtocompilethenewlycreatedclassforlater).

OntheFitNessetestpage,weshouldalsodefinethepackagethetestresidesin.ThisallowsthetestswritteninFitNessetofindthetestfixturewritteninourAndroidproject.Inthesamepagewearestillediting,add:

|import|

|com.blundell.tut.fitnesse.fixture|

Now,weaddourAndroidprojectclassfilestothepathofourFitNessetests.ThisallowsFitNessetouseournewlywrittentestfixtureandourTemperatureConverter;thecodeundertest:

!path

/Users/blundell/AndroidApplicationTestingGuide/core/build/classes/test

!path

/Users/blundell/AndroidApplicationTestingGuide/core/build/classes/main

NoteThisshouldbeadaptedtoyoursystempaths.Themainpointhereisthepathafter/core/.Thisispointingtowherethecompiled*.classfilesareforyourapplicationundertest.Note,thatweneedtoaddthetestsourcesandtheprojectsourcesseparately.

Afterfinishingthesesteps,wecanclickontheTestbuttontorunthetests,andthefollowingscreenshotwillreflecttheresults:

Wecaneasilyidentifyeverytestthatsucceededbytheirgreencolor,andthefailedonesbytheirredcolor.Inthisexample,wedon’thaveanyfailure,soeverythingisgreen.Notice,italsoshowstheclasspathandTEST_SYSTEMvariableswedeclared.

FitNessehasanotherusefulfeature,TestHistory.Allthetestrunsandaspecificnumberofresultsaresavedforaperiodoftime,sothatyoucanreviewtheresultslateronandcomparethem,andthus,analyzetheevolutionofyourchanges.

ThisfeatureisaccessedbyclickingTestHistorylocatedatthebottomofthelist,underTools,onthetopmenu.

Inthefollowingscreenshot,wecanseetheresultsforthelast3testruns,where2failedand1succeeded.Alsobyclickingonthe+(plus)or-(minus)signs,youcanexpandorcollapsetheviewtoshoworhidedetailedinformationaboutthetestrun:

GivWenZenGivWenZenisaframeworkthatbuildsuponFitNesseandSlimtoallowtheusertoexploittheBehavior-drivenDevelopmenttechniqueofexpression,usingtheGiven-When-Thenvocabularytodescribetests.ThesetestdescriptionsarealsocreatedusingtheFitNessewikifacility,ofexpressingthetestsasplaintextcontainedintablesinawikipage.

Theideaissimpleandstraightforward,andfollowsupwithwhatwehavebeendoingwithFitNesse,butthistimeinsteadofwritingacceptancetestsgivingatableofvalues,wewillusethethreeBehavior-drivenDevelopmentmagicwords,Given-When-Then,todescribeourscenarios.

Firstly,let’sinstallFitNessewithGivWenZen.Downloadthefulldistributionfromitsdownloadlistpageathttp://goo.gl/o3Hlpo.Onceunzipped,theGivWenZenJARstartsupexactlythesamewayasFitNessedid(becauseit’sjustamodificationontop):

$java-jar/lib/fitnesse.jar-p8900

Furtherreading,comprehensiveinstructionsandexamplescanbefoundonthewikiathttps://github.com/weswilliams/GivWenZen/wiki.WeusedGivWenZen1.0.3intheseexamples,butnewerversionsshouldworkaswell.

TheGivWenZenfulldistributionincludesallthedependenciesneeded,includingFitNesse,soifyouhaveFitNesserunningfrompreviousexamplesitisbettertostopit,asyoumustuseadifferentJARforGivWenZen.

Uponstartup,pointyourbrowsertothehomepageandyouwillfindafamiliarFitNessefrontpage,orifyouhaveconfiguredtheportlikewedidpreviously,checkoutsometestsathttp://localhost:8900/GivWenZenTests.Youcantakesometimetoexploretheexamplesincluded.

CreatingthetestscenarioLet’screateasimplescenarioforourtemperatureconverter,sowecanunderstandGiven-When-Thenabitbetter.

AsaplainEnglishsentence,ourscenariowouldbe:

GivenI’musingtheTemperatureConverter,WhenIenter100intotheCelsiusfield,ThenIobtain212intheFahrenheitfield.

ThevalueisdirectlytranslatedintoaGivWenZenscenariobyaddingthistoawikipage:

-|script|

|given|I'musingthe!-TemperatureConverter-!|

|when|Ienter100intotheCelsiusfield|

|then|Iobtain212intheFahrenheitfield|

Thetranslationisstraightforward.Thetabletitlemustbescript,andinthiscaseitisprecededbyadash(-)tohideit.TheneachoftheGiven-When-Thenscenariosisplacedinacolumn,andthepredicateintheothercolumn.

Beforerunningthisscript,whenthewholepageisexecuted,weneedtoinitializeGivWenZenbyrunninganotherscript.Youdothisbyaddingittothetopofthewikipage:

|import|

|org.givwenzen|

-|script|

|start|givwenzenforslim|

Wealsoneedtoinitializetheclasspathandaddthecorrespondingimportsforallscripts.Usually,thisisdoneinoneoftheSetUppages,whichareexecutedbeforerunningeverytestscript(likethesetUp()methodinaJUnittest),butforthesakeofsimplicity,weareaddingtheinitializationtothissamepage:

!defineTEST_SYSTEM{slim}

!path./target/classes

!path./target/examples

!path./lib/clover-2.6.1.jar

!path./lib/commons-logging.jar

!path./lib/commons-vfs-1.0.jar

!path./lib/dom4j-1.6.1.jar

!path./lib/fitnesse.jar

!path./lib/guava-18.0.jar

!path./lib/javassist.jar

!path./lib/log4j-1.2.9.jar

!path./lib/slf4j-simple-1.5.6.jar

!path./lib/slf4j-api-1.5.6.jar

!path./givwenzen-20150106.jar

!path

/Users/blundell/AndroidApplicationTestingGuide/core/build/classes/test

!path

/Users/blundell/AndroidApplicationTestingGuide/core/build/classes/main

YouwillneedtochangethelasttwopathvariablestomatchyourTemperatureConverter

project;you’llseewhyyouneedtheselater.

IfyourunthetestsatthispointbyclickingtheTestbutton,youwillreceivethefollowingmessage:

__EXCEPTION__:org.givwenzen.DomainStepNotFoundException:

Thesecondcolumninthetable,forourtestoutline,holdsthedomainsteps,hence,theexceptionDomainStepNotFound.Youneedastepclasswithanannotatedmethodmatchingthispattern:“I’musingtheTemperatureConverter”.

Typicalcausesofthiserrorareasfollows:

StepClassismissing:ThisisourerrorStepClassismissingthe@DomainStepsannotationStepMethodismissingthe@DomainStepannotationTheStepMethodannotationhasaregularexpressionthatdoesnotmatchthecurrentteststepyouhavewritten

Anexamplestepclasscouldbe:

@DomainSteps

publicclassStepClass{

@DomainStep("I'musingtheTemperatureConverter")

publicvoiddomainStep(){

//TODOimplementstepbyinvokingyourowncode

}

}

Thestepclassshouldbeplacedinthepackageorsubpackageofbdd.steps,oryoucouldalternativelydefineyourowncustompackage.Thispackageisgoingtoliveinsidethe/core/test/moduleofourapplication.Ifyounoticed,abovethesetUppage,weaddedourapplicationonthepath,sothisDomainStepcanbefoundafterwebuildtheproject.

Inordertousethe@DomainStep(s)annotationinourproject,weneedtheGivWenZenJARonourprojecttestpath.Thiscanbedonebycopyingthegivwenzen.jarfileinto/core/libs,orevenbetterwithGradlebyaddingitasaremotedependencytobuild.gradle:

testCompile'com.github.bernerbits:givwenzen:1.0.6.1'

TipYou’llnoticethatthistestCompiledependencyisn’ttheofficialGivWenZenreleasebutsomeonehasforked(copied)thecode,anduploadedit.Thisdoesn’tmattertousfornowbecauseweareonlyusingthetwoannotationclasses(whichIknowareidenticalinthisversion),butit’sworthkeepinginmindandrevertingtotheoriginalGivWenZenlibraryifitiseverreleasedasaremotedependency.

Followingthesmalloutlineexample,inourparticularcasetheimplementationofStepClasswillbe:

packagebdd.steps.tc;

importcom.blundell.tut.TemperatureConverter;

importorg.givwenzen.annotations.DomainStep;

importorg.givwenzen.annotations.DomainSteps;

@DomainSteps

publicclassTemperatureConverterSteps{

privatestaticfinalStringCELSIUS="Celsius";

privatestaticfinalStringFAHRENHEIT="Fahrenheit";

privatestaticfinalStringUNIT_NAME

="("+CELSIUS+"|"+FAHRENHEIT+")";

privatestaticfinalStringANY_TEMPERATURE

="([-+]?\\d+(?:\\.\\d+)?)";

privatedoubleinputTemperature=Double.NaN;

@DomainStep("I(?:a|')musingtheTemperatureConverter")

publicvoidcreateTemperatureConverter(){

//donothing

}

@DomainStep("Ienter"+ANY_TEMPERATURE

+"intothe"+UNIT_NAME+"field")

publicvoidsetField(doubleinputTemperature,StringunitName){

this.inputTemperature=inputTemperature;

}

@DomainStep("Iobtain"+ANY_TEMPERATURE

+"inthe"+UNIT_NAME+"field")

publicbooleanverifyConversion(doubleexpectedTemperature,String

unitName){

doubleoutputTemperature=convertInputInto(unitName);

returnMath.abs(outputTemperature-expectedTemperature)<0.01D;

}

privatedoubleconvertInputInto(StringunitName){

doubleconvertedInputTemperature;

if(CELSIUS.equals(unitName)){

convertedInputTemperature=getCelsius();

}elseif(FAHRENHEIT.equals(unitName)){

convertedInputTemperature=getFahrenheit();

}else{

thrownewRuntimeException("Unknownconversionunit"+

unitName);

}

returnconvertedInputTemperature;

}

privatedoublegetCelsius(){

returnTemperatureConverter.fahrenheitToCelsius(inputTemperature);

}

privatedoublegetFahrenheit(){

returnTemperatureConverter.celsiusToFahrenheit(inputTemperature);

}

}

Inthisexample,weareusingasubpackageofbdd.stepsbecause,bydefault,thisisthepackagehierarchyGivWenZensearchesforstep’simplementations.Otherwise,extraconfigurationisneeded.

Classesimplementingstepsshouldbeannotatedby@DomainSteps,andeachofthestep’smethodsannotatedby@DomainStep.EachstepmethodannotationreceivesaStringregularexpressionasaparameter.ThisregularexpressionisusedbyGivWenZentomatchthesteps.

Forexample,inourscenario,wehavedefinedthisstep:

Ienter100intotheCelsiusfield

Ourannotationisasfollows:

@DomainStep("Ienter"+ANY_TEMPERATURE

+"intothe"+UNIT_NAME+"field")

Thiswillmatch,andtheregularexpressiongroupvaluesdefinedbyANY_TEMPERATUREandUNIT_NAMEwillbeobtainedandprovidedtothemethodasitsargument’svalueandunitName:

publicvoidsetField(doubleinputTemperature,StringunitName)

RecallthatinapreviouschapterIrecommendedreviewingregularexpressionsbecausetheycouldbeuseful.Wellthisisprobablyoneoftheseplaceswheretheyareextremelyuseful.ItallowsforaflexibleuseoftheEnglishlanguage.HereI(?:a|'m)wasusedtoallowIamandI’m.InANY_TEMPERATURE,wearematchingeverypossibletemperaturevaluewiththeoptionalsignanddecimalpoint.ConsequentlyUNIT_NAMEmatchestheunitname;thatis,CelsiusorFahrenheit.

Theseregularexpressionsareusedintheconstructionofthe@DomainStepannotationparameters.Groupsdelimitedby()parenthesisintheseregularexpressionsareconvertedintomethodparameters.ThisishowsetField()obtainsitsparameters.

ThenwehaveaverifyConversion()methodthatreturnstrueorfalsedependingonwhethertheactualconversionmatchestheexpectedone,withinadifferenceoftwodecimalplaces.

Finally,wehavesomemethodsthatactuallyinvoketheconversionmethodsintheTemperatureConverterclass.

Onrunningthetestsonceagain,allthetestspass.Wecanconfirmthisbyanalyzingtheoutputmessage:

Assertions:1right,0wrong,0ignored,0exceptions.

Weshouldnotonlycreatescenariosfornormalsituations,butcoverexceptionalconditionsaswell.Say,inplaintext,ourscenarioissomethinglikethis:

Note

GivenI’musingtheTemperatureConverter,whenIenter-274intotheCelsiusfield,thenIobtainanInvalidtemperature:-274.00Cbelowabsolutezeroexception.

ItcanbetranslatedintoaGivWenZentablelikethefollowing:

-|script|

|given|Iamusingthe!-TemperatureConverter-!|

|when|Ienter-274intotheCelsiusfield|

|then|Iobtain'Invalidtemperature:-274.00Cbelowabsolutezero'

exception|

Byaddingasinglesupportingstepmethod,wewillbeabletorunit.Thestepmethodcanbeimplementedlikethis:

@DomainStep("Iobtain'(Invalidtemperature:"+ANY_TEMPERATURE+"

C|Fbelowabsolutezero)'exception")

publicbooleanverifyException(Stringmessage,Stringvalue,String

unit){

try{

if("C".equals(unit)){

getFahrenheit();

}else{

getCelsius();

}

}catch(RuntimeExceptionex){

returnex.getMessage().contains(message);

}

returnfalse;

}

Thismethodobtainstheexceptionmessage,temperaturevalue,andunitfromtheregularexpression.Thenthisiscomparedagainsttheactualexceptionmessagetoverifythatitmatches.

NoteDon’tforgetwhenyouaddJavacodetoyourStepClassannotationyouwillneedtocompiletheclassagainsothatFitNessecanusethenewcode.OnewaytodothisisjusttorunyourJavatestsfromtheIDE,forcingarecompile.

Additionally,wecancreateotherscenariosthat,inthissituation,willbesupportedbytheexistingstep’smethods.Thesescenarioscouldbe:

-|script|

|given|I'musingthe!-TemperatureConverter-!|

|when|Ienter-100intotheCelsiusfield|

|then|Iobtain-148intheFahrenheitfield|

-|script|

|given|I'musingthe!-TemperatureConverter-!|

|when|Ienter-100intotheFahrenheitfield|

|then|Iobtain-73.33intheCelsiusfield|

-|script|

|given|I'musingthe!-TemperatureConverter-!|

|when|Ienter-460intotheFahrenheitfield|

|then|Iobtain'Invalidtemperature:-460.00Fbelowabsolutezero'

exception|

BecauseGivWenZenisbasedonFitNesse,wearefreetocombinebothapproachesandincludethetestsfromourprevioussession,inthesamesuite.Doingso,wecanruntheentiresuitefromthesuitepage,obtainingtheoverallresultsasfollows:

SummaryInthischapter,wediscoveredBehavior-drivenDevelopmentasanevolutionofTest-drivenDevelopment,whichweexaminedinpreviouschapters.

WediscussedthedrivingforcesbehindBehavior-drivenDevelopment.Weanalyzedtheconceptsservingasthefoundations,exploredtheGiven-When-Thenvocabularyidea,andintroducedFitNesseandSlimashelpfultoolsindeployingtests.

WepresentedGivWenZen,atoolbasedonFitNessethatgivesustheabilitytocreatenear-English,prose-stylescenarios,andtestthem.

WeintroducedthesetechniquesandtoolstooursampleAndroidproject.However,wearestilllimitedtotestsubjectsthataretestableundertheJVM,avoidingtheuseofAndroid-specificclassesandtheuserinterface.WewillbeexploringsomealternativestoovercomethislimitationinChapter9,AlternativeTestingTactics.

Thenextchapterdealswithadifferentaspectoftesting,concentratingonperformanceandprofiling,whichisanaturalsteptofollowafterwehaveourapplicationbehavingcorrectly,andaccordingtoourtestspecifications.

Chapter8.TestingandProfilingPerformanceInthepreviouschapters,westudiedanddevelopedtestsforourAndroidapplication.Thosetestsletusevaluatecomplianceagainstaspecificationandallowedustodeterminewhetherthesoftwarewasbehavingcorrectlyornotaccordingtotheserulesbytakingabinaryverdict,whetheritcompliedgreenornot.Ifalltestcasespass,itmeansoursoftwareisbehavingasexpected.Ifoneofthetestcasesfails,thesoftwareneedstobefixed.

Inmanyothercases,mainlyafterwehaveverifiedthatthesoftwareconformstoallthesespecifications,wewanttomoveforwardandknowhoworinwhatmannerthecriteriaaresatisfied.Atthesametime,wewouldwanttoknowhowthesystemperformsunderdifferentsituationstoanalyzeotherattributessuchasusability,speed,responsetime,andreliability.

AccordingtotheAndroiddeveloperguide(http://developer.android.com/),thesearethebestpracticeswhenitcomestodesigningourapplication:

DesigningforperformanceDesigningforresponsivenessDesigningforseamlessness

It’sextremelyimportanttofollowthesebestpracticesandtothinkaboutperformanceandresponsivenessfromtheverybeginningofthedesign.SinceourapplicationwillrunonAndroiddeviceswithlimitedcomputerpower,identifyingthetargetsforoptimizationonceourapplicationisbuilt,atleastpartially,andthenapplyingtheperformancetesting(whichwewillbediscussingsoon)canbringusbiggergains.

DonaldKnuthpopularizedthisyearsago:

“Prematureoptimizationistherootofallevil”.

Optimizations,whicharebasedonguesses,intuition,andevensuperstition,ofteninterferewiththedesignovershort-termperiods,andwithreadabilityandmaintainabilityoverlong-termperiods.Onthecontrary,micro-optimizationsarebasedonidentifyingthebottlenecksorhotspotsthatrequireoptimization,applyingthechanges,andthenbenchmarkingagaintoevaluatetheimprovementsoftheoptimization.So,thepointweareconcentratingonhereismeasuringtheexistingperformanceandtheoptimizationalternatives.

Thischapterwillintroduceaseriesofconceptsrelatedtobenchmarkingandprofiling,asfollows:

TraditionalloggingstatementmethodsCreatingAndroidperformancetestsUsingprofilingtools

MicrobenchmarksusingCaliper

YeOldeLoggemethodSometimes,thisistoosimplisticforreal-lifescenariosbutI’mnotgoingtosaythatitcouldnothelpinsomecases,mainlybecauseitsimplementationtakesminutesandyouonlyneedthelogcattextoutputtoanalyzethecase.Thiscomesinhandyduringsituationswhereyouwanttoautomateproceduresorapplycontinuousintegration,asdescribedinpreviouschapters.

Thismethodconsistsoftimingamethod(orapartofit),surroundingitbytwotimemeasures,andloggingthedifferenceattheend:

privatestaticfinalbooleanBENCHMARK_TEMPERATURE_CONVERSION=true;

@Override

publicvoidonTextChanged(CharSequenceinput,intstart,intbefore,int

count){

if(!destinationEditNumber.hasWindowFocus()

||destinationEditNumber.hasFocus()||input==null){

return;

}

Stringstr=input.toString();

if("".equals(str)){

destinationEditNumber.setText("");

return;

}

longt0;

if(BENCHMARK_TEMPERATURE_CONVERSION){

t0=System.currentTimeMillis();

}

try{

doubletemp=Double.parseDouble(str);

doubleresult=(option==Option.C2F)

?TemperatureConverter.celsiusToFahrenheit(temp)

:TemperatureConverter.fahrenheitToCelsius(temp);

StringresultString=String.format("%.2f",result);

destinationEditNumber.setNumber(result);

destinationEditNumber.setSelection(resultString.length());

}catch(NumberFormatExceptionignore){

//WARNINGthisisgeneratedwhilstnumbersarebeingentered,

//forexamplejusta'-'

//sowedon'twanttoshowtheerrorjustyet

}catch(Exceptione){

sourceEditNumber.setError("ERROR:"+e.getLocalizedMessage());

}

if(BENCHMARK_TEMPERATURE_CONVERSION){

longt=System.currentTimeMillis()-t0;

Log.v(TAG,"TemperatureConversiontook"+t

+"mstocomplete.");

}

}

Thisisverystraightforward.Wetakethetimesandlogthedifference.Forthis,weareusingtheLog.v()method,andwecanseetheoutputinthelogcatwhenweruntheapplication.YoucancontroltheexecutionofthisbenchmarkbysettingtrueorfalsetotheBENCHMARK_TEMPERATURE_CONVERSIONconstantthatyoudefinedoutsidethemethod.

WhenwelaunchtheactivitywiththeBENCHMARK_TEMPERATURE_CONVERSIONconstantsettotrueinthelogcat,wewillreceivemessagesliketheseeverytimetheconversiontakesplace:

TemperatureConversiontook5mstocomplete.

TemperatureConversiontook1mstocomplete.

TemperatureConversiontook5mstocomplete.

TimingloggerNow,theonebetterthanthisistheandroid.util.TimingLoggerAndroidclass.TheTimingLoggerobjectcanhelpyoutimeyourmethodcallswithouthavingtoworryaboutmaintainingthosetimevariablesyourself.ItalsohasahigherdegreeofaccuracythanSystem.currentTimeMillis():

privatestaticfinalStringTAG="TemperatureTag";

@Override

publicvoidonTextChanged(CharSequenceinput,intstart,intbefore,int

count){

if(!destinationEditNumber.hasWindowFocus()

||destinationEditNumber.hasFocus()||input==null){

return;

}

Stringstr=input.toString();

if("".equals(str)){

destinationEditNumber.setText("");

return;

}

TimingLoggertimings=newTimingLogger(TAG,"onTextChanged");

timings.addSplit("startingconversion");

try{

doubletemp=Double.parseDouble(str);

doubleresult=(option==Option.C2F)

?TemperatureConverter.celsiusToFahrenheit(temp)

:TemperatureConverter.fahrenheitToCelsius(temp);

StringresultString=String.format("%.2f",result);

destinationEditNumber.setNumber(result);

destinationEditNumber.setSelection(resultString.length());

}catch(NumberFormatExceptionignore){

//WARNINGthisisgeneratedwhilstnumbersarebeingentered,

//forexamplejusta'-'

//sowedon'twanttoshowtheerrorjustyet

}catch(Exceptione){

sourceEditNumber.setError("ERROR:"+e.getLocalizedMessage());

}

timings.addSplit("finishconversion");

timings.dumpToLog();

}

Ifyoulaunchtheapplicationnow,youwillnoticethatnothingcomesoutinyourlogcat.ThisisbecauseTimingLoggerneedsyoutoexplicitlyturnontheloggingfortheTagyoudefined.Otherwise,themethodcallswilldonothing.Fromaterminal,runthefollowingcommand:

adbshellsetproplog.tag.TemperatureTagVERBOSE

TipYoucancheckwhatlevelyourloggingtagissettowiththegetpropcommand:

adbshellgetproplog.tag.TemperatureTag

Youcanlistallotherpropertiesfromyourdeviceusingthiscommand:adbshellgetprop

Now,whenwelaunchtheapplication,wewillreceivemessagesliketheseeverytimeaconversioncompletes:

onTextChanged:begin

onTextChanged:0ms,startingconversion

onTextChanged:2ms,finishconversion

onTextChanged:end,2ms

Somethingyoushouldtakeintoaccountisthatthesebenchmark-enablingconstantsshouldnotbeenabledintheproductionbuild,asothercommonconstants,suchasDEBUGorLOGD,areused.Toavoidmistakes,youshouldintegratetheverificationoftheseconstants’valuesinthebuildprocessyouareusingforautomatedbuilds,suchasGradle.Further,personally,Iwouldremoveallbenchmarkingorverificationloggingfromthebuildbeforeitshipstoproduction—notcommentoutbutdelete.Rememberthatyoucanalwaysfinditagaininyourversioncontrolsystem,inthehistoryoronabranch.

Loggingcodeexecution’sspeedlikethisissimple,butformorecomplexperformanceissues,youmightwanttousemoredetailed—thoughmorecomplex—techniques.

PerformancetestsinAndroidSDKIfthepreviousmethodofaddinglogstatementsdoesnotsuityou,therearedifferentmethodsofgettingperformancetestresultsfromourapplication.Thisisknownasprofiling.

Whenrunninginstrumentedcode(aswithourAndroidinstrumentedtestcases),thereisnostandardwayofgettingperformancetestresultsfromanAndroidapplication,astheclassesusedbyAndroidtestsarehiddenintheAndroidSDKandonlyavailabletosystemapplications,thatis,applicationsthatarebuiltaspartofthemainbuildorsystemimage.Thisstrategyisnotavailableforus,sowearenotdiggingdeeperinthatdirection.Instead,wewillfocusonotheravailablechoices.

LaunchingtheperformancetestThesetestsarebasedonanapproachsimilartowhatwejustdiscussed,andtheyareusedbyAndroidtotestsystemapplications.Theideaistoextendandroid.app.Instrumentationtoprovideperformancesnapshots,automaticallycreatingaframeworkthatwecanevenextendtosatisfyotherneeds.Let’sunderstandbetterwhatthismeanswithasimpleexample.

CreatingtheLaunchPerformanceBaseinstrumentationOurfirststepistoextendInstrumentationtoprovidethefunctionalityweneed.Weareusinganewpackagenamedcom.blundell.tut.launchperftokeepourtestsorganized:

publicclassLaunchPerformanceBaseextendsInstrumentation{

privatestaticfinalStringTAG="LaunchPerformanceBase";

protectedBundleresults;

protectedIntentintent;

publicLaunchPerformanceBase(){

this.results=newBundle();

this.intent=newIntent(Intent.ACTION_MAIN);

this.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

setAutomaticPerformanceSnapshots();

}

/**

*Launchesintent{@link#intent},

*andwaitsforidlebeforereturning.

*/

protectedvoidlaunchApp(){

startActivitySync(intent);

waitForIdleSync();

}

@Override

publicvoidfinish(intresultCode,Bundleresults){

Log.v(TAG,"Testresults="+results);

super.finish(resultCode,results);

}

}

WeareextendingInstrumentationhere.Theconstructorinitializedthetwofieldsinthisclass:resultsandintent.Attheend,weinvokethesetAutomaticPerformanceSnapshots()method,whichisthekeyheretocreatingthisperformancetest.

ThelaunchApp()methodisinchargeofstartingthedesiredActivityandwaitingbeforereturning.

Thefinish()methodlogstheresultsreceivedandtheninvokestheInstrumentation’sfinish().

CreatingtheTemperatureConverterActivityLaunchPerformanceclassThisclasssetsuptheIntenttoinvokeTemperatureConverterActivityandfurnishtheinfrastructureprovidedbytheLaunchPerformanceBaseclasstotesttheperformanceoflaunchingourActivity:

publicclassTemperatureConverterActivityLaunchPerformance

extendsLaunchPerformanceBase{

@Override

publicvoidonCreate(Bundlearguments){

super.onCreate(arguments);

StringclassName="com.blundell.tut.TemperatureConverterActivity";

intent.setClassName(BuildConfig.APPLICATION_ID,className);

start();

}

@Override

publicvoidonStart(){

super.onStart();

launchApp();

finish(Activity.RESULT_OK,results);

}

}

Here,onCreate()callssuper.onCreate()astheAndroidlifecycledictates.ThentheIntentisset,specifyingtheclassnameandthepackage.ThenoneoftheInstrumentation’smethods,start(),iscalled.Itcreatesandstartsanewthreadinwhichtoruninstrumentation.ThisnewthreadwillmakeacalltoonStart(),whereyoucanimplementtheinstrumentation.

ThentheonStart()implementationfollows,invokinglaunchApp()andfinish().

RunningthetestsTobeabletorunthistest,weneedtodefinethespecificInstrumentationintheBuild.gradlefileoftheTemperatureConverterproject.

Thisisthesnippetofcodewehavetoaddtotheapp/build.gradle:

defaultConfig{

//othercode

testInstrumentationRunner

"com.blundell.tut.launchperf.TemperatureConverterActivityLaunchPerformance"

}

Onceeverythingisinplace,wearereadytostartrunningthetest.

First,installtheAPKthatincludesthesechanges.Then,wehaveseveraloptionstorunthetests,aswereviewedinpreviouschapters.Inthiscase,weareusingthecommandline,asitistheeasiestwayofgettingallthedetails.Ifyouonlyhaveonedeviceconnected,usethis:

$adbshellaminstrument-w

com.blundell.tut.test/com.blundell.tut.launchperf.TermeratureConverterActiv

ityLaunchPerformance

NoteIfyouareeverwonderingwhatInstrumentationtestrunnersyouhaveinstalledonyourdevice,youcanusethiscommand:adbshellpmlistinstrumentation

Wereceivethesetofresultsforthistestinthestandardoutput:

IINSTRUMENTATION_RESULT:other_pss=7866

INSTRUMENTATION_RESULT:global_alloc_count=4009

INSTRUMENTATION_RESULT:java_allocated=7271

INSTRUMENTATION_RESULT:execution_time=347

INSTRUMENTATION_RESULT:gc_invocation_count=0

INSTRUMENTATION_RESULT:native_pss=0

INSTRUMENTATION_RESULT:received_transactions=-1

INSTRUMENTATION_RESULT:other_shared_dirty=7128

INSTRUMENTATION_RESULT:native_shared_dirty=0

INSTRUMENTATION_RESULT:java_free=4845

INSTRUMENTATION_RESULT:java_size=12116

INSTRUMENTATION_RESULT:global_freed_size=155012

INSTRUMENTATION_RESULT:java_pss=1095

INSTRUMENTATION_RESULT:pre_sent_transactions=-1

INSTRUMENTATION_RESULT:java_private_dirty=884

INSTRUMENTATION_RESULT:pre_received_transactions=-1

INSTRUMENTATION_RESULT:other_private_dirty=6228

INSTRUMENTATION_RESULT:native_private_dirty=0

INSTRUMENTATION_RESULT:cpu_time=120

INSTRUMENTATION_RESULT:sent_transactions=-1

INSTRUMENTATION_RESULT:native_allocated=10430

INSTRUMENTATION_RESULT:java_shared_dirty=8360

INSTRUMENTATION_RESULT:global_freed_count=1949

INSTRUMENTATION_RESULT:native_free=14145

INSTRUMENTATION_RESULT:native_size=10430

INSTRUMENTATION_RESULT:global_alloc_size=372992

INSTRUMENTATION_CODE:-1

Wehavehighlightedtwoofthevaluesweareinterestedin:execution_timeandcpu_time.TheyaccountforthetotalexecutiontimeandtheCPUtimeusedrespectively.

Runningthistestonanemulatorincreasesthepotentialformismeasurement,becausethehostcomputerisrunningotherprocesses,whichalsotakeuptheCPU,andtheemulatordoesnotnecessarilyrepresenttheperformanceofarealpieceofhardware.

Needlesstosay,inthisandanyothercasewhereyoumeasuresomethingthatisvariableovertime,youshoulduseameasurementstrategyandrunthetestseveraltimestoobtaindifferentstatisticalvalues,suchasaverageorstandarddeviation.

UsingtheTraceviewanddmtracedumpplatformtoolsTheAndroidSDKincludesamongitsvarioustoolstwothatarespeciallyintendedtoanalyzeperformanceproblemsandprofiles,andpotentiallydeterminethetargettoapplyoptimizations.AndroidalsooffersustheDalvikDebugMonitorService(DDMS),whichcollatesthesetoolsallinoneplace.DDMScanbeopenedfromAndroidStudiobynavigatingtoTools|Android|DeviceMonitor,orfromthecommandlinewiththecommandmonitor.YoucanuseTraceviewandothertoolsinsideDDMSbyusinghandyGUIshortcuts.Here,however,wearegoingtousethecommand-lineoptionssothatyoucanunderstandthetoolsbehindtheGUI.

Thesetoolshaveanadvantageoverotheralternatives:usually,nomodificationtothesourcecodeisneededforsimplertasks.However,formorecomplexcases,someadditionsareneeded,buttheyareverysimple,aswewillseeshortly.

Ifyoudon’tneedprecisionaboutstartingandstoppingtracing,youcandriveitfromthecommandlineorAndroidStudio.Forexample,tostarttracingfromthecommandline,youcanusethefollowingcommand.Remembertoaddtheserialnumberwith–sifyouhavemultipledevicesattached:

$adbshellamstart-ncom.blundell.tut/.TemperatureConverterActivity

$adbshellamprofilecom.blundell.tutstart/mnt/sdcard/tc.trace

DosomethingsuchasenteringatemperaturevalueintheCelsiusfieldtoforceaconversion,thenrunthis:

$adbshellamprofilecom.blundell.tutstop

$adbpull/mnt/sdcard/tc.trace/tmp/tc.trace

7681KB/s(1051585bytesin0.133s)

$traceview/tmp/tc.trace

Otherwise,ifyouneedmoreprecisionaboutwhenprofilingstarts,youcanaddtheprogrammaticstyle:

@Override

publicvoidonTextChanged(CharSequenceinput,intstart,intbefore,int

count){

if(!destinationEditNumber.hasWindowFocus()

||destinationEditNumber.hasFocus()||input==null){

return;

}

Stringstr=input.toString();

if("".equals(str)){

destinationEditNumber.setText("");

return;

}

if(BENCHMARK_TEMPERATURE_CONVERSION){

Debug.startMethodTracing();

}

try{

doubletemp=Double.parseDouble(str);

doubleresult=(option==Option.C2F)

?TemperatureConverter.celsiusToFahrenheit(temp)

:TemperatureConverter.fahrenheitToCelsius(temp);

StringresultString=String.format("%.2f",result);

destinationEditNumber.setNumber(result);

destinationEditNumber.setSelection(resultString.length());

}catch(NumberFormatExceptionignore){

//WARNINGthisisgeneratedwhilstnumbersarebeingentered,

//forexamplejusta'-'

//sowedon'twanttoshowtheerrorjustyet

}catch(Exceptione){

sourceEditNumber.setError("ERROR:"+e.getLocalizedMessage());

}

if(BENCHMARK_TEMPERATURE_CONVERSION){

Debug.stopMethodTracing();

}

}

Thiswillcreateatracefile,usingthedefaultname,dmtrace.trace,ontheSDcardbyinvokingDebug.startMethodTracing(),whichstartsmethodtracingwiththedefaultlognameandbuffersize.Whenwearedone,wecallDebug.stopMethodTracing()tostoptheprofiling.

NoteRememberthatenablingprofilingreallyslowsdowntheapplicationexecution,sotheresultsshouldbeinterpretedbytheirrelativeweight,notbytheirabsolutevalues.

TobeabletowritetotheSDcard,theapplicationrequiresanandroid.permission.WRITE_EXTERNAL_STORAGEpermissiontobeaddedtothemanifest.

ForTraceviewusingDDMS,thestreamissentthroughtheJDWPconnectionstraighttoyourdevelopmentcomputer,andthepermissionisnotneeded.

Youneedtoexercisetheapplicationinordertoobtainthetracefile.Thisfileneedstobepulledtothedevelopmentcomputertobefurtheranalyzedusingtraceview:

$adbpull/mnt/sdcard/dmtrace.trace/tmp/dmtrace.trace

8491KB/s(120154bytesin0.013s)

$traceview/tmp/dmtrace.trace

Afterrunningthiscommand,thetraceview’swindowappears,displayingalltheinformationcollected,asshowninthisscreenshot:

Thetoppartofthewindowshowsthetimelinepanelandacoloredareaforeverymethod.Timeincreasestotherightalongthescale.Therearealsosmalllinesunderthecoloredrow,displayingtheextentofallthecallstotheselectedmethod.

Weprofiledasmallsegmentofourapplication,soonlythemainthreadwasrunningfromourprocess.Inthecaseswhereotherthreadsrunduringtheprofiling,thisinformationwillalsobedisplayed.Forinstance,thisshowsthatanAsyncTaskwasexecutedbythesystem.

Thebottompartshowstheprofilepanel,everymethodexecuted,anditsparent-childrelationships.Werefertocallingmethodsasparentsandthecalledmethodsaschildren.Whenclickedon,amethodexpandstoshowitsparentsandchildren.Parentsareshownwithapurplebackgroundandchildrenwithayellowbackground.

Also,thecolorselectedforthemethod,doneinaround-robinfashion,isdisplayedbeforethemethodname.

Finally,atthebottom,there’saFind:field,wherewecanenterafiltertoreducetheamountofinformationdisplayed.Forexample,ifweareinterestedindisplayingonlythemethodsinthecom.blundell.tutpackage,weshouldentercom/blundell/tut.

Clickingonacolumnwillsettheorderofthelistaccordingtothatcolumninascendingordescendingorder.

Thistableshowsyoutheavailablecolumnsandtheirdescriptions:

Column Description

Name Thenameofthemethod,includingitspackagename,intheformwejustdescribed,whichisbyusing/(slash)asthedelimiter.Also,theparametersandthereturntypearedisplayed.

InclCpuTime% Theinclusivetime,asapercentageofthetotaltime,usedbythemethod.Thisincludesallitschildren.

InclCpuTime

Theinclusivetime,inmilliseconds,usedbytheparticularmethod.Thisincludesthemethodandallitschildren.

ExclCpuTime% Theexclusivetime,asapercentageofthetotaltime,usedbythemethod.Thisexcludesallitschildren.

ExclCpuTime

Theexclusivetime,inmilliseconds.Thisisthetotaltimespentintheparticularmethod.Itexcludesallitschildren.

InclRealTime% Inclusivetimeplusthewaitingtimeoftheprocesstoexecuteasapercentage(waitingforI/O).

InclRealTime Inclusivetimeplusthewaitingtimeoftheprocesstoexecute.

ExclRealTime% Exclusivetimeplusthewaitingtimeoftheprocesstoexecuteasapercentage(waitingforI/O).

ExclRealTime Exclusivetimeplusthewaitingtimeoftheprocesstoexecute.

Calls+Recur

Calls/Total

Thiscolumnshowsthenumberofcallsfortheparticularmethodandthenumberofrecursivecalls.

Thenumberofcallscomparedwiththetotalnumberofcallsmadetothismethod.

CpuTime/Call Thetimeofeverycallinmilliseconds.

ThefinalwordonTraceviewisawordofwarning:TraceviewcurrentlydisablestheJITcompilerfromrunning,whichmaycauseTraceviewtomisattributetimetocodeblocks,whichtheJITmaybeabletowinback.Therefore,itisimperativeaftermakingchangesyouimplyfromTraceviewdata,thatyouensurethattheresultingcodeactuallyrunsfasterwhenrunwithoutTraceview.

DmtracedumpDmtracedumpisanalternativetotraceview.Itallowsyoutogenerateyourtracedatainalternativeformats,includingHTML,andalsoacall-stackdiagram,usingthetracefilesalreadygathered.Thelaterdiagramisofatreestructure,andeachnodeofthetreerepresentsonecallinthestack.

Youcanusethesametraceviewfileswehavepulledfromthedevicewiththenewcommand:

dmtracedump–t40–gdmtrace.png/tmp/dmtrace.trace

Whenrunningdmtracedump,ifyougetthedotcommandnotfounderrorandno*.pngfileoutput,itmeansyouneedtoinstallGraphViz.GraphVizcreatesthevisualgraphicaloutputofyourtrace.Youcanreadmoreaboutitanddownloaditatwww.graphviz.org.Onceitisinstalled,yourerrorshouldgoaway.

Thegraphsproducedcanbeverybig,andit’srecommendedthatyoupassadetailedbutpinpointingtracefilesothatyouroutputisdirectedtowardsyourcodeasmuchaspossible,Alternatively,aswejustdid,youcanmakeuseofthe–tparametersothatyouattempttoincludeonlythosechildnodesthattakeupafairamountofCPUtime(suchasyourforegroundappcode).Hereisasnippetofthegraphproducedfromatracewhenweenteratemperatureconversion:

ToviewyourtracedataasHTML,runthefollowing:

dmtracedump–h/tmp/dmtrace.trace>dmtrace.html

ThisalternativeHTMLviewallowsyoutonavigatearoundthedetailsofyourtraceandfilterthecallstacksofeachcall,inawaydifferentfromhowtheoriginaltraceviewGUIdoes:

Thistabledescribestheextracommand-lineargumentsyoucanusewithdmtracedump:

Command Description

-d<trace-

file-name>Carryoutacomparisonagainstthistracefileandprintthedifference.

-g<graph-out-

file-name.png>

Generatethegraphinthisfile.Technically,itmightnotgeneratePNGimages,butifyounameitsomething.png,youcanopenthefiletoseethegraph.

-hTurnontheHTMLoutput.ThiswillbeprintedonyourconsolejustasHTMLcode,soremembertopipethisoutputtoafile,suchasexample.html.

-o Dumpthetracefileinsteadofprofiling.

-s<trace-

file-name>

URLbasetothelocationofthesortableJavaScriptfile(I’mnotsurewhattheuseofthisparameteris!https://code.google.com/p/android/issues/detail?id=53468).

-t<percent>Minimumthresholdforincludingchildnodesinthegraph(thechild’sinclusivetimeasapercentageoftheparent’sinclusivetime).Ifthisoptionisnotused,thedefaultthresholdis20percent.

MicrobenchmarksBenchmarkingistheactofrunningacomputerprogramoroperationinordertocompareoperationsinawaythatproducesquantitativeresults,normallybyrunningasetoftestsandtrialsagainstthem.

Benchmarkscanbeorganizedinthefollowingtwobigcategories:

MacrobenchmarksMicrobenchmarks

Macrobenchmarksexistasameanstocomparedifferentplatformsinspecificareassuchasprocessorspeed,numberoffloating-pointoperationsperunitoftime,graphicsand3Dperformance,andsoon.Theyarenormallyusedagainsthardwarecomponents,butcanalsobeusedtotestsoftware-specificareas,suchascompileroptimizationoralgorithms.

Asopposedtothesetraditionalmacrobenchmarks,amicrobenchmarkattemptstomeasuretheperformanceofaverysmallpieceofcode,oftenasinglemethod.Theresultsobtainedareusedtochoosebetweencompetingimplementationsthatprovidethesamefunctionality,whendecidingtheoptimizationpath.

Theriskhereistomicrobenchmarksomethingdifferentthanwhatyouthinkyouaremeasuring.ThisissomethingtotakeintoaccountmainlyinthecaseofJITcompilers,asusedbyAndroid,startingwithversion2.2Froyo.TheJITcompilermaycompileandoptimizeyourmicrobenchmarkdifferentlythanthesamecodeinyourapplication.So,becautiouswhentakingyourdecision.

Thisisdifferentfromtheprofilingtacticintroducedintheprevioussection,asthisapproachdoesnotconsidertheentireapplicationbutasinglemethodoralgorithmatatime.

CalipermicrobenchmarksCaliperisGoogle’sopensourceframeworkforwriting,running,andviewingresultsofmicrobenchmarks.Therearemanyexamplesandtutorialsonitswebsiteathttp://code.google.com/p/caliper.

Caliperisendorsedondeveloper.android.comandisusedbyGoogletomeasuretheperformanceoftheAndroidprogramminglanguageitself.Weareexploringitsessentialusehere,andwillintroducemoreAndroid-relatedusageinthenextchapter.

Itscentralideaistobenchmarkmethods,mainlytounderstandhowefficienttheyare.Wemaydecidethatthisisthetargetforouroptimization,perhapsafteranalyzingtheresultsprovidedbyprofilingtheappviaTraceview.

Caliperbenchmarksuseannotationstohelpyoubuildyourtestscorrectly.BenchmarksarestructuredinafashionsimilartoJUnittests.Previously,CalipermirroredJUnit3initsconventions;forinstance,wheretestshadtostartwiththeprefixtest,benchmarksstartedwiththeprefixtime.Withthelatestversion,itislikeJUnit4whereJUnithas@Test,Caliperuses@Benchmark.Everybenchmarkthenacceptsanintparameter,usuallynamedreps,indicatingthenumberofrepetitionstobenchmarkthecodethatsitsinsidethemethod,whichissurroundedbyaloopcountingtherepetitions.

ThesetUp()methodor@Beforeannotationispresentandisusedas@BeforeExperiment.

BenchmarkingthetemperatureconverterLet’sstartbycreatinganewJavamoduleinsideourproject.Yes,thistime,itisnotanAndroidmodule—justJava.

Forconsistency,usethecom.blundell.tutpackageasthemainpackage.

Addadependencytothismoduleonyourcoremoduleinthe/benchmark/build.gradlefile.Thisallowsyoutoaccessthetemperatureconvertercode:

compileproject(':core').

Also,addtheCaliperlibraryasadependency;thisishostedonMavencentral.However,atthetimeofwritingthisbook,theversionreleasedbyGoogleisCaliper1.0-beta-1,whichdoesnotincludetheannotationswehavejustdiscussed.Ihavetriedtopokethemtofixthis,athttps://code.google.com/p/caliper/issues/detail?id=291,starthatissueifyoufeelsoinclined.Therefore,inthemeantime,anotherdeveloperhasreleasedCaliperunderhispackagetoMavencentraltoallowustouseannotations.Thisistheimportyouneed:

compile'net.trajano.caliper:caliper:1.1.1'

CreatetheTemperatureConverterBenchmarkclassthatwillbecontainingourbenchmarks:

publicclassTemperatureConverterBenchmark{

publicstaticvoidmain(String[]args){

CaliperMain.main(CelsiusToFahrenheitBenchmark.class,args);

}

publicstaticclassCelsiusToFahrenheitBenchmark{

privatestaticfinaldoubleMULTIPLIER=10;

@Param({"1","10","100"})

inttotal;

privateList<Double>temperatures=newArrayList<Double>();

@BeforeExperiment

publicvoidsetUp(){

temperatures.clear();

generateRandomTemperatures(total);

}

privatevoidgenerateRandomTemperatures(inttotal){

Randomr=newRandom(System.currentTimeMillis());

for(inti=0;i<total;i++){

doublerandomTemperature=MULTIPLIER*r.nextGaussian();

temperatures.add(randomTemperature);

}

}

@Benchmark

publicvoidtimeCelsiusToFahrenheit(intreps){

for(inti=0;i<reps;i++){

for(doublet:temperatures){

TemperatureConverter.celsiusToFahrenheit(t);

}

}

}

}

}

WehaveasetUp()methodsimilartoJUnitteststhatusethe@BeforeExperimentannotation.Itisrunbeforethebenchmarksarerun.Thismethodinitializesacollectionofrandomtemperaturesusedintheconversionbenchmark.Thesizeofthiscollectionisafieldandisannotatedherewiththe@ParamannotationsothatCaliperknowsaboutitsexistence.Caliperwillallowustoprovidethevalueofthisparameterwhenwerunthebenchmarks.However,forthisexample,wehavegiventheparamsomedefaultvaluesof"1","10","100".Thismeanswewillhaveatleastthreebenchmarks,withone,then10,andthen100valuesoftemperature.

WeuseaGaussiandistributionforthepseudo-randomtemperatures,asthiscanbeagoodmodeloftherealityofauser.

Thebenchmarkmethoditselfusesthe@Benchmarkannotationsothatcalipercanrecognizeandrunthismethod,inthistimeCelsiusToFahrenheit()instance.Insidethismethod,weloopforthenumberofrepetitionspassedtousasamethodparameter,eachtimeinvokingtheTemperatureConverter.celsiusToFahrenheit()conversion,whichisthemethodwewishtobenchmark.

RunningCaliperTorunCaliper,right-clickontheclassandselectfromthemenuandrunTemperatureConverterBenchmark.main().Ifyouwanttochangethetotalparameterfromthedefaultof1,10,100,edittherunconfiguration,andintheProgramargumentsfield,input–Dtotal=5,50,500.

Eitherway,thiswillrunthebenchmarks,andifeverythinggoeswell,wewillbepresentedwiththeresults:

Experimentselection:

Instruments:[allocation,runtime]

Userparameters:{total=[1,10,100]}

Virtualmachines:[default]

Selectiontype:Fullcartesianproduct

Thisselectionyields6experiments.

Startingtrial1of6:{instrument=allocation,

benchmarkMethod=timeCelsiusToFahrenheit,vm=default,parameters={total=1}}…

Complete!

bytes(B):min=32.00,1stqu.=32.00,median=32.00,mean=32.00,3rd

qu.=32.00,max=32.00

objects:min=1.00,1stqu.=1.00,median=1.00,mean=1.00,3rdqu.=1.00,

max=1.00

….

Startingtrial6of6:{instrument=runtime,

benchmarkMethod=timeCelsiusToFahrenheit,vm=default,parameters=

{total=100}}…Complete!

runtime(ns):min=158.09,1stqu.=159.52,median=161.16,mean=162.42,3rd

qu.=163.06,max=175.13

Executioncomplete:1.420m.

Collected81measurementsfrom:

2instrument(s)

2virtualmachine(s)

3benchmark(s)

Resultshavebeenuploaded.Viewthemat:

https://microbenchmarks.appspot.com/runs/33dcd3fc-fde7-4a37-87d9-

aa595b6c9224

Tohelpvisualizetheseresults,thereisaservicehostedonGoogleAppEngine(http://microbenchmarks.appspot.com)thatacceptsyourresultdataandletsyouvisualizeitinamuchbetterway.YoucanseethisURLintheprecedingoutput,wheretheresultshavebeenpublished.

Ifyouwishtoaccessasuiteofbenchmarks,orcollateyourresultsovertime,youcanlogintothisserverandgainanAPIkeytohelpcongregateyourresults.Onceyouhaveobtainedthiskey,itshouldbeplacedinthe~/.caliper/config.propertiesfileinyourhomedirectory,andthenexttimeyourunthebenchmarks,theresultswillbelinkedtoyourlogin.

Theconfig.propertieswilllooklikethissnippetafteryoupastedtheAPIkeyobtained:

#Caliperconfigfile

#Runwith--print-configtoseealloftheoptionsbeingapplied

#INSTRUMENTCONFIG

#instrument.micro.options.warmup=10s

#instrument.micro.options.timingInterval=500ms

#instrument.micro.options.reportedIntervals=7

#instrument.micro.options.maxRuntime=10s

#VMCONFIG

vm.args=-Xmx3g-Xms3g

#SeetheCaliperwebapptogetakeysoyoucanassociateresultswith

youraccount

results.upload.options.key=abc123-a123-123a-b123-a12312312

Theresultwillbeasfollows:

Aswellastherunspeeds,thegeneratedwebsiteshowsyoutheconfigurationoftheJVMusedtorunthetests.Theblueandredsectionsareexpandableforseeingmoreproperties,helpingyoutodetectwhentheenvironmentbeingrunonisactuallyaffectingthedifferentresultsbeingreported.

SummaryInthischapter,wedissectedtheavailablealternativesfortestingtheperformancemeasuresofourapplicationbybenchmarkingandprofilingourcode.

SomeoptionsthatshouldbeprovidedbytheAndroidSDKarenotavailableatthetimeofwritingthisbook,andthereisnowaytoimplementAndroidPerformanceTestCasesbecausesomeofthecodeishiddenintheSDK.Wevisitedandanalyzedsomeothervalidalternatives.

Amongthesealternatives,wefoundthatwecanusesimplelogstatementsormoresophisticatedcodethatextendsinstrumentation.

Subsequently,weanalyzedprofilingalternativesanddescribedandexemplifiedtheuseoftraceviewanddmtracedump.

Finally,youdiscoveredCaliper,amicrobenchmarkingtoolthathasnativesupportforAndroid.However,weintroduceditsmostbasicusage,andpostponedmorespecificAndroidandDalvikVMusageforthenextchapter.

Tobeabletoquantifyyourtestingeffortsinthenextchapter,wewillbeexecutingcoveragereportsonourcode.WewillalsointroducealternativetestinganddiscussnewupcominglibrariesandtopicsintheAndroidtestingworldtohopefullygiveyousomejumping-offpointstoexploreandcontinueonyourowntestingvoyage.

Chapter9.AlternativeTestingTacticsUptothispoint,wehaveanalyzedthemostcommonandaccessibletacticstoimplementtestinginourprojects.However,thereareafewmissingpiecesinourpuzzle,whichwe’llhopetocoverinthisfinalchapter.TheAndroidecosystemisalwaysmovingforward,withtheadventofAndroidStudioandGradle.Thetoolboxfortestingisalsoalwaysbeingaddedtoo.Inthisarea,we’lllookatsomethird-partylibrariesthatcanhelpusexpandourtestingframework;suchasRobolectricforAndroidtestingontheJVM,aswellaspotentialbleedingedgeandfuturedevelopments,likeFork;imaginethreadingforyourtests.

Inthischapter,wewillbecoveringthefollowingtopics:

JacococodecoverageRobotiumTestingonhost’sJVMRobolectricFestSpoon/Fork

CodecoveragePerhapsAndroid’sAchilles’heelwouldbethelackofdocumentation,andthenumberofplacesyouhavetovisittogetthecompleteversionofwhatyouaretryingtofind,orwhat’sevenworse,inmanycasestheofficialdocumentationisincorrect,orhasnotbeenupdatedtomatchthecurrentrelease.ThedocumentationforthenewGradlebuildsystemisverysparseontheground,andthisiswheremostpeoplestartwhentryingtoreaduponcodecoverage;solet’slightupafewdarkcorners.

Codecoverageisameasureusedinsoftwaretestingthatdescribestheamountofsourcecodethatwasactuallytestedbythetestsuite,andtowhatdegree,followingsomecriteria.Ascodecoverageinspectsthecodedirectly,itisthereforeaformofwhiteboxtesting.

NoteWhite-boxtesting(alsoknownasclearboxtesting,glassboxtesting,transparentboxtesting,andstructuraltesting),isamethodoftestingsoftwarethattestsinternalstructuresorworkingsofanapplication,asopposedtoitsfunctionality(sayblack-boxtesting).

Fromtheseveraltoolsavailable,providingcodecoverageanalysisforJavaweareusingJacoco,anopen-sourcetoolkitformeasuringandreportingJavacodecoveragethatissupportedbytheAndroidproject.Theinfrastructuretostartusingitforyourownprojectsisalreadythere,therefore,minimizingtheeffortneededtoimplementit.JacocosupersedestheEMMAcodecoveragetool,whiletakingknowledgefromlessonslearnedinthisendeavor,andbeingbuiltbythesameteam.

Jacocodistinguishesitselffromothertoolsbygoingafteradistinctivefeaturecombination;supportforlarge-scaleenterprisesoftwaredevelopment,whilekeepingindividualdeveloper’sworkfastanditerative.ThisisfundamentalinaprojectthesizeofAndroid,andJacocoshinesatitsbest,providingcodecoverageforit.

JacocofeaturesJava,theAndroidGradlepluginandtheGradlebuildsystem,allhavenativesupportforJacoco.FromthelatestJacocoversionavailableatthisbook’srelease,paraphrasingitsdocumentation,themostdistinctivesetoffeaturesarethefollowing:

Jacococaninstrumentclassesforcoverageeitheroffline(beforetheyareloaded)oronthefly(usinganinstrumentingapplicationclassloader).Supportedcoveragetypes:class,method,line,branch,andinstruction.Jacococandetectwhenasinglesourcecodelineiscoveredonlypartially.Coveragestatsareaggregatedatmethod,class,package,and“allclasses”levels.Outputreporttypes:plaintext,HTML,XML.Allreporttypessupportdrill-downtoauser-controlleddetaildepth.TheHTMLreportsupportssourcecodelinking.Outputreportscanhighlightitemswithcoveragelevels,belowuser-providedthresholds.Coveragedataobtainedindifferentinstrumentationortestrunscanbemergedtogether.Jacocodoesnotrequireaccesstothesourcecodeanddegradesgracefullywithdecreasingamountsofdebuginformationavailableintheinputclasses.Jacocoisrelativelyfast;theruntimeoverheadofaddedinstrumentationissmall(5to20%),andthebytecodeinstrumentoritselfisveryfast(mostlylimitedbyfileI/Ospeed).MemoryoverheadisafewhundredbytesperJavaclass.

TemperatureconvertercodecoverageTheAndroidGradlepluginhassupportforJacococodecoverageoutofthebox.Thesetupinvolvesselectingwhichbuildflavoryouwanttoobtaincoveragereportsfor,andselectingyourJacocoversion.Wewanttoinstrumentourdebugflavorsothatwecanhavecoveragewithoutaffectingreleasecode.Undertheandroidclosure,addtheselinestoyourandroid/build.gradlefile:

android{

buildTypes{

debug{

testCoverageEnabledtrue

}

}

jacoco{

version='0.7.2.201409121644'

}

}

TheJacocoversiondoesnotactuallyhavetobeaddedhere,however,theversionofJacocoshippingwithAndroidiscurrentlybehindthelatestrelease.ThelatestversionoftheJacococoveragelibrarycanbefoundontheirGitHubpageathttps://github.com/jacoco/jacocoorMavencentral.Therefore,itisrecommendedthatyoumaketheversionexplicit.

GeneratingcodecoverageanalysisreportYouwillneedtohaveanemulatorrunningasJacocoinstrumentsyourandroidtests,andthesearerunonadevicesoanemulatorisappropriate.Whenthetestsarecomplete,acodecoveragereportisgeneratedonthedeviceandthenpulledtoyourlocalmachine.Ifyouchoosetousearealdeviceinsteadofanemulator,itwillneedtoberooted.OtherwisethispullofthereportswillfailwithaPermissionDeniedexception.

Runcodecoveragefromthecommandlineasfollows:

$./gradlewbuildcreateDebugCoverageReport

Alternatively,youcanusethiscommandifyouhavemultipleflavors:

$./gradlewbuildconnectedCheck

Thisfollowingmessageverifiesthatourtestshavebeenrunandthecoveragedataisretrieved:

:app:connectedAndroidTest

:app:createDebugCoverageReport

:app:connectedCheck

BUILDSUCCESSFUL

Thishascreatedthereportfilesinsidethe/app/build/outputs/reports/coverage/debug/directory.Ifyouusemultipleflavors,yourpathwillbeslightlydifferent.

Nowbeforewegoanyfurther,ifyouhaven’trealizedyet,wehavenotonlybeengeneratingthereportfortheAndroidappmodule,butwealsohavecodeinourJavacoremodule.Let’screateareportforthisaswell.

WithGradlehavingsupportforJacoco,weonlyneedtoapplytheJacocoplugintoourcode/build.gradlefile:

applyplugin:'jacoco''jacoco''jacoco''jacoco'''

MoreconfigurationsarepossiblewiththesameclosurethatweareusingforourAndroidmodule.DetailsofpropertiesthatcanbechangedarefoundontheGradleJacocopluginwebsiteathttp://gradle.org/docs/current/userguide/jacoco_plugin.html.

Now,ifyourunthe./gradlewcommandtasks,youshouldseeanewGradletaskthatisgenerated,jacocoTestReport.Runthistasktogeneratecodecoverageforourcoremodule:

$./gradlewjacocoTestReport

Thishascreatedthereportfilesinsidethe/core/build/reports/jacoco/test/directory.

Excellent!Nowwehavecodecoveragereportsforbothourappcodeandourcorecode.

NoteItispossibletotakebothofthesereportsandmergethemintoonefile.Youwillmost

likelyhavetoworkwiththeXMLoutputtodothis.ThisisleftasataskforthereaderbuttakealookontheJacocowebsiteandtheGradlepluginsiteforhints(ithasbeendonebefore).

Let’sopentheappmodulesindex.htmltodisplaythecoverageanalysisreport.

Theinformationpresentedinthereportincludescoveragemetricsinawaythatallowsdrillingdownintodata,inatop-downfashion,startingwithallclasses,andgoingallthewaytothelevelofindividualmethodsandsourcelines(intheHTMLreport).

ThefundamentalcomponentofcodecoverageinJacocoisthebasicblock;allothertypesofcoveragearederivedfromthebasicblockcoverageinsomeway.Linecoverageismostlyusedtolinktothesourcecode.

ThistabledescribestheimportantpiecesofinformationintheJacococoveragereport:

Label Description

Element Thenameoftheclassorpackage.

MissedInstructions,Coverage

Avisualindicatorshowingthenumberofinstructionsnotcoveredbytests(inred),nexttothepercentageofinstructionscoveredbytests.Example:if(x=1&&y=2)wouldbetwoinstructionsbutonelineofcode.

MissedBranches,Coverage

Avisualindicatorofthenumberofbranchesnotcoveredbytests(inred),nexttothepercentageofbranchescovered.Thinkofanif/elsestatementastwobranches.

Thenumberofbranchesinamethodisagoodmeasureofitscomplexity.

Missed,Cxty

Thenumberofcomplexpaths(cyclomaticcomplexity)missed,nexttothetotalcomplexity.Acomplexitypathisdefinedasasequenceofbytecodeinstructions,withoutanyjumpsorjumptargets.Addingabranchtothecode(anifstatement)wouldaddtwopathways(trueorfalse),thusmakingthecomplexityincreaseby1.However,addinganinstruction(x=1;)wouldnotincreasethecomplexity.

Missed,Lines Thenumberoflinesnotexecutedbyanytest,nexttothetotalnumberoflines.

Missed,Methods

Thenumberofmethodsmissed,nexttothetotalnumberofmethods.ThisisabasicJavamethodthatiscomposedbyagivennumberofbasicpaths.

Missed,Classes Thenumberofclasseswithoutasingletest,nexttothetotalnumberofclasses.

Wecandrill-downfromthepackagetoclasses,tospecificmethods,andthelinescoveredarepresentedingreen,uncoveredonesappearinred,whilepartiallycoveredonesareinyellow.

Thisisanexampleofthereportforthecore/TemperatureConverterclass:

Inthisreport,wecanseethattheclassTemperatureConverterisnot100%covered.Whenwelookintothecode,itistheconstructorthatisnevertested.

Doyouknowwhy?Thinkforamoment.

Yes,becauseaprivateconstructorisnevercalled.Thisisautilityclassthatisnotsupposedtobeinstantiatedatall.

Ifyoucanimaginecreatinganewclasswithjustastaticmethod,youdon’toftencreatetheprivateconstructor;itwouldbeleftasthenon-visibledefaultpublicconstructor.Inthiscase,Ihavebeenratherdiligentandwrotethisprivateconstructor,becauseIwasagoodboyscoutatthetime(andstillam!).

Wecanseeherenotonlyhowthisanalysisishelpingustotestourcodeandfindpotentialbugs,butalsotoimprovethedesign.

Onceweconsiderthisprivateconstructorasasensiblepieceofcodenottoberunningtestsupon,wecanseenowthateventhoughtheclassisnotyet100percentcoveredandthusnotgreen,wecanbeassuredthatthisconstructorwon’tbeinvokedfromanyotherclass.

Ithinkaveryimportantlessonhereis;100percentcodecoveragedoesnothavetobeyourgoal.Understandingyourdomainandthearchitectureofyourapplicationallowsyoutomakemuchmorereachableandrealisticestimatesfortheamountofcodecoveragethat:

GivesyoutheconfidencetochangecodewithoutrepercussionsGivesyoubeliefthattheproductyouwereaskedtodeliver,istheproductyouhavecreated

CoveringtheexceptionsContinuingwithourexaminationofthecoveragereportwillleadustodiscoveranotherblockthatisnotexercisedbyourcurrenttests.Theblockinquestionisthelastcatchinthefollowingtry-catchblockinapp/TemperatureConverterActivity:

try{

doubletemp=Double.parseDouble(str);

doubleresult=(option==Option.C2F)

?TemperatureConverter.celsiusToFahrenheit(temp)

:TemperatureConverter.fahrenheitToCelsius(temp);

StringresultString=String.format("%.2f",",("%.("%."",","result);

destinationEditNumber.setNumber(result);

destinationEditNumber.setSelection(resultString.length());

}catch(NumberFormatExceptionignore){

//WARNINGthisisgeneratedwhilstnumbersarebeingentered,

//forexamplejusta-''''''

//sowedon'tdon'tdon'tdon't'wanttoshowtheerrorjustyet

}catch(Exceptione){

sourceEditNumber.setError("ERROR:"+e.getLocalizedMessage());

}

Firstthingsfirst,whyarewecatchinggenericException?Let’smakethismorespecifictotheerrorweareexpectingtohandle.Thatwaywedon’thandleexceptionswearen’texpecting,andalsoifsomeonereadsthecodetheywillknowexplicitlywhatwearetryingtodohere.

Nowweknowwhatcodeiscausingusnottohavefulltestcoverage,weknowwhatteststowritetothrowthisexceptionandupdateourtestsuiteandourJacocoreport:

}catch(InvalidTemperatureExceptione){

sourceEditNumber.setError("ERROR:"+e.getLocalizedMessage());

}

Weshouldprovideatest,orbetterapairoftests,oneforeachtemperatureunitthatgivenaninvalidtemperatureverifiesthattheerrorisdisplayed.ThefollowingisthetestinTemperatureConverterActivityTestsfortheCelsiuscase,andyoucaneasilyconvertittoprovidetheFahrenheitcase:

publicvoidtestInvalidTemperatureInCelsius()throwsThrowable{

runTestOnUiThread(newRunnable(){

@Override

publicvoidrun(){

celsiusInput.requestFocus();

}

});

getInstrumentation().waitForIdleSync();

//invalidtemplessthanABSOLUTE_ZERO_C

sendKeys("MINUS380");");");");"

Stringmsg="Expectedcelsiusinputtocontainan

error.";.";.";.";"

assertNotNull(msg,celsiusInput.getError());

}

Werequestthefocusforthefieldundertest.Aswedidbefore,weshouldachievethisbyusingaRunnableontheUIthreadotherwisewewillreceiveanexception.

Thensettheinvalidtemperatureandretrievetheerrormessagetoverifythatitisnotnull.Runningtheend-to-endprocessagain,wecanattestthatthepathisnowcovered,givingustotalcoverageasintended.

Thisistheiterativeprocessyoushouldfollowtochangeasmuchaspossibleofthecodetogreen.Aswassaidbefore,whenthelineofcodeisnotgreen,aslongasyouhaveconsideredtheoptionsandarestillconfidentinchangingothercodewhilethispathisuntested,thenthatisok.

IntroducingRobotiumOnecomponentofthevastemergingroboticfaunaisRobotium(http://robotium.org),atestframeworkcreatedtosimplifythewritingoftests,requiringminimalknowledgeoftheapplicationundertest.Robotiumismainlyorientedtowritingpowerfulandrobustautomaticblack-boxtestcasesforAndroidapplications.Itcancoverfunction,system,andacceptancetestscenarios,evenspanningmultipleAndroidactivitiesofthesameapplicationautomatically.Robotiumcanalsobeusedtotestapplicationsthatwedon’thavethesourcecodefor,orevenpre-installedapplications.

Let’sputRobotiumtoworkcreatingsomenewtestsforTemperatureConverter.Tokeepourtestsorganized,wecreateanewpackagenamedcom.blundell.tut.robotiuminourTemperatureConverterproject,undertheandroidTestdirectory.WewillinitiallybetestingTemperatureConverterActivity,itisreasonabletocallitTemperatureConverterActivityTests,eventhoughwealreadyhaveaclasswiththesamenameinanotherpackagealsoextendingActivityInstrumentationTestCase2.Afterall,thisclasswillcontaintestsforthissameActivitytoo.

AddingRobotiumLet’saddRobotiumtoourproject,we’llonlybeusingitintestcasesandsoitshouldgoonthetestcaseclasspath.Asofthiswriting,thelatestversionofRobotiumis5.2.1.Inapp/build.gradle,weaddthefollowing:

dependencies{

...

androidTestCompile('com.jayway.android.robotium:robotium-solo:5.2.1')

}

CreatingthetestcasesFromthepreviouschapters,weknowthatifwearecreatingtestcasesforanActivitythatshouldrunconnectedtothesysteminfrastructure,weshouldbaseitonActivityInstrumentationTestCase2,andthatiswhatwearegoingtodo.

ThetestFahrenheitToCelsiusConversion()testMoreorlessthetestcaseshavethesamestructureasotherInstrumentation-basedtests.ThemaindifferenceisthatweneedtoinstantiateRobotium’sSolointhetestsetUp(),andcleanupRobotiuminthetearDown():

publicclassTemperatureConverterActivityTestsextends

ActivityInstrumentationTestCase2<TemperatureConverterActivity>{

privateTemperatureConverterActivityactivity;

privateSolosolo;

publicTemperatureConverterActivityTests(){

super(TemperatureConverterActivity.class);

}

@Override

protectedvoidsetUp()throwsException{

super.setUp();

activity=getActivity();

solo=newSolo(getInstrumentation(),activity);

}

@Override

protectedvoidtearDown()throwsException{

solo.finishOpenedActivities();

super.tearDown();

}

}

ToinstantiateSolo,wehavetopassareferencetotheInstrumentationclassandtotheActivityundertest.

Ontheotherhand,tocleanupSoloweshouldcallthefinishOpenedActivities()method.ThiswillfinalizeSoloandthenfinishourActivity,andwetheninvokesuper.tearDown().

SoloprovidesavarietyofmethodstodriveUItestsandsomeassertions.Let’sstartbyre-implementingthetestFahrenheitToCelsiusConversion()thatwepreviouslyimplementedusingtheconventionalapproach,butinthiscaseusingSolofacilities:

publicvoidtestFahrenheitToCelsiusConversion(){

solo.clearEditText(CELSIUS_INPUT);

solo.clearEditText(FAHRENHEIT_INPUT);

solo.clickOnEditText(FAHRENHEIT_INPUT);

solo.enterText(FAHRENHEIT_INPUT,"32.5");

solo.clickOnEditText(CELSIUS_INPUT);

doublef=32.5;

doubleexpectedC=TemperatureConverter.fahrenheitToCelsius(f);

doubleactualC=

((EditNumber)solo.getEditText(CELSIUS_INPUT)).getNumber();

doubledelta=Math.abs(expectedC-actualC);

Stringmsg=f+"F->"+expectedC+"C"

+"""""butwas"+actualC+"C(delta"+delta+")";

assertTrue(msg,delta<0.005);

}

Thisisprettysimilar,however,thefirstdifferenceyoumayhavenoticedisthatinthiscasewearenotgettingreferencestotheUIelementsaswepreviouslydidinthesetUp()method,usingfindViewById()tolocatetheview.However,weareusingoneofthebiggestadvantagesofSolo,whichislocatingtheviewsforususingsomecriteria.Inthiscase,thecriterionistheorderinwhichtheEditTextappears.Thesolo.clearEditText(intindex)methodexpectsanintegerindexofthepositiononthescreenstartingfrom0.Consequently,weshouldaddtheseconstantstothetestcase,justlikeinouruserinterfacetheCelsiusfieldisontop,andFahrenheitbeneath:

privatestaticfinalintCELSIUS=0;

privatestaticfinalintFAHRENHEIT=1;

TheotherRobotiummethodsfollowthesameconvention,andwearesupplyingtheseconstantswhennecessary.Thistestisverysimilartotheoneincom.blundell.tut.TemperatureConverterActivityTest,butyoumayhavenoticedthatthereisasubtledifference.Robotiumislocatedatamuchhigherlevelandwedon’thavetoworryaboutasmanyinternalsorimplementationdetails;forexample,wheninourprevioustestweinvokedcelciusInput.requestFocus()totriggertheconversionmechanism,herewejustsimulatewhattheuserdoesandissueasolo.clickOnEditText(CELSIUS).

Wesimplifiedthetestsensibly,butthebiggestadvantageofusingSoloisyettocome.

TestingbetweenActivitiesAsRobotiumissituatedatamuchhigherlevel,andwedon’tdealwithimplementationdetails,itisnotourproblemifanewActivityislaunchedwhenweclickonanAndroidwidget;weonlytreatthiscasefromtheUIperspective.

Here,Iamgoingtodiscusssomefunctionalitytheoretically.Thishasnotyetbeencreatedandisleftuptotheuserasafurtherstep,ifyousowish.

Nowthatwehaveaworkingtemperatureconverter,itwouldbeniceifwecouldlettheuserdecideuptohowmanydecimalplacestheywanttoseeaconversion.Allowingtheusertochangethisoption,viaanAndroidDialog,soundslikeasensibleoption.

Ourpurposeistochangethevalueofdecimalplacespreferencetofive,andverifythatthechangeactuallytookplace.BecauseofthehighlevelofRobotium,thistestisreadableandunderstandablewithoutactuallyhavingthefunctionalityimplemented.ThisisanexampleofaBDDapproachtoimplementthisfeature.

Thefollowingcodesnippetillustratesthedetailsofthetest:

publicfinalvoidtestClickOptionMenuSavesDecimalPreference(){

intdecimalPlaces=5;

StringnumberRegEx="^[0-9]+$";

solo.sendKey(Solo.MENU);

solo.clickOnText("Preferences");

solo.clickOnText("Decimalplaces");

assertTrue(solo.searchText(numberRegEx));

solo.clearEditText(DECIMAL_PLACES);

assertFalse(solo.searchText(numberRegEx));

solo.enterText(DECIMAL_PLACES,Integer.toString(decimalPlaces));

solo.clickOnButton("OK");

solo.goBack();

solo.sendKey(Solo.MENU);

solo.clickOnText("Preferences");

solo.clickOnText("Decimalplaces");

assertTrue(solo.searchText(numberRegEx));

inteditTextDecimalPlaces=

Integer.parseInt(solo.getEditText(DECIMAL_PLACES)

.getText().toString());

assertEquals(decimalPlaces,editTextDecimalPlaces);

}

Therearenogorydetailsabouthowsharedpreferencesandoptionsmenusareimplemented.Weonlytestitsfunctionality.WestartbypressingtheMENUkeyandclickingonPreferences.

Wow,wejustspecifiedthemenuitemtitleandthat’sit!

ThenewActivityhasstarted,butwedon’thavetoworryaboutthatimplementationdetail.WecontinueandclickonDecimalplaces.

Weverifythatsomefieldcontaininganumber,thepriorvalueofthispreference,appeared.DoyourememberwhatIsaidaboutregularexpressions?Theyalwayscomeinhandyinonewayoranother,tomatchanydecimalintegernumber(anydigitfollowedbyzeroormoredigits).Then,weclearthefieldandverifythatitwasinfactcleared.

Weenterthestring,representingthenumberwewanttouseasapreference,5inthiscase.ClickontheOKbuttonandthepreferenceissaved.

Finally,weneedtoverifythatitactuallyhappened.Thesameprocedureisusedtogetthemenuandthefield.Finally,weverifythattheactualnumberisalreadythere.

YoumaywonderwhereDECIMAL_PLACEScomefrom.WepreviouslydefinedCELSIUSandFAHRENHEITindexconstantsforthefieldsonthescreen,andthisisthesamecase,becausethiswillbethethirdEditTextweshoulddefineinourclass:

privatestaticfinalintDECIMAL_PLACES=2;

TestscanberunfromyourIDEorthecommandline,accordingtoyourpreferences.

Testingonthehost’sJVMWeleftthissubjectfortheendofthischapter,asitseemsthisistheHolyGrailoftheAndroidplatform.

AndroidisbasedonavirtualmachinenamedDalvik,afteravillageinIceland,optimizedformobileresourceswithlimitedcapabilitiessuchasconstrainedamountofmemoryandprocessorspeed.Thusrepresentativeofamobiledevicebutcertainlyaverydifferentenvironmentthanourmemoryrichandspeedyhostcomputers,typicallyhavingplentyofmemoryandprocessorspeedtoenjoy.

Ordinarily,werunourapplicationsandtestsonanemulatorordevice.ThesetargetshaveamuchslowerrealoremulatedCPU.Thus,runningourtestsisatime-consumingactivity,mainlywhenourprojectstartstogrow.ApplyingTest-drivenDevelopmenttechniquescompelsustorunhundredsofteststoverifyeverychangeweintroduced.

NoteIt’sworthnoticingthatthistechniquecanbeusedonlyasaworkaroundduringthedevelopmentprocesstospeedthingsup,anditshouldneverreplacefinaltestingontherealplatform,asincompatibilitiesbetweentheDalvikandJavaSEruntimemayaffecttheaccuracyofthetests.

Wearehalfwaytherealreadywiththecreationofourcoremodule.NowweareintheJavaworldandfreetorunourtestsontheJVM(anduseJUnit4,comingtoanAndroidnearyousoon).Thereisone-waydependencyfromtheappAndroidmoduletothecoreJavamodule.AllowingustofreeourselvesfromtheshacklesofAndroidtesting,encumbrancewhenrunningtestsinthecoremodule.

Later,weshouldfindoutamethodthatallowsustointerceptthestandardcompilation-dexing-runningonanemulatororadevicesequence,andbeabletorunAndroidonourhostcomputerdirectly.

ComparingtheperformancegainAquickreminderaboutthespeedgainwhenrunningtheseJava-onlytestscomparedtoAndroidinstrumentationtests.

Thedistinctionisevident.Thereisnoemulatorstartup,oranydevicecommunication,andthereforethespeedgainisimportant.Analyzingtheevidence,wecanfindoutthesedifferences.

Runningalltestsinmydevelopmentcomputertakes0.005seconds;withsometeststakingsolittletimethattheyarenotevenaccountedfor,andaredisplayedas0.000seconds.

IfImovetheseteststoourappmodule,andrunthesametestsontheemulator,thismakesthehugedifferenceevident.Thesesameteststook0.443secondstorun,almost100timesmore,andthat’sahugedifferenceifyouconsiderhundredsoftestsrunning,tensoftimesaday.

Itisalsogoodtonoticethatotheradvantagesexist,besidesthespeedgain,andtheyaretheavailabilityofyearsofJava,tooling,library,andplugincreation,includingseveralmockframeworksandcodeanalysistools.

AddingAndroidtothepictureWeintentionallyleftAndroidoutsideourpicture.Let’sanalyzewhathappensifweincludeasimpleAndroidtestinsidecore.RememberthatforanAndroidtesttocompileandroid.jarfromtheSDK,itshouldalsobeaddedtothemodules’libraries.

Andhereiswhatweobtain:

java.lang.RuntimeException:Stub!

atandroid.content.Context.<init>(Context.java:4)

atandroid.content.ContextWrapper.<init>(ContextWrapper.java:5)

atandroid.app.Application.<init>(Application.java:6)

NoteAddingtheandroid.jartotheclasspathforcoreisslightlyawkwardandlongwinded.Itisnotsomethingthatisdonebydefault.ThisisagoodthingasitstopsusaccidentallyusingAndroid-specificclasseswhenwritingcodeinsidecore.

Thereasonisthatandroid.jarprovidesonlytheAPI,nottheimplementation.Allmethodshavethesameimplementation:

thrownewRuntimeException("Stub!");

IfwewanttocircumventthislimitationtotestsomeclassesoutsideoftheAndroidoperatingsystem,weshouldcreateanandroid.jarthatmockseveryclass.However,wewouldalsofindproblemsforsubclassesofAndroidclasses,likeTemperatureConverterApplication.Thiswouldbeadauntingtaskandasignificantamountofwork,soweshouldlookforanothersolution.

IntroducingRobolectricRobolectric(http://robolectric.org)isaunittestframeworkthatinterceptstheloadingofAndroidclassesandrewritesthemethodbodies.Robolectricre-definesAndroidmethodssotheyreturndefaultvalues,suchasnull,0,orfalse.Ifavailable,itforwardsmethodcallstoshadowobjects,mimickingAndroidbehavior.

Alargenumberofshadowobjectsareprovided,butthisisfarfromcompletecoverage,however,itisimprovingconstantly.Thisshouldalsoleadyoutotreatitasanevolvingopensourceproject,forwhichyoushouldbereadytocontributetomakeitbetter,butalsotodependonitwithcautionbecauseyoumaydiscoverthatwhatyouneedforyourtestshasnotbeenimplementedyet.Thisisnotinanywaytodiminishitsexistingprospects.

InstallingRobolectricRobolectriccanbeinstalledbyusingthelatestRobolectricJARfromtheMavencentralrepository.Atthetimeofthiswriting,thelatestavailableisversion2.4:

testCompile'org.robolectric:robolectric:2.4'

Usually,addingadependencyisassimpleasthisoneline,however,withRobolectricabitofjiggerypokeryisneededforittoworkwiththeGradlebuildtypes.

First,Robolectrictestsrequiretheirownmoduletorunin.Thisisnothingnew.CreateanewJavamodule,we’llcallitrobolectric-tests.Keepthepackagethesameasalwayscom.blundell.tut.Now,wehavetomodifytherobolectric-tests/build.gradle,sowecanhookRobolectricinplaceoftheandroid.jar:

defandroidModuleName=":app";

defflavor="debug"

evaluationDependsOn(androidModuleName)

applyplugin:'java'

dependencies{

defandroidModule=project(androidModuleName)

testCompileproject(path:androidModuleName,

configuration:"${flavor}Compile")

defdebugVariant=androidModule.android.applicationVariants

.find({it.name==flavor})

testCompiledebugVariant.javaCompile.classpath

testCompiledebugVariant.javaCompile.outputs.files

testCompilefiles(

androidModule.plugins.findPlugin("com.android.application")

.getBootClasspath())

testCompile'junit:junit:4.12'

testCompile'org.robolectric:robolectric:2.4'

}

Thisisabigchunkofconfigurationtotakein,let’sbreakitdownintosteps.

Firstly,wedefinethemodulenameforourAndroidapplication,andthenwenametheflavorthatwewillwanttotestagainst.

TheEvaluationDependsOnclasstellsGradletoensurethatourapplicationmoduleisevaluatedbeforeourtests,thisstopsanystrangeerrorsfromorderofexecutionquirks.

Next,weapplythejavapluginaspernormalconventionforaJavaproject.

ThedependenciesclosureiswhereweaddalloftheAndroiddependenciestoourclasspath.First,weaddtheselectedbuildvariantofourmodule,debug,thentheclasspathanditsdependencies,alsoensuringwehavesystemdependenciesfromourAndroidplugin.

Lastly,weapplyJUnit4andRobolectricastestdependencies.

NoteRemember,ifyouhavemultipleproductflavorsandbuildtypes,thenthisconfigurationneedsthefullbuildvariantaddingtothescript.Itwouldbeprettystraightforwardtoamendthisbuildscript.

AddingresourcesWhenyourunyourtests,RobolectricattemptstolookupyourAndroidManifest.xmlsoitcanfindresourcesforyourapplication,andknowaboutyourtargetSDKversion,amongotherproperties.WiththecurrentRobolectricversionandourchoiceofusingaseparatemodule,RobolectriccannotfindyourresourcesoryourAndroidmanifest.Youcanstillwritetestsandgetfeedbackwithoutthisoptionalstep,butyoumayfindsomestrangenesswhenaccessingclassesthatuseresources;forexample,R.string.hello_world,andwillgetmessageslikethisinyourconsole:

WARNING:Nomanifestfilefoundat./AndroidManifest.xml.Fallingbackto

theAndroidOSresourcesonly.Toremovethiswarning,annotateyourtest

classwith@Config(manifest=Config.NONE).

Thiscanbefixedbydoingasitsayswithan@Configannotation,orcreatingacustomtestrunnerthatspecifiesthemanifestlocationoraswechoosetodohere,creatingaconfigurationfileandaddingittoyourclasspath.Insidetherobolectric-testsmodule,createthefolder/src/test/resources,andcreateafileorg.robolectric.Config.properties.ThiswillcontainourAndroidmanifestlocation;itwillalsocontainourminimumSDKversion,aswedon’tstatethisinourmanifest.Itwillhavethesecontents:

manifest=../app/src/main/AndroidManifest.xml

emulateSdk=16

NoteRobolectricattemptstolookupyourminimumSDKinsidetheAndroidManifest.xml.However,withtheGradlebuildsystemyoudonotdeclareithere,butdeclareitintheapp/build.gradle.

WearenowsetupandreadytocreatesomeRobolectrictests!

WritingsometestsWewillgetacquaintedwithRobolectricbyreproducingsomeofthetestswewrotebefore.Onegoodexamplecanbere-writingtheEditNumbertests.Let’screateanewEditNumberTestsclass,thistimeinthenewlycreatedproject,andcopythetestsfromtheEditNumberTestsclassintheTemperatureConverterTestproject:

@RunWith(RobolectricTestRunner.class)

publicclassEditNumberTests{

privatestaticfinaldoubleDELTA=0.00001d;

privateEditNumbereditNumber;

Intheprevioussnippet,wedeclarethetestrunnerwiththe@RunWithannotation.ThenwedefinedtheeditNumberfield,toholdthereferencetotheEditNumberclass:

@Before

publicvoidsetUp()throwsException{

editNumber=newEditNumber(Robolectric.application);

editNumber.setFocusable(true);

}

Thissnippetcomprisestheusualsetup()method.InthesetUp()method,wecreatedanEditNumberwithanapplicationcontext,andthenwesetitasfocusable.Thecontextisusedtocreatetheview,andRobolectrichandlesthisforus:

@Test

publicfinalvoidtestClear(){

Stringvalue="123.45";";";";"

editNumber.setText(value);

editNumber.clear();

assertEquals("",editNumber.getText().toString());

}

@Test

publicfinalvoidtestSetNumber(){

editNumber.setNumber(123.45);

assertEquals("123.45",editNumber.getText().toString());

}

@Test

publicfinalvoidtestGetNumber(){

editNumber.setNumber(123.45);

assertEquals(123.45,editNumber.getNumber(),DELTA);

}

Inthislastsnippet,wehavethebasicteststhatarethesameastheEditNumbertestsofourpreviousexamples.

Wearehighlightingthemostimportantchanges.Thefirstoneistospecifythetestrunner

JUnitthatwilldelegatetheprocessingoftheteststo,byusingtheannotation@RunWith.Inthiscase,weneedtouseRobolectricTestRunner.classastherunner.ThenwecreateanEditTextclass,usingaRobolectricContext,asthisisaclassthatcouldn’tbeinstantiatedwithoutsomehelp.Finally,aDELTAvalueisspecifiedintestGetNumberasassertEqualssince,thefloatingpointnumberrequiresitinJUnit4.Additionally,weaddedthe@Testannotationtomarkthemethodastests.

TheothertestmethodsthatexistedintheoriginalEditNumberTestscannotbeimplemented,orsimplyfailforavarietyofreasons.Forexample,aswementionedbefore,Robolectricclassesreturndefaultvalues,suchasnull,0,false,andsoon,andthisisthecaseforEditable.Factory.getInstance(),whichreturnsnullandcausesthetesttofail;becausethereisnootherwayofcreatinganEditableobject,weareatadeadend.

Similarly,theInputFilterthatEditNumbersetsisnonfunctional.Itisfutiletocreateatestthatexpectssomebehavior.

ThealternativetotheseshortcomingswouldbetocreateShadowclasses,butthisrequiresalterationoftheRobolectricsourceandthecreationofRobolectric.shadowOf()methods.Thisprocedureisdescribedinthedocumentationthatyoumayfollow,ifyouareinterestedinapplyingthisapproachtoyourtests.

Havingidentifiedtheseissues,wecanproceedtorunthetests,andtheywillruninthehost’sJVMwithnoneedtostartorcommunicatewithanemulatorordevice.

Google’smarchonshadowsForsomereason,GoogledoesnotlikeRobolectric,they’veneveracknowledgedthatitworks,orneversaidthatit’sasolutiontoaproblem.Iftheyignorethesolution,thenthatmeanstheproblemofslowrunningtestsdoesn’texist,right.TheyseemtofeelthatRobolectricdetractsfromAndroid,andsohavekindofpubliclygivenitthecoldshoulder.Surreptitiouslypushingitawaybyignoringitsexistence,thatisupuntilnow.

Googlehascreatedexactlywhatwesaidbefore,anandroid.jarfilewithdefaultmethodimplementations.Thismeansnomorestub!errorswhenaccessingamethod.Further,theyhaveremovedallofthefinalmodifiersfromclasses,allowingmockingFrameworkstohaveafieldday.Unfortunately,atthetimeofthiswritingitisundocumented.Nosurprise!Idon’twanttogiveusagesteps,aswhileundocumentedthesewillbechangingrapidly.However,whatIwillsayis,ifGooglegotthisright,thenitmeansforthetestingscenariodescribedpreviously,Robolectricisoutofthewindow,andwecanusethestandardAndroidtestingSDKs.Thesameprincipleswillapply,andsoIthinkit’sstillvaluableifyouunderstandhowRobolectricworks.YoucanapplythisunderstandingtothefuturethatIcannot.

IntroducingFestAnotherweaponforourtestingarsenalisbettertestingassertions.Haveyounoticedhowsometimesstacktracesforfailedtestsarereallyunfriendlyand/ormysticallywrong?Theygiveyoulittleinformationabouttherealfailureandyouendupconfused,havingtoreadtheentiresourcetofathomouthowtofixtheproblem.

Asanexample,lookatthisassertion:

org.junit.Assert.assertEquals(3,myList.size());

Weareassertingthatacollectionofobjectsaftersometaskhasasizeofthree,lookatourerrormessagewhenthetestfails:

java.lang.AssertionError:

Expected:3

Actual:2

Ok,thatkindofmakessense,butit’sabitabstract.Whatitemismissingfromourlist?Iamgoingtohavetorunthetestsagaintofindout,orIcouldaddacustomerrormessage:

assertEquals("Listnotthecorrectsize"+myList,

3,myList.size());

Givingmetheerrormessage:

java.lang.AssertionError:Listnotthecorrectsize[A,B]

Expected:3

Actual:2

Thaterrormessageismuchbetter.ItshowsI’mmissingCfrommylist.However,lookingbackattheassertion,it’sgettingkindofunreadable.Sometimes,ataglance,itmightevenappearlikeIamtryingtoassertwhetherthatinitialstringisequaltotheothervars,theparameterorderingdoesnothelpatall.WhatifIhadanotherobjectthatdoesn’tsoeasilyimplementtoString?Iwouldneedtocreatemorecustomcodetoprintaniceerrormessage,probablyrepeatingmyselfquitealotwithboilerplateerrormessages.

Stopallthatworryingrightnow!TakealookathowwecandothesameassertionwithFest:

org.fest.assertions.api.assertThat(myList).hasSize(3);

Now,ourerrormessagelookslike:

java.lang.AssertionError:expectedsize:<3>butwas:<2>in:<['A','B'']>

Nice,withnoextraeffortbyus,wegetanerrormessageshowinguswhatisinthelistandhowthesizesdifferentiate.Alsolookingbackattheassertion,it’smuchmorereadableandevencodingitwaseasierwiththefluidinterface.Thisimprovesthereadabilityofourtestcode,andspeedsupdebuggingandtestfixing.

Afterdoingthischange,IcametorealizeweactuallymighthavewantedtotestthecontentsoftheList,butduetotheencumbranceoftheJUnitassertions,wewerenot

bothering.Festtotherescueoncemore:

assertThat(myList).contains("A",""B",""C");

output:

java.lang.AssertionError:expecting:

<['A',]>

tocontain:

<['A','B','C']>

butcouldnotfind:

<['C']>

ThinkhowwewouldhavehadtodothatwithJUnitassertions,andIthinkyou’llappreciatethepowerofFest.

Festcomesinmultipleflavorsforassertionsondifferentlibraries;theseincludethejavaflavor,asshownpreviously,andanAndroidflavor,whichallowsyoutodofluid-styleinterfaceassertionsonAndroidcomponents,likeViewsandFragments.HereisanexampleofJUnitassertingvisibility,andthenFest:

assertEquals(View.VISIBLE,layout.getVisibility());

assertThat(layout).isVisible();

AddingeitherlibrarytoyourprojectisjustanotherGradledependency,thelatestversionscanbefoundonMavencentral.HereistheexampleforJavafest,thelatestatthetimeofwriting:

testCompile'org'.easytesting:fest-assert-core:2.0M10'

NoteNote,thatAndroidFesthasbeenrebrandedintoAssert-Jandissplitintomanydependenciesdependingonwhatyouwanttotest.Theassertionswillworkexactlythesameway.Moreinformationandinstructionstoaddasadependencycanbefoundathttps://github.com/square/assertj-android.

IntroducingSpoonDevicefragmentationisalwaystalkedaboutaroundAndroidanditissomethingyoushouldbeconsiderateof.Thenumberofdifferentdevicesandformfactorsmeansyoureallyneedtobeconfidentthatyourapplicationrunswell,onalltheaforementioned.Spoonisrightheretohelp;Spoon(http://square.github.io/spoon)isanopen-sourceprojectthatgivesyouatestrunnerthatallowsinstrumentationteststoberunonallconnecteddevicesinparallel.Italsoallowsyoutotakescreenshotsasthetestsarerunning.Notonlydoesthisspeedupyourtestingandfeedbackcycle,italsoallowsyoutopotentiallyvisuallyseewheretestswentwrong.

Spooncanbeaddedtoyourprojectwiththisdependency:

testCompilecom.squareup.spoon:spoon-client:1.1.2

Youcanthentakescreenshotsinsideyourtests,allowingyoutoseethestateofyourapplicationwhenyouarealsoassertingbehavior:

Spoon.screenshot(activity,"max_celcius_to_fahrenheit");

Ifyoutakethescreenshotrightbeforeyourassertion,youcanusethescreenshotstohelpyoudeterminefailures.AnothercoolfeatureisSpoonwillcollateyourscreenshotsfromonetestintoananimatedGIF.soyoucanwatchthesequenceofevents.

Spoonisthenrunfromthecommandline,usingthiscommand:

$java-jarspoon-runner-1.1.2-jar-with-dependencies.jar\

--apkandroidApplicationTestGuide.apk\

--test-apkandroidApplicationTestGuideTests.apk

NoteYoucanfindyourAPKfilesinsidethe/build/folder.IfyouneedmoreinformationofusingtheAPKfilesinthisway,andtestingfromthecommandline,takealookbackatChapter7,Behavior-drivenDevelopment.

IntroducingForkAnotherhumorousnameforalibrary,butstickwithitreader,thissimilarityisnotjustaco-incidence.AftertellingyouhowamazingSpoonisatspeedingupyourtestsbyrunningallyourinstrumentationtestsinparallelonallconnecteddevices,wellherecomesFork,totellyouthatthisnaïvescheduling(theirwordsnotmine),isaburdenonyourselfandyourCI.Forkcanrunyourtestsevenfaster!

Forkincreasesyourtests’speedbyintroducingaconceptcalledDevicePools.Insimpleterms,imagineyouhadtwoidenticaldevices,whicharetwoSonyXperiaZ1srunningAndroid5.0.Forkwilltakeyourtestsuiteandsplititinhalf,runninghalfthetestsoneachdevice.Thus,itsavesyou50percentofthetestrunspeed(roughlyexcludingwarmup/setuptime).

Thesedevicepoolscomeindifferentflavorsforthingssuchas,apilevel,smallestwidth,tabletdevices,ormanualpools,whereyoudeclarethedeviceserialidyouwanttouse.Moreinformationaboutdevicepoolsandcustomparametersfortheforktaskcanbefoundathttp://goo.gl/cIm6GQ.

ForkcanbeusedwithGradle,byaddingtheplugintoyourbuildscriptandapplyingit:

buildscript{

dependencies{

classpath'com'.shazam.fork:fork-gradle-plugin:0.10.0'

}

}

applyplugin:'fork'

Now,youcanrunforktestsinsteadofyournormalinstrumentationtestswiththiscommand:

./gradlewfork

NoteIfyouhavemultipleflavorsinyourproject,youcanseewhatforktasksareavailablewiththecommand:./gradlewtasks|grepfork.

SpoonandForkarepowerfultools,andcombinednowwithyourknowledgeofinstrumentationtests,unittesting,benchmarking,andcodeanalysis,youcanputtogetherarobust,informational,andwell-roundedtestsuite,whichgivesyouconfidenceandagilitywhenitcomestowritingyourAndroidapplications.

SummaryThischapterhasbeenalittlemoreinvolvedthanpreviousones,withthesoleintentionoffacingrealisticsituationsandstate-of-the-artAndroidtesting.

WestartedbyenablingcodecoveragethroughJacoco,runningourtests,andobtainingadetailedcodecoverageanalysisreport.

Wethenusedthisreporttoimproveourtestsuite.Writingteststocovercodewewerenotawarehadnotbeentested.Thisledustobettertests,andinsomecasesimprovedthedesignoftheprojectundertest.

WeintroducedRobotium,averyusefultooltoeasethecreationoftestcasesforourAndroidapplications,andweimprovedsometestswithit.

ThenweanalyzedoneofthehottesttopicsinAndroidtesting;testingonthedevelopmenthostJVM,optimizing,andreducingconsiderablythetimeneededtorunthetests.SomethingthatishighlydesirablewhenweareapplyingTest-drivenDevelopmenttoourprocess.Withinthisscope,weanalyzedRobolectricandcreatedsometestsasdemonstrationstogetyoustartedonthesetechniques.

Toroundoffourknowledge,welookedatFestandsomecutlerythesecanhelpushavemoreexpressivetests,improvedfeedback,andamorepowerfuloveralltestsuite.

WehavereachedtheendofthisjourneythroughtheavailablemethodsandtoolsforAndroidtesting.Youshouldnowbemuchbetterpreparedtostartapplyingthistoyourownprojects.Theresultswillbevisibleassoonasyoubegintousethem.

Finally,IhopethatyouhaveenjoyedreadingthisbookasmuchasIdidwritingit.

Happytesting!

IndexA

AccessPrivateDataTestclass/Androidunittestsactivities

testing/Testingactivitiesandapplications,TestingactivitiesActivityInstrumentationTestCase2class

about/TheActivityInstrumentationTestCase2classconstructor/TheconstructorsetUpmethod/ThesetUpmethodtearDownmethod/ThetearDownmethod

ActivityInstrumentationTestCase2.getActivity()method/CreatingthefixtureActivityManager

URL/InstrumentationActivityMonitorinnerclass

about/TheActivityMonitorinnerclassexample/Example

ActivityTestCaseclassabout/TheActivityTestCaseclassscrubClassmethod/ThescrubClassmethod

Androidunittests/Androidunittests

android-test-kitURL/TestingwithEspresso

android.test.mocksubpackageMockApplicationclass/MockobjectsMockContentProviderclass/MockobjectsMockContentResolverclass/MockobjectsMockContextclass/MockobjectsMockCursorclass/MockobjectsMockDialogInterfaceclass/MockobjectsMockPackageManagerclass/MockobjectsMockResourcesclass/Mockobjects

Androidapplicationsbuildingmanually,Gradleused/BuildingAndroidapplicationsmanuallyusingGradle

AndroidEmulatorPluginabout/Creatingthejobs

AndroidEmulatorplugin/InstallingandconfiguringJenkinsAndroidproject

creating/CreatingtheAndroidprojectpackage,exploring/Packageexplorertestcase,creating/Creatingatestcase

testannotations/Testannotationstests,running/Runningtheteststests,debugging/Debuggingtests

AndroidSDKperformancetests/PerformancetestsinAndroidSDK

AndroidStudiosupport,forsystemtests/AndroidStudioandotherIDEsupporttests,running/RunningalltestsfromAndroidStudio

AndroidTestCasebaseclassabout/TheAndroidTestCasebaseclassassertActivityRequiresPermission()method/TheassertActivityRequiresPermission()methodassertWritingContentUriRequiresPermission()method/TheassertWritingContentUriRequiresPermission()method

Androidtestingframeworkabout/Androidtestingframeworkfeatures/Androidtestingframeworkinstrumentationframework/InstrumentationGradle/Gradletesttargets/Testtargets

annotations,testsabout/Testannotations@SmallTest/Testannotations@MediumTest/Testannotations@LargeTest/Testannotations@Smoke/Testannotations@FlakyTest/Testannotations@UIThreadTest/Testannotations@Suppress/Testannotations

AOSPtestsURL/TheBrowserProvidertests

applicationstesting/Testingactivitiesandapplicationsmocking/MockingapplicationsandpreferencesRenamingMockContextclass/TheRenamingMockContextclasscontexts,mocking/Mockingcontexts

Assert-JURL/IntroducingFest

assertActivityRequiresPermission()methodabout/TheassertActivityRequiresPermission()methodpackageNameparameter/DescriptionclassNameparameter/Descriptionpermissionparameter/Descriptionexample/Example

assertInsertQuery()method/TheBrowserProvidertestsassertions

about/Assertionsindepthcustommessage/Custommessagesstaticimports/StaticimportsassertAssignableFrommethod/EvenmoreassertionsassertContainsRegexmethod/EvenmoreassertionsassertContainsInAnyOrdermethod/EvenmoreassertionsassertContainsInOrdermethod/EvenmoreassertionsassertEmptymethod/EvenmoreassertionsassertEqualsmethod/EvenmoreassertionsassertMatchesRegexmethod/EvenmoreassertionsassertNotContainsRegexmethod/EvenmoreassertionsassertNotEmptymethod/EvenmoreassertionsassertNotMatchesRegexmethod/EvenmoreassertionscheckEqualsAndHashCodeMethodsmethod/Evenmoreassertions

assertLeftAlignedmethod/ViewpropertiesassertOnScreenmethod/ViewpropertiesassertReadingContentUriRequiresPermissionmethod

about/TheassertReadingContentUriRequiresPermissionmethoduri/Descriptionuriparameter/Descriptionpermissionparameter/Descriptionexample/Example

assertRightAlignedmethod/ViewpropertiesassertWritingContentUriRequiresPermission()method

about/Descriptionuriparameter/Descriptionpermissionparameter/Descriptionexample/Example

AVDcreating/CreatingAndroidVirtualDevicesrunning,fromcommandline/RunningAVDsfromthecommandlineheadlessemulator/Headlessemulatorkeyguard,disabling/Disablingthekeyguardservices,cleaningup/Cleaningupprocesses,cleaningup/Cleaningupemulator,terminating/Terminatingtheemulatorspeedingup,withHAXM/SpeedingupyourAVDwithHAXMalternatives/AlternativestotheAVD

AVDManagerabout/CreatingAndroidVirtualDevices

BBehavior-drivenDevelopment

about/FunctionaloracceptancetestsURL/Functionaloracceptancetests

Behavior-drivenDevelopment(BDD)Given/Given,When,andThenWhen/Given,When,andThenThen/Given,When,andThen

benchmarkingabout/Microbenchmarks

BrowserProvidertests/TheBrowserProvidertestsbuttons,monkey_recorder.py

about/Recordandplayback

CCalabash

URL/FunctionaloracceptancetestsCaliper

running/RunningCaliperCalipermicrobenchmarks

about/CalipermicrobenchmarksURL/Calipermicrobenchmarkstemperatureconverter,benchmarking/Benchmarkingthetemperatureconverter

codecoverageabout/Codecoverage

commandlinetests,running/Runningtestsfromthecommandline

commandline,options-r/Runningtestsfromthecommandline-e<NAME><VALUE>/Runningtestsfromthecommandline-p<FILE>/Runningtestsfromthecommandline-w/Runningtestsfromthecommandline-eunittrue/Runningspecifictestsbycategory-efunctrue/Runningspecifictestsbycategory-eperftrue/Runningspecifictestsbycategory-esize{small|medium|large}/Runningspecifictestsbycategory-eannotation<annotation-name>/Runningspecifictestsbycategorydebug/Othercommand-lineoptionspackage/Othercommand-lineoptionsclass/Othercommand-lineoptionscoverage/Othercommand-lineoptions

CompatibilityTestSuite(CTS)/Androidunittestscontentproviders

testing/Testingfiles,databases,andcontentproviderscontexts

mocking/Mockingcontextscontinuousintegration

withJenkins/ContinuousintegrationwithJenkinscustommessage

about/Custommessages

DDalvikDebugMonitorService(DDMS)/UsingtheTraceviewanddmtracedumpplatformtoolsDalvikJITcompiler

about/PerformancetestsDalvikvirtualmachine

about/Testtargets/Testingonthehost’sJVMdatabases

testing/Testingfiles,databases,andcontentprovidersdemonstrationapplication

creating/ThedemonstrationapplicationDependencyInjection(DI)/TestinglocalandremoteservicesDevicePools

about/IntroducingForkdmtracedump

using/UsingtheTraceviewanddmtracedumpplatformtools,Dmtracedumpcommand-linearguments/Dmtracedump

EEditNumberclass/TheEditNumberclassemulator

tests,running/Runningfromtheemulatorterminating/Terminatingtheemulator

emulatorconfigurationsabout/Additionalemulatorconfigurationsnetworkconditions,simulating/Simulatingnetworkconditions

EspressoURL/TestingwithEspressoused,fortestingoutsidein/TestingwithEspresso

EvaluationDependsOnclass/InstallingRobolectricexceptions

testing/Testingexceptions

FFest

URL/Staticimportsabout/IntroducingFest

filestesting/Testingfiles,databases,andcontentproviders

FitNesseURL/Functionaloracceptancetests,FitNesseabout/FitNesserunning,fromcommandline/RunningFitNessefromthecommandlineTemperatureConverterTestssubwiki,creating/CreatingaTemperatureConverterTestssubwikitestsystems/Addingthesupportingtestclasses

fittestsystem/AddingthesupportingtestclassesFork

about/IntroducingForkfunctionality,adding

about/Addingfunctionalitytemperatureconversion/TemperatureconversionEditNumberclass/TheEditNumberclassTemperatureConverterunittests/TheTemperatureConverterunittestsEditNumbertests/TheEditNumbertestsTemperatureChangeWatcherclass/TheTemperatureChangeWatcherclassTemperatureConvertertests/MoreTemperatureConvertertestsInputFiltertests/TheInputFiltertests

functionaloracceptancetestsabout/Functionaloracceptancetestsexample/Testcasescenario

functionaltestingabout/Testscriptingwithmonkeyrunner

GGit

about/Git–thefastversioncontrolsystemlocalGitrepository,creating/CreatingalocalGitrepository

Gitplugin/InstallingandconfiguringJenkinsGiven-When-Thenvocabulary/GivWenZenGivWenZen

about/GivWenZenURL/GivWenZenwikiURL/GivWenZentestscenario,creating/Creatingthetestscenario

GoogleGradlepluginURL/Instrumentation

GradleURL/AndroidStudioandotherIDEsupport,BuildingAndroidapplicationsmanuallyusingGradleabout/Gradle,BuildingAndroidapplicationsmanuallyusingGradleused,forrunningtests/RunningtestsusingGradlecustomannotation,creating/Creatingacustomannotationperformancetests,running/Runningperformancetestsdryrun,oftests/Dryrunused,forbuildingAndroidapplicationsmanually/BuildingAndroidapplicationsmanuallyusingGradletasks/BuildingAndroidapplicationsmanuallyusingGradle

GradleJacocopluginURL/Generatingcodecoverageanalysisreport

Gradleplugin/InstallingandconfiguringJenkinsGraphViz

URL/Dmtracedump

HHAXM

AVD,speedingupwith/SpeedingupyourAVDwithHAXMheadlessemulator/Headlessemulator

Iinstrumentation

about/InstrumentationActivityMonitorinnerclass/TheActivityMonitorinnerclass

instrumentationframeworkabout/Instrumentation

InstrumentationTestCaseclassabout/TheInstrumentationTestCaseclasslaunchActivitymethod/ThelaunchActivityandlaunchActivityWithIntentmethodslaunchActivityWithIntentmethod/ThelaunchActivityandlaunchActivityWithIntentmethodssendKeysmethod/ThesendKeysandsendRepeatedKeysmethodssendRepeatedKeysmethod/ThesendKeysandsendRepeatedKeysmethodsrunTestOnUiThreadhelpermethod/TherunTestOnUiThreadhelpermethod

integrationtestsabout/IntegrationtestsUItests/UItests

IsolatedContextclassabout/TheIsolatedContextclass

JJacoco

URL/Why,what,how,andwhentotest?about/Jacocofeaturesfeatures/Jacocofeatures

Jacococoveragereportabout/Generatingcodecoverageanalysisreport

Javatestingframeworkabout/Javatestingframework

jbehaveabout/FunctionaloracceptancetestsURL/Functionaloracceptancetests

Jenkinsabout/ContinuousintegrationwithJenkinsinstalling/InstallingandconfiguringJenkinsconfiguring/InstallingandconfiguringJenkinsURL/InstallingandconfiguringJenkinsjobs,creating/CreatingthejobsAndroidtestresults,obtaining/ObtainingAndroidtestresults

JUnitplugin/InstallingandconfiguringJenkinsJython

URL/Testscriptingwithmonkeyrunner

Kkeyguard

disabling/Disablingthekeyguard

LLaunchPerformanceBaseinstrumentation

creating/CreatingtheLaunchPerformanceBaseinstrumentationlibraries

using/Usinglibrariesintestprojectslocalandremoteservices

testing/Testinglocalandremoteservices

Mmacrobenchmarks/Microbenchmarksmemoryusage

testingfor/Testingformemoryusagemicrobenchmarks

about/MicrobenchmarksCaliper/Calipermicrobenchmarks

MockContentResolverclassabout/TheMockContentResolverclass

MockContextclassabout/AnoverviewofMockContext

MockitoURL/Extensiveuseofmockobjectsabout/Extensiveuseofmockobjectsbenefits/Extensiveuseofmockobjectsusageexample/Mockitousageexample

mockobjectsabout/Mockobjects,Mockobjectsreferencelink/MockobjectsMockContextclass/AnoverviewofMockContextIsolatedContextclass/TheIsolatedContextclassalternateroute,providingtofile/Alternateroutetofileanddatabaseoperationsalternateroute,providingtodatabaseoperations/AlternateroutetofileanddatabaseoperationsMockContentResolverclass/TheMockContentResolverclassusing/Extensiveuseofmockobjectslibraries,importing/ImportinglibrariesMockitousageexample/MockitousageexampleEditNumberfiltertests/TheEditNumberfiltertests

monkeyapplicationrunning/RunningmonkeyURL/Runningmonkeyclient-servermonkey/Theclient-servermonkey

monkeyrunnertoolused,fortestscriptingwith/Testscriptingwithmonkeyrunnerfeatures/Testscriptingwithmonkeyrunnertestscreenshots,obtaining/Gettingtestscreenshotsrecord/Recordandplaybackplayback/Recordandplayback

MoreAssertsclassabout/Evenmoreassertions

Nnetworkconditions

simulating/Simulatingnetworkconditions

Ooptions,forlatency

about/Simulatingnetworkconditionsoptions,fornetworkspeed

about/Simulatingnetworkconditionsoptions,NewItemscreen

about/Creatingthejobs

Pparsers,testing

about/TestingparsersAndroidassets/Androidassets

parsertestabout/Theparsertest

performancetestsabout/Performancetests

performancetests,AndroidSDKabout/PerformancetestsinAndroidSDKlaunching/LaunchingtheperformancetestLaunchPerformanceBaseinstrumentation/CreatingtheLaunchPerformanceBaseinstrumentationTemperatureConverterActivityLaunchPerformanceclass,creating/CreatingtheTemperatureConverterActivityLaunchPerformanceclassrunning/Runningthetests

preferencesmocking/Mockingapplicationsandpreferences

ProviderTestCase2<T>classabout/TheProviderTestCase2<T>classconstructor/Theconstructorexample/Anexample

Rregressiontesting/Testscriptingwithmonkeyrunnerregularexpressions

referencelink/EvenmoreassertionsRenamingMockContextclass/TheRenamingMockContextclassRenamingMockContextmethod/MockingcontextsRobolectric

about/IntroducingRobolectricURL/IntroducingRobolectricinstalling/InstallingRobolectricresources,adding/Addingresourcestests,writing/Writingsometestsshadows/Google’smarchonshadows

Robotiumabout/IntroducingRobotiumURL/IntroducingRobotiumadding,toproject/AddingRobotiumtestcases,creating/Creatingthetestcases

SSCM

about/Git–thefastversioncontrolsystemServiceTestCase<T>class

about/TheServiceTestCase<T>constructor/Theconstructor

setContext()method/MockingcontextssetUp()method

about/ThesetUp()method/TestingactivitiesSetUppages/Creatingthetestscenarioslimtestsystem/AddingthesupportingtestclassesSpoon

about/IntroducingSpoonURL/IntroducingSpoon

staticimportsabout/Staticimports

systemtestsabout/Systemteststypes/SystemtestsAndroidStudiosupport/AndroidStudioandotherIDEsupportotherIDEsupport/AndroidStudioandotherIDEsupport

TTDD

about/GettingstartedwithTDDadvantages/GettingstartedwithTDD,AdvantagesofTDDtestcase,writing/Writingatestcasetests,running/Runningalltestscode,refactoring/Refactoringthecoderequisites/Understandingtherequirements

tearDown()methodabout/ThetearDown()method

TemperatureChangeWatcherclass/TheTemperatureChangeWatcherclasstemperatureconverter

benchmarking/BenchmarkingthetemperatureconverterTemperatureConverterActivityLaunchPerformanceclass

creating/CreatingtheTemperatureConverterActivityLaunchPerformanceclassTemperatureConverterActivityTestsclass

creating/CreatingtheTemperatureConverterActivityTestsclassfixture,creating/Creatingthefixtureuserinterface,creating/Creatingtheuserinterfaceuserinterfacecomponentsexistence,testing/TestingtheexistenceoftheuserinterfacecomponentsIDs,defining/GettingtheIDsdefinedtestrequisites,translating/Translatingrequirementstotestsscreenlayout/Screenlayout

temperatureconverterapplicationcreating/Creatingasampleproject–thetemperatureconverter,Creatingtheprojectrequisites/Listofrequirementsuserinterfaceconceptdesign/UserinterfaceconceptdesignJavamodule,creating/CreatingaJavamoduleviewing/Viewingourfinalapplication

TemperatureConverterclass/TheTemperatureConverterunittestsTemperatureConvertercodecoverage

about/Temperatureconvertercodecoveragecodecoverageanalysisreport,generating/Generatingcodecoverageanalysisreportexceptions,covering/Coveringtheexceptions

TemperatureConverterTestssubwikicreating/CreatingaTemperatureConverterTestssubwikichildpages,adding/Addingchildpagestothesubwikiacceptancetestfixture,adding/Addingtheacceptancetestfixturesupportingtestclasses,adding/Addingthesupportingtestclasses

testAccessAnotherAppsPrivateDataIsNotPossible()method/Androidunittests

testcasecreating/Creatingatestcase

TestCasebaseclassabout/TheTestCasebaseclassdefaultconstructor/Thedefaultconstructorconstructor,naming/ThegivennameconstructorsetName()method/ThesetName()method

testcases,Robotiumcreating/CreatingthetestcasestestFahrenheitToCelsiusConversion()test/ThetestFahrenheitToCelsiusConversion()testtesting,betweenactivities/TestingbetweenActivities

testHasDefaultBookmarks()method/TheBrowserProvidertestsTestHistory/Addingthesupportingtestclassestesting,onJVM

about/Testingonthehost’sJVMperformancegain,comparing/ComparingtheperformancegainAndroid,adding/AddingAndroidtothepicture

testingtacticscodecoverage/CodecoverageTemperatureConvertercodecoverage/TemperatureconvertercodecoverageRobotium/IntroducingRobotiumtest,runningonJVM/Testingonthehost’sJVMRobolectric/IntroducingRobolectricFest/IntroducingFestSpoon/IntroducingSpoonFork/IntroducingFork

testPartialFirstTitleWord()method/TheBrowserProviderteststestrequisites,translating

emptyfields/EmptyfieldsViewproperties/Viewproperties

testsadvantages/Why,what,how,andwhentotest?considerations/Whattotesttypes/Typesoftestsrunning/Runningthetestsrunning,fromAndroidStudio/RunningalltestsfromAndroidStudiosingletestcase,runningfromASide/RunningasingletestcasefromyourIDErunning,fromemulator/Runningfromtheemulatorrunning,fromcommandline/Runningtestsfromthecommandlinealltests,running/Runningalltestsrunning,fromspecifictestcase/Runningtestsfromaspecifictestcasespecifictest,runningbyname/Runningaspecifictestbynamespecifictests,runningbycategory/Runningspecifictestsbycategory

running,Gradleused/RunningtestsusingGradledebugging/Debuggingtests

tests,considerationsactivitylifecycleevents/Activitylifecycleeventsdatabaseandfilesystemoperations/Databaseandfilesystemoperationsphysicalcharacteristics,ofdevice/Physicalcharacteristicsofthedevice

tests,typesunittests/Unittestsintegrationtests/Integrationtestsfunctionaloracceptancetests/Functionaloracceptancetestsperformancetests/Performancetestssystemtests/Systemtests

TestSuiteBuilder.FailedToCreateTestsclassabout/TheTestSuiteBuilder.FailedToCreateTestsclass

testsystems,FitNessefit/Addingthesupportingtestclassesslim/AddingthesupportingtestclassesURL/Addingthesupportingtestclasses

testtargetsabout/Testtargets

TextWatcherabout/TheTemperatureChangeWatcherclass

timingloggerabout/Timinglogger

touchmode,AndroidUIreferencelink/ThesetUpmethod

TouchUtilsclassabout/TheTouchUtilsclass

Traceviewusing/UsingtheTraceviewanddmtracedumpplatformtools

UUItests

about/UItestsunittests

about/UnittestssetUp()method/ThesetUp()methodtearDown()method/ThetearDown()methodexecution,outsideoftestmethod/Outsidethetestmethodexecution,insideoftestmethod/Insidethetestmethodassert*methods/Insidethetestmethodmockobjects/Mockobjects

VVCS

about/Git–thefastversioncontrolsystemViewassertions

about/Viewassertionsreferencelink/ViewassertionsassertBaselineAlignedmethod/ViewassertionsassertBottomAlignedmethod/ViewassertionsassertGroupContainsmethod/ViewassertionsassertGroupIntegritymethod/ViewassertionsassertGroupNotContainsmethod/ViewassertionsassertHasScreenCoordinatesmethod/ViewassertionsassertHorizontalCenterAlignedmethod/ViewassertionsassertLeftAlignedmethod/ViewassertionsassertOffScreenAbovemethod/ViewassertionsassertOffScreenBelowmethod/ViewassertionsassertOnScreenmethod/ViewassertionsassertRightAlignedmethod/ViewassertionsassertTopAlignedmethod/ViewassertionsassertVerticalCenterAlignedmethod/Viewassertions

viewstesting,inisolation/Testingviewsinisolation

Wwiki

URL/CreatingaTemperatureConverterTestssubwiki

YYeOldeLoggemethod

about/YeOldeLoggemethod

top related