learning android application testing related/pdfs and books... · 2016. 5. 31. · java testing...
Post on 17-Aug-2020
2 Views
Preview:
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