Upload
khangminh22
View
0
Download
0
Embed Size (px)
Citation preview
MasteringSoftwareTestingwithJUnit5
ComprehensiveguidetodevelophighqualityJavaapplications
BoniGarcía
MasteringSoftwareTestingwithJUnit5
Copyright©2017PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:October2017
Productionreference:1231017
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
Birmingham
B32PB,UK.
ISBN978-1-78728-573-6
www.packtpub.com
Credits
Author
BoniGarcía
CopyEditor
CharlotteCarneiro
Reviewers
LuisFernándezMuñoz
AshokKumarS
ProjectCoordinator
RitikaManoj
CommissioningEditor
SmeetThakkar
Proofreader
SafisEditing
AcquisitionEditor
NigelFernandes
Indexer
AishwaryaGangawane
ContentDevelopmentEditor
MohammedYusufImaratwale
Graphics
JasonMonteiro
TechnicalEditor
RalphRosario
ProductionCoordinator
ShraddhaFalebhai
AbouttheAuthorBoniGarcíahasaPhDdegreeoninformationandcommunicationstechnologyfromTechnicalUniversityofMadrid(UPM)inSpainsince2011.Hisdoctoraldissertationwasfocusedontestautomationforwebapplications.Heisanauthorofmorethan30researchpapers,includinginternationalconferences,journals,andmagazines.
Atthetimeofwritingthisbook,hewasworkingasaresearcheratKingJuanCarlosUniversity(URJC),andalsoasassistantprofessoratDigitalArtandTechnologyUniversity(U-tad)inSpain.Hehasparticipatedindifferentresearchinitiatives.Forinstance,heismemberofKurentoproject,wherehehascreatedatestingframeworkforWebRTCapplications.HealsoparticipatesinthecoordinationofElasTest,aprojectaimedtocreateanelasticplatformforend-to-endtestingofdifferenttypesofapplications.
Boniisanactivememberonthefreeopensourcesoftwarecommunitywithabigemphasisonsoftwaretestingandwebengineering.Amongothers,heistheownerandmaindeveloperoftheWebDriverManagerandselenium-jupiterprojects(JUnit5extensionforSelenium).
Firstofall,Iwouldliketothankmygirl,Verónica,forheressentialsupportwhilewritingthisbook.Withoutyou,thismanuscriptwouldhavebeensimplyimpossible.But,mostimportantly,thankyouforsharingyourlifewithme.Iwouldalsoliketothanktomysisters,YolyandInma,foralltheirhelp,notonlywiththisbook,butinlifeingeneral.Thankyouforbeingalwaysthere.Inaddition,Iwanttodedicatethisbooktomynieces,Andrea,Silvia,andLaura,whoarethemostamazingpeopleintheuniverse(atleastthepartIknow).Ialsowouldliketorememberheremyparents,PabloandDolores.ThereisnothingIwouldlikemorethanifyouwerehere.Iknowthatapartofyouislivingwithus,inyourdaughters,grandchildren,andmyself.Imissyousomuch.Finally,Ialsowanttodedicatethisbooktothemostimportantpersonintheworld,mylittleboy,Pablo.Son,wheneveryoureadthis,IwantyoutoknowthatIlearnedwhathappinessreallymeansbylookingintoyoureyes.Iwouldliketobelievethesewordscantravelintimeandspacetogiveyouabighuganytime.Rememberthatyourparentsloveyoumorethananything.
AbouttheReviewersLuisFernándezMuñozinthelast25yearshehasbeenservingasaProfessorandResearcheratTechnicalUniversityofMadrid(UPM)andKingJuanCarlosUniversity(URJC),bothinSpain.hehasalsocollaborateasConsultorandTrainerindifferentpublicandprivatenationalandinternationalinstitutions.Hismainexpertisecomprisesallthesoftwareengineeringdisciplines,fromcodingandtesting,toanalysis,design,architecture,andprojectmanagement,usuallyaroundC++/JavaandWeb/JEEtechnologies.
AsaConsultorinprivatecompanies,hehasparticipatedindifferentprojectsasdifferentroles,frommanagementtodevelopmentofcodeandtests.Inthedifferentareashehasworked,hehighlightcloudcomputing,combatsimulationandphysicalactivityexpertsystems,amongothers.
Additional,myentrepreneurspirittookmetobecomeintheoneofco-foundersofTSCompany,anUPMstartupwith25employeesinonlyfouryears.
HehasdevelopedmyresearchendeavorintheNaturalComputingGroup,inwhichhemadehisPhDdissertationfocusedonParallelAlgorithmsfortheApplicationofEvolutionRulesinEndomembranePSystems.Afterthat,hehasdirectedsomeothersPhDdissertations.Thankstoallofthis,hehasparticipatedinmorethan60publications,inconferences,journals,andseveralresearchprojects.
Thisversatilecareerhasgivenmetheopportunityoflearnbyreadingandlisteningtorelevantpeopleinthefield,whichisoneofhismainhobbies.Deepreflections,discussionsandbuildingthementalstructureofconceptualideasrigorously,buthisway,ishismotivationchallenge.Traveling,chatting,andventuring-withoutlosinghismind-isthewayheenjoy’sthebestinlife.
AshokKumarSisanAndroiddeveloperresidinginBangalore,India.Agadgetenthusiast,hethrivesoninnovatingandexploringthelatestgizmosinthetechspace.HehasbeendevelopingsoftwaresforallGooglerelatedtechnologies,andhealsoaGooglecertifiedAndroiddeveloper.Astrongbelieverinspirituality,heheavilycontributestotheopensourcecommunityasane-spiritualritualtoimprovehise-karma.HeregularlyconductsworkshopsaboutAndroidapplicationdevelopmentinlocalengineeringcollegesandschools.Healsoorganizesmultipletecheventsathis
organizationandheisaregularattendeeofalltheconferencesthathappenonAndroidandJavarelatedtechnologiesinthesiliconvalleyofIndia(Bengaluru).HealsorunsaYouTubechannel,calledAndroidABCD,wherehediscussesallaspectsofAndroiddevelopment,includingquicktutorials.Having4yearsofprofessionalexperience.CurrentlyworkingwithDunstTechnologiesPvtLtdasfull-timeSr.MobileEngineer.HehasextensivelyworkedonAndroidNativeapplicationsrangingfromEnterpriseapplicationstocommerceapplication.Ispendmostofmytimeinexploringbrilliantarchitecturesandlibraries.Ihaveastronginterestincodequality,testing,andautomation,andallthree.heisaspeakeratAndroidconferencesthathappen’sinBengaluru,Apartfromallofit,Iamalsoaphotographer,Astoryteller.
www.PacktPub.comForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.
DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www.packtpub.com/mapt
Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
CustomerFeedbackThanksforpurchasingthisPacktbook.AtPackt,qualityisattheheartofoureditorialprocess.Tohelpusimprove,pleaseleaveusanhonestreviewonthisbook’sAmazonpageathttps://www.amazon.com/dp/1787285731.
Ifyou’dliketojoinourteamofregularreviewers,youcane-mailusatcustomerreviews@packtpub.com.WeawardourregularreviewerswithfreeeBooksandvideosinexchangefortheirvaluablefeedback.Helpusberelentlessinimprovingourproducts!
TableofContentsPreface
WhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport
DownloadingtheexamplecodeErrataPiracyQuestions
1. RetrospectiveOnSoftwareQualityAndJavaTestingSoftwarequality
QualityengineeringRequirementsandspecificationQualityAssurance
ISO/IEC-25000VerificationandValidation
SoftwaredefectsStaticanalysis
SoftwaretestingTestinglevels
UnittestingIntegrationtestingSystemtesting
TestingmethodsBlack-boxtestingWhite-boxtestingNon-functionaltesting
TestingtypesOthertestingapproaches
TestingframeworksfortheJVMJUnit3
StandardtestsinJUnit3TestexecutioninJUnit3
JUnit4StandardtestsinJUnit4TestexecutioninJUnit4AdvancedfeaturesofJUnit4
JUnitecosystem
Summary2. What’sNewInJUnit5
RoadtoJUnit5JUnit5motivation
ModularityJUnit4runnersJUnit4rules
JUnit5inceptionJUnit5community
JUnit5architectureTestEngineSPITestLauncherAPI
RunningtestsinJUnit5JupitertestswithMavenJupitertestswithGradleLegacytestswithMavenLegacytestswihGradleTheConsoleLauncherJupitertestsinJUnit4
IntelliJEclipse
TheextensionmodelofJUnit5TestlifecycleConditionalextensionpointsDependencyinjectionThird-partyextensions
Summary3. JUnit5StandardTests
TestlifecycleTestinstancelifecycleSkippingtestsDisplaynames
AssertionsJupiterassertions
GroupofassertionsAssertingexceptionsAssertingtimeouts
Third-partyassertionlibrariesTaggingandfilteringtests
FilteringtestswithMavenMavenregularsupport
FilteringtestswithGradleMeta-annotations
ConditionaltestexecutionAssumptions
NestedtestsRepeatedtestsMigrationfromJUnit4toJUnit5
RulesupportinJupiterSummary
4. SimplifyingTestingWithAdvancedJUnitFeaturesDependencyinjection
TestInfoParameterResolverRepetitionInfoParameterResolverTestReporterParameterResolver
DynamictestsTestinterfacesTesttemplatesParameterizedtests
@ValueSource@EnumSource@MethodSource@CsvSourceand@CsvFileSource@ArgumentsSourceArgumentconversion
ImplicitconversionExplicitconversion
CustomnamesJava9
JUnit5andJava9compatibilityBeyondJUnit5.0Summary
5. IntegrationOfJUnit5WithExternalFrameworksMockito
MockitoinanutshellJUnit5extensionforMockito
SpringSpringinanutshell
SpringmodulesIntroductiontoSpringTestTestingSpringBootapplications
JUnit5extensionforSpringSelenium
SeleniuminanutshellJUnit5extensionforSelenium
Cucumber
CucumberinanutshellJUnit5extensionforCucumber
DockerDockerinanutshellJUnit5extensionforDocker
AndroidAndroidinanutshellGradlepluginforJUnit5inAndroidprojects
RESTRESTinanutshellUsingRESTtestlibrarieswithJupiter
Summary6. FromRequirementsToTestCases
TheimportanceofrequirementsTestplanningTestdesign
EquivalencepartitioningBoundaryanalysisTestcoverage
SoftwaretestingprinciplesThepsychologyoftesting
Testanti-patternsCodesmells
Summary7. TestingManagement
SoftwaredevelopmentprocessesContinuousIntegration
JenkinsTravisCI
TestreportingMavenSurefireReportAllure
Defect-trackingsystemsStaticanalysisPuttingallpiecestogether
FeaturesandrequirementsDesignTests
Summary
PrefaceHumansarenotperfectthinkers.Atthetimeofthiswriting,softwareengineersarehumanbeings.Mostofthem.Forthatreason,writinghigh-quality,usefulsoftwareisareallydifficulttask.Aswewilldiscoverinthisbook,softwaretestingisoneofthemostimportantactivitiescarriedoutbysoftwareengineers(thatis,developers,programmers,ortesters)towarrantyalevelofqualityandconfidenceinagivenpieceofsoftware.
JUnitisthemostusedtestingframeworkfortheJavalanguage,andoneofthemostremarkableinsoftwareengineeringingeneral.Nowadays,JUnitismuchmorethanaunittestingframeworkforJava.Aswewilldiscover,itcanbeusedtoimplementdifferenttypesoftests(suchasunit,integration,end-to-end,oracceptancetests)usingdifferentstrategies(suchasblack-boxorwhite-box).
OnSeptember10,2017,theJUnitteamreleasedJUnit5.0.0.ThisbookismainlyfocusedonthisnewmajorreleaseofJUnit.Aswewilldiscover,JUnit5hassupposedacompleteredesignoftheJUnitframework,improvingimportantfeatures,suchasmodularization(JUnit5architectureiscompletelymodular),composability(theextensionmodelofJUnit5allowstointegratethird-partyframeworksintheJUnit5testlifecycleisaneasyway),andcompatibility(JUnit5supportstheexecutionofJUnit3and4legacytestsinthebrand-newJUnitPlatform).Allofit,followingamodernprogrammingmodelbasedonJava8andcompliantwithJava9.
Softwareengineeringinvolvesamultidisciplinarybodyofknowledgewithastrongimpetusforthechange.Thisbookprovidesacomprehensivereviewofmanydifferentaspectsrelatedtosoftwaretestingfrom,mainlyfollowinganopensourcepointofview(JUnitisopensourcefromitsinception).Inthisbook,inadditiontoJUnit,youlearnhowtousethird-partyframeworksandtechnologiesinourdevelopmentprocess,namely,Spring,Mockito,Selenium,Appium,Cucumber,Docker,Android,RESTservices,Hamcrest,Allure,Jenkins,TravisCI,Codecov,orSonarCube,amongothers.
WhatthisbookcoversChapter1,RetrospectiveOnSoftwareQualityAndJavaTesting,providesadetailedreviewofsoftwarequalityandtesting.Theobjectiveofthischapteristoclarifytheterminologyofthisdomaininanintelligibleway.Moreover,thischapterprovidesasummarythehistoryofJUnit(version3and4)andalsosomeJUnitenhancers(forexample,librariesthatcanbeusedtoextendJUnit).
Chapter2,What’sNewInJUnit5,firstintroducesthemotivationtocreateaversion5ofJUnit.Then,thischapterdescribesthemaincomponentsoftheJUnit5architecture,namely,Platform,Jupiter,andVintage.Next,wediscoverhowtorunJUnittests,forexample,usingdifferentbuildtoolssuchasMavenorGradle.Finally,thischapteristheextensionmodelofJUnit5,whichallowsextendingthecorefunctionalityofJUnit5byanythirdparty.
Chapter3,JUnit5StandardTests,givesadetaileddescriptionofthebasicfeaturesofthenewJUnit5programmingmodel.Thisprogrammingmodel,togetherwiththeextensionmodel,iscalledJupiter.Inthischapter,youlearnaboutthebasictestlifecycle,assertions,taggingandfilteringtests,conditionaltestexecution,nestedandrepeatedtests,andfinallyhowtomigratefromJUnit4.
Chapter4,SimplifyingTestingWithAdvancedJUnitFeatures,provideadetaileddescriptionoftheJUnit5features,suchasdependencyinjection,dynamictests,testinterfaces,testtemplates,parameterizedtests,compatibilitywithJava9,andplannedfeaturesfortheforJUnit5.1(notreleasedyetatthetimeofthiswriting).
Chapter5,IntegrationOfJUnit5WithExternalFrameworks,talksabouttheintegrationofJUnit5withexistingthird-partysoftware.Thisintegrationcanbedoneindifferentways.Typically,theJupiterextensionmodelshouldbeusedtointeractwithexternalframeworks.ThisisthecaseofMockito(apopularmockframework),Spring(aJavaframeworkaimedtocreatedenterpriseapplicationsbasedondependencyinjection),Docker(acontainerplatformtechnology),orSelenium(testframeworkforwebapplications).Inaddition,developerscanreusetoJupitertestlifecycletointeractwithothertechnologies,forexample,AndroidorRESTservices.
Chapter6,FromRequirementsToTestCases,providesasetofbestpracticesaimedtohelpasoftwaretestertowritemeaningfultestcases.Consideringthe
requirementsasthebasisofsoftwaretesting,thischapterprovidesacomprehensiveguideforcodingtestsavoidingtypicalmistakes(anti-patternsandcodesmell).
Chapter7,TestingManagement,isthefinalchapterofthebook,anditsobjectiveistoguidethereadertounderstandhowsoftwaretestingactivitiesaremanagedinalivingsoftwareproject.Tothataim,thischapterreviewsconceptssuchasContinuousIntegration(CI),buildservers(Jenkins,Travis),testreporting,ordefecttrackingsystems.Toconcludethebook,acompleteexampleapplicationtogetherwithdifferenttypesoftests(unit,integration,andend-to-end)ispresented.
WhatyouneedforthisbookInordertounderstandtheconceptspresentedinthisbookbetter,itishighlyrecommendedtoforktheGitHubrepository,whichcontainsthecodeexamplespresentedinthisbook(https://github.com/bonigarcia/mastering-junit5).Intheauthor’sopinion,touchingandplayingwiththecodeisessentialtoachieveaquickhands-onunderstandingoftheJUnit5testingframework.Asintroducedbefore,thelastchapterofthisbookprovidesacompleteapplicationexamplecoveringsomeofthemostimportanttopicsofthisbook.Thisapplication(calledRatemycat!)isalsoavailableonGitHub,intherepositoryhttps://github.com/bonigarcia/rate-my-cat.
Inordertoruntheseexample,youwillneedJDK8orhigher.YoucandownloadtheOracleJDKfromitswebsite:http://www.oracle.com/technetwork/java/javase/downloads/index.html.Inaddition,itishighlyrecommendedtouseanIntegratedDevelopmentEnvironment(IDE)toeasethedevelopmentandtestingprocess.Aswewilldiscoverinthisbook,atthetimeofthiswritingtherearetwoIDEsfullycompliantwithJUnit5,namely:
Eclipse4.7+(Oxygen):https://eclipse.org/ide/.IntelliJIDEA2016.2+:https://www.jetbrains.com/idea/.
IfyouprefertorunJUnit5fromthecommandline,twopossiblebuildtoolscanbeused:
Maven:https://maven.apache.org/Gradle:https://gradle.org/
WhothisbookisforThisbookistargetedforJavasoftwareengineers.Forthatreason,thispieceofliteraturetriestospeakthesamelanguagethanthereader(thatis,Java)andthereforeitisdrivenbyworkingcodeexamplesavailableontheaforementionedpublicopensourceGitHubrepositories.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“The@AfterAlland@BeforeAllmethodsareexecutedonlyonce”.
Ablockofcodeissetasfollows:packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.api.Test;
classStandardTest{
@Test
voidverySimpleTest(){
assertTrue(true);
}
}
Anycommand-lineinputoroutputiswrittenasfollows:mvntest
Newtermsandimportantwordsareshowninboldlikethis:“Compatibilityisthedegreetowhichaproduct,systemorcomponentcanexchangeinformationwithotherproducts”.
Warningsorimportantnotesappearinaboxlikethis.
Tipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook-whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,[email protected],andmentionthebook’stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilesemaileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregistertoourwebsiteusingyouremailaddressandpassword.2. HoverthemousepointerontheSUPPORTtabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou’relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/bonigarcia/mastering-junit5.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!
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.
Pleasecontactusatcopyright@packtpub.comwithalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.
RetrospectiveOnSoftwareQualityAndJavaTestingInordertomakeanapplepiefromscratch,youmustfirstinventtheuniverse.
-CarlSagan
Thewell-knowntestingframeworkJUnithascomealongwaysinceitsinceptionin1995.OnSeptember10,2017,animportantmilestoneintheprojectlifecycletookplace,i.e.thereleaseofJUnit5.0.0.BeforegoingdeepintothedetailsofJUnit5,itisworthreviewingthestatusquoofsoftwaretesting,inordertounderstandfromwherewehavecome,andwherewearegoing.Tothataim,thischapterprovidesahigh-levelreviewofthebackgroundofsoftwarequality,softwaretesting,andtestingforJava.Concretely,thechapteriscomposedofthreesections:
Softwarequality:Thefirstsectionreviewsthestatusquoinqualityengineering:Qualityassurance,ISO/IEC-2500,Verification&Validation(V&V),andsoftwaredefects(bugs).Softwaretesting:Thisisthemostcommonlyperformedactivitytoguaranteesoftwarequalityandreducethenumberofsoftwaredefects.Thissectionprovidesatheoreticalbackgroundofsoftwaretestinglevels(unit,integration,system,andacceptance),methods(black-box,white-box,andnon-functional),automatedandmanualsoftwaretesting.TestingframeworksfortheJavaVirtualMachine(JVM):ThissectionprovidesasummaryofthemainfeaturesofthelegacyversionsoftheJUnitframework(thatis,versions3and4).Finally,abriefdescriptionofalternativetestingframeworksandenhancerstoJUnitisdepicted.
SoftwarequalitySoftwareisthecollectionofcomputerprograms,relateddata,andassociateddocumentationdevelopedforaparticularcustomerorforageneralmarket.Itisanessentialpartofthemodernworld,andithasbecomepervasiveintelecommunications,utilities,commerce,culture,entertainment,andsoon.ThequestionWhatissoftwarequality?cangeneratedifferentanswers,dependingontheinvolvedpractitioner’sroleinasoftwaresystem.Therearetwomaingroupsofpeopleinvolvedinasoftwareproductorservice:
Consumers:arepeoplewhousesoftware.Inthisgroup,wecandifferentiatebetweencustomers(thatis,peopleresponsiblefortheacquisitionofsoftwareproductsorservices)andusers(thatis,peoplewhousethesoftwareproductsorservicesforvariouspurposes).Nevertheless,thedualrolesofcustomersandusersarequitecommon.Producers:arepeopleinvolvedwiththedevelopment,management,maintenance,marketing,andserviceofsoftwareproducts.
Thequalityexpectationsofconsumersarethatasoftwaresystemperformsusefulfunctionsasspecified.Forsoftwareproducers,thefundamentalqualityquestionisfulfillingtheircontractualobligationsbyproducingsoftwareproductsthatconformtotheServiceLevelAgreement(SLA).Thedefinitionofsoftwarequalitybythewell-knownsoftwareengineerRogerPressmancomprisesbothpointsofview:
Aneffectivesoftwareprocessappliedinamannerthatcreatesausefulproductthatprovidesmeasurablevalueforthosewhoproduceitandthosewhouseit.
QualityengineeringQualityengineering(alsoknownasqualitymanagement)isaprocessthatevaluates,assesses,andimprovesthequalityofsoftware.Therearethreemajorgroupsofactivitiesinthequalityengineeringprocess:
1. Qualityplanning:Thisstageestablishestheoverallqualitygoalbymanagingcustomer’sexpectationsundertheprojectcostandbudgetaryconstraints.Thisqualityplanalsoincludesthestrategy,thatis,theselectionofactivitiestoperformandtheappropriatequalitymeasurementstoprovidefeedbackandassessment.
2. QualityAssurance(QA):Thisguaranteesthatsoftwareproductsandprocessesintheprojectlifecyclemeettheirspecifiedrequirementsbyplanningandperformingasetofactivitiestoprovideadequateconfidencethatqualityisbeingbuiltintothesoftware.ThemainQAactivityisVerification&Validation,butthereareothers,suchassoftwarequalitymetrics,theuseofqualitystandards,configurationmanagement,documentationmanagement,oranexpert’sopinion.
3. Post-QA:Thesestageincludesactivitiesforqualityquantificationandimprovementmeasurement,analysis,feedback,andfollow-upactivities.Theaimoftheseactivitiesistoprovidequantitativeassessmentofproductqualityandidentificationofimprovementopportunities.
Thesephasesarerepresentedinthefollowingchart:
SoftwareQualityEngineeringProcess
RequirementsandspecificationRequirementsareakeytopicinthequalityengineeringdomain.Arequirementisastatementidentifyingacapability,physicalcharacteristic,orqualityfactorthatboundsaproductorprocessneedforwhichasolutionwillbepursued.Therequirementdevelopment(alsoknownasrequirementsengineering)istheprocessofproducingandanalyzingcustomer,product,andproduct-componentrequirements.Thesetofproceduresthatsupportthedevelopmentofrequirements,includingplanning,traceability,impactanalysis,changemanagement,andsoon,isknownasrequirementsmanagement.Therearetwokindsofsoftwarerequirements:
Functionalrequirementsareactionsthattheproductmustdotobeusefultoitsusers.Theyarisefromtheworkthatstakeholdersneedtodo.Almostanyactionsuchas,inspecting,publishing,ormostotheractiveverbscanbeafunctionalrequirement.Non-functionalrequirementsareproperties,orqualities,thattheproductmusthave.Forexample,theycandescribepropertiessuchasperformance,usability,orsecurity.Theyareoftencalledqualityattributes.
Anotherimportanttopicstronglylinkedwiththerequirementsisthespecification,whichisadocumentthatspecifiesinacomplete,precise,verifiablemanner,therequirements,design,behavior,orothercharacteristicsofasystem,andoftentheproceduresfordeterminingwhethertheseprovisionshavebeensatisfied.
QualityAssuranceQualityAssurance(QA)isprimarilyconcernedwithdefiningorselectingstandardsthatshouldbeappliedtothesoftwaredevelopmentprocessorsoftwareproduct.DanielGalin,theauthorofthebookSoftwareQualityAssurance(2004)definedQAas:
Systematic,plannedsetofactionsnecessarytoprovideadequateconfidencethatthesoftwaredevelopmentandmaintenanceprocessofasoftwaresystemproductconformstoestablishedspecificationaswellaswiththemanagerialrequirementsofkeepingthescheduleandoperatingwithinthebudgetaryconfines.
TheQAprocessselectstheV&Vactivities,tools,andmethodstosupporttheselectedqualitystandards.V&Visasetofactivitiescarriedoutwiththemainobjectiveofwithholdingproductsfromshipmentiftheydonotqualify.Incontrast,QAismeanttominimizethecostsofqualitybyintroducingavarietyofactivitiesthroughoutthedevelopmentandmaintenanceprocessinordertopreventthecausesoferrors,detectthem,andcorrectthemintheearlystagesofdevelopment.Asaresult,QAsubstantiallyreducestheratesofnon-qualifyingproducts.Allinall,V&VactivitiesareonlyapartofthetotalrangeofQAactivities.
ISO/IEC-25000Variousqualitystandardshavebeenproposedtoaccommodatethesedifferentqualityviewsandexpectations.ThestandardISO/IEC-9126wasoneofthemostinfluentialinthesoftwareengineeringcommunity.Nevertheless,researchersandpractitionersdetectedseveralproblemsandweaknessesinthisstandard.Forthatreason,theISO/IEC-9126internationalstandardissupersededbytheISO/IEC-25000seriesofinternationalstandardsonSoftwareproductQualityRequirementsandEvaluation(SQuaRE).Thissectionprovidesahigh-leveloverviewofthisstandard.
TheISO/IEC-2500qualityreferencemodeldistinguishesdifferentviewsonsoftwarequality:
Internalquality:Thisconcernsthepropertiesofthesystem,thatcanbemeasuredwithoutexecutingit.Externalquality:Thisconcernsthepropertiesofthesystem,thatcanbeobservedduringitsexecution.Qualityinuse:Thisconcernsthepropertiesexperiencedbyitsconsumerduringoperationandmaintenanceofthesystem.
Ideally,thedevelopment(processquality)influencestheinternalquality;then,theinternalqualitydeterminestheexternalquality.Finally,externalqualitydeterminesqualityinuse.Thischainisdepictedinthefollowingpicture:
ISO/IEC-2500ProductQualityReferenceModel
ThequalitymodelofISO/IEC-25000dividestheproductqualitymodel(thatis,theinternalandexternalattributes)intoeighttop-levelqualityfeatures:functionalsuitability,performanceefficiency,compatibility,usability,reliability,security,maintainability,andportability.Thefollowingdefinitionshavebeenextracteddirectlyfromthestandard:
Functionalsuitability:Thisrepresentsthedegreetowhichaproductorsystemprovidesfunctionsthatmeetstatedandimpliedneedswhenusedunderspecifiedconditions.Performanceefficiency:Thisrepresentstheperformancerelativetotheamountofresourcesusedunderstatedconditions.Compatibility:Thisisthedegreetowhichaproduct,systemorcomponentcanexchangeinformationwithotherproducts,systemsorcomponents,and/orperformitsrequiredfunctions,whilesharingthesamehardwareorsoftwareenvironment.Usability:Thisisthedegreetowhichaproductorsystemcanbeusedbyspecifieduserstoachievespecifiedgoalswitheffectiveness,efficiency,andsatisfactioninaspecifiedcontextofuse.Reliability:Thisisthedegreetowhichasystem,product,orcomponentperformsspecifiedfunctionsunderspecifiedconditionsforaspecifiedperiodoftime.Security:ThisisthedegreetowhichaproductorsystemprotectsinformationanddatasothatpersonsorotherproductsorsystemshavethedegreeofdataaccessappropriatetotheirtypesandlevelsofauthorizationMaintainability:Thisrepresentsthedegreeofeffectivenessandefficiencywithwhichaproductorsystemcanbemodifiedtoimproveit,correctit,oradaptittochangesinenvironmentandinrequirementsPortability:Thisisthedegreeofeffectivenessandefficiencywithwhichasystem,product,orcomponentcanbetransferredfromonehardware,software,orotheroperationalorusageenvironmenttoanother
Ontheotherhand,theattributesofqualityinusecanbecategorizedintothefollowingfivecharacteristics:
Effectiveness:Thisistheaccuracyandcompletenesswithwhichusersachievespecifiedgoals.Efficiency:Thesearetheresourcesexpendedinrelationtotheaccuracyandcompletenesswithwhichusersachievegoals.Satisfaction:Thisisthedegreetowhichuserneedsaresatisfiedwhenaproductorsystemisusedinaspecifiedcontextofuse.Freedomfromrisk:Thisisthedegreetowhichaproductorsystemmitigatesthepotentialrisktoeconomicstatus,humanlife,health,ortheenvironment.Contextcoverage:Thisisthedegreetowhichaproductorsystemcanbeusedwitheffectiveness,efficiency,freedomfromrisk,and
VerificationandValidationVerificationandValidation-alsoknownasSoftwareQualityControl-isconcernedwithevaluatingthatthesoftwarebeingdevelopedmeetsitsspecificationsanddeliversthefunctionalityexpectedbytheconsumers.Thesecheckingprocessesstartassoonasrequirementsbecomeavailable,andcontinuethroughallstagesofthedevelopmentprocess.Verificationisdifferenttovalidation,althoughtheyareoftenconfused.
ThedistinguishedprofessorofcomputerscienceBarryBoehmexpressedthedifferencebetweenthembackin1979:
Verification:arewebuildingtheproductright?Theaimofverificationistocheckthatthesoftwaremeetsitsstatedfunctionalandnon-functionalrequirements(thatis,thespecification).Validation:arewebuildingtherightproduct?Theaimofvalidationistoensurethatthesoftwaremeetsconsumer’sexpectations.Itisamoregeneralprocessthanverification,duetothefactthatspecificationsdonotalwaysreflecttherealwishesorneedsofconsumers.
V&VactivitiesincludeawidearrayofQAactivities.AlthoughsoftwaretestingplaysanextremelyimportantroleinV&V,otheractivitiesarealsonecessary.WithintheV&Vprocess,twobiggroupsoftechniquesofsystemcheckingandanalysismaybeused:
Softwaretesting:ThisisthemostcommonlyperformedactivitywithinQA.Givenapieceofcode,softwaretesting(orsimplytesting)consistsofobservingasampleofexecutions(testcases),andgivingaverdictonthem.Hence,testingisanexecution-basedQAactivity,soaprerequisiteistheexistenceoftheimplementedsoftwareunits,components,orsystemtobetested.Forthisreason,itissometimescalleddynamicanalysis.Staticanalysis:ThisisaformofV&Vthatdoesnotrequireexecutionofthesoftware.Staticanalysisworksonasourcerepresentationofthesoftware:eitheramodelofthespecificationofdesignorthesourceortheprogram.Perhaps,themostcommonlyusedareinspectionsandreviews,whereaspecification,design,orprogramischeckedbyagroupofpeople.Additionalstaticanalysistechniquesmaybeused,suchasautomatedsoftwareanalysis(thesourcecodeofaprogramischecked
forpatternsthatareknowntobepotentiallyerroneous).
Itshouldbenotedthatthereisastrongdivergenceofopinionaboutwhattypesoftestingconstitutevalidationorverification.Someauthorsbelievethatalltestingisverificationandthatvalidationisconductedwhenrequirementsarereviewedandapproved.Otherauthorsviewunitandintegrationtestingasverificationandhigher-ordertesting(forexample,systemorusertesting)asvalidation.Tosolvethisdivergence,V&Vcanbetreatedasasingletopicratherthanastwoseparatetopics.
SoftwaredefectsKeytothecorrectnessaspectofV&Vistheconceptofsoftwaredefects.Thetermdefect(alsoknownasbug)referstoagenericsoftwareproblem.TheIEEEStandard610.12proposethefollowingtaxonomyrelatedtosoftwaredefects:
Error:Ahumanactionthatproducesanincorrectresult.Errorscanbeclassifiedintotwocategories:1. Syntaxerror(programstatementthatviolatesoneormorerulesof
thelanguageinwhichitiswritten).2. Logicerror(incorrectdatafields,out-of-rangeterms,orinvalid
combinations).Fault:Themanifestationofanerrorinthesoftwaresystemisknownasafault.Forexample,anincorrectstep,process,ordatadefinition.Failure:Theinabilityofthesoftwaresystemtoperformitsrequiredfunctionsisknownas(system)failure.
Thetermbugwasfirstcoinedin1946bythesoftwarepioneerGraceHooper,whenamothtrappedinrelyofanelectromechanicalcomputercausedasystemmalfunction.Inthisdecade,thetermdebugwasalsointroduced,astheprocessofdetectingandcorrectingdefectsinasystem.
Inadditiontothislevelofgranularityfordefects,itisalsointerestingtocontemplateincidencesassymptomsassociatedwithafailureperceivedbythesoftwareconsumer.Allinall,error,faults,failures,andincidencesaredifferentaspectsofsoftwaredefects.Acausalrelationexistsbetweenthesefouraspectsofdefects.Errorsmaycausefaultstobeinjectedintothesoftware,andfaultsmaycausefailureswhenthesoftwareisexecuted.Finally,incidenceshappenwhenfailuresareexperiencedbythefinaluserorcostumer.DifferentQAactivitiescanbecarriedouttotrytominimizethenumberofdefectswithinasoftwaresystem.AsdefinedbyJeffTianinhisbookSoftwareQualityEngineering(2005),thealternativescanbegroupedintothefollowingthreegenericcategories:
Defectpreventionthrougherrorremoval:Forexample,theuseofcertainprocessesandproductstandardscanhelptominimizetheinjectioncertainkindsoffaultsintothesoftware.
Defectreductionthroughfaultdetectionandremoval:Thetraditionaltestingandstaticanalysisactivitiesareexamplesofthiscategory.Wediscoverthespecifictypesofthesemechanismsinthebodyofthischapter.Defectcontainmentthroughfailureprevention:Theseactivitiesaretypicallyoutofthescopeofthesoftwaresystem.Theobjectiveofcontainmentistominimizethedamagecausedbysoftwaresystemfailures(forexample,wallstocontainradioactivematerialincaseofreactorfailures).
SoftwaredefectchainandassociatedQAactivities
StaticanalysisStaticanalysisofasoftwarepieceisperformedwithoutexecutingthecode.Thereareseveraladvantagestosoftwareanalysisovertesting:
1. Duringtesting,errorscanhideothererrors.Thissituationdoesnothappenwithstaticanalysis,becauseitisnotconcernedwithinteractionsbetweenerrors.
2. Incompleteversionsofasystemcanbestaticallyanalyzedwithoutadditionalcost.Intesting,ifaprogramisincomplete,testharnesseshavetobedeveloped.
3. Staticanalysiscanconsiderbroaderqualityattributesofasoftwaresystem,suchascompliancewithstandards,portability,andmaintainability.
Therearedifferentmethodsthatcanbeidentifiedasstaticanalysis:
Inspection(firstproposedbyMichaelFaganin1976)areexaminationsofsoftwareartifactsbyhumaninspectorsaimedatdiscoveringandfixingfaultsinthesoftwaresystems.Allkindsofsoftwareassetsaresubjecttobeinspected,forexamplethespecification,designmodels,andsoon.Theprimaryreasonfortheexistenceofinspectionisnotwaitingfortheavailabilityofexecutableprograms(suchasintesting)beforestartingperforminginspection.Reviewistheprocessinwhichagroupofpeopleexaminethesoftwareanditsassociateddocumentation,lookingforpotentialproblemsandnon-conformancewithstandards,andotherpotentialproblemsoromissions.Nowadays,reviewsarefrequentlycarriedoutfornewcodebeforebeingmergedinasharedsourcecoderepository.Typically,thereviewisdonebyadifferentpersontothecodeauthorwithinthesameteam(peerreview).Thisprocessisquiteexpensiveintermsoftimeandeffort,butontheotherside,whencorrectlyperformed,ithelpstoensureahighinternalcodequalityreducingpotentialrisks.
Awalkthroughisaspecialformofreview.AccordingtoIEEEStandardforSoftwareReviews,awalkthroughisaformofsoftwarepeerreviewinwhichadesignerorprogrammerleadsmembersofthedevelopmentteamandotherinterestedparties
throughasoftwareproduct,andtheparticipantsaskquestionsandmakecommentsaboutpossibleerrors,violationofdevelopmentstandards,andotherproblems.
Automatedsoftwareanalysisassessesthesourcecodeusingpatternsthatareknowntobepotentiallydangerous.Thistechniqueisusuallydeliveredascommercialoropensourcetoolsandservices,commonlyknownaslintorlinter.Thesetoolscanlocatemanycommonprogrammingfaults,analyzethesourcecodebeforeitistested,andidentifypotentialproblemsinordertore-codethembeforetheymanifestthemselvesasfailures.Theintentionofthislintingprocessistodrawacodereader’sattentiontofaultsintheprogram,suchas:1. Datafaults:Thismayincludevariablesdeclaredbutneverused,
variablesassignedtwicebutneverusedbetweenassignments,andsoon.
2. Controlfaults:Thismayincludeunreachablecodeorunconditionalbranchesintoloops.
3. Input/outputfaults:Thismayincludevariablesoutputtwicewithnointerveningassignment.
4. Interfacefaults:Thismayincludeparameter-typemismatches,parameterundermismatches,non-usageoftheresultsoffunctions,uncalledfunctionsandprocedures,andsoon.
5. Storagemanagementfaults:Thismayincludeunassignedpointers,pointersarithmetic,andsoon.
Halfwaybetweenstaticanalysisanddynamictestingwefindanespecialwayofsoftwareevaluation,calledformalverification.Thiskindofassessmentprovidesmechanismstocheckthatasystemoperatesaccordingtoitsformalspecification.Tothataim,softwareistreatedasamathematicalentitywhosecorrectnesscanbeprovedusinglogicaloperations,combiningdifferenttypesofstaticanddynamicevaluation.Nowadays,formalmethodsarenotwidelyadoptedmainlyduetoscalabilityproblems.Projectsusingthesetechniquesaremostlyrelativelysmall,suchascriticalkernelsystems.Assystemsgrow,theeffortrequiredtodevelopaformalspecificationandverificationgrowexcessively.
SoftwaretestingSoftwaretestingconsistsofthedynamicevaluationofthebehaviorofaprogramonafinitesetoftestcases,suitablyselectedfromtheusuallyinfiniteexecutionsdomain,againsttheexpectedbehavior.Thekeyconceptsofthisdefinitionaredepictedasfollows:
Dynamic:TheSystemUnderTest(SUT)isexecutedwithspecificinputvaluestofindfailuresinitsbehavior.Thus,theactualSUTshouldensurethatthedesignandcodearecorrect,andalsotheenvironment,suchasthelibraries,theoperatingsystemandnetworksupport,andsoon.Finite:Exhaustivetestingisnotpossibleorpracticalformostrealprograms.Theyusuallyhavealargenumberofallowableinputstoeachoperation,plusevenmoreinvalidorunexpectedinputsandthepossiblesequencesofoperationsareusuallyinfiniteaswell.Testersmustchooseanumberoftestssothatwecanrunthetestsintheavailabletime.Selected:Sincethereisahugeorinfinitesetofpossibletestsandwecancanaffordtorunonlyasmallfractionofthem,thekeychallengeoftestingishowtoselecttheteststhataremostlikelytoexposefailuresinthesystem.Expected:Aftereachtestexecution,itmustbedecidedwhethertheobservedbehaviorofthesystemwasafailureornot.
Softwaretestingisabroadtermencompassingawidespectrumofdifferentconcepts.Thereisnouniversalclassificationforallthedifferenttestingformsavailableintheliterature.Fortheshakeofclarity,inthisbookweclassifythedifferentformoftestsusingthreeaxis,namelytestinglevel(unit,integration,system,andacceptance),testingmethods(black-box,white-box,andnon-functionaltesting),andtestingtypes(manualandautomated).
Nextsectionsprovidemoredetailsaboutalloftheseconcepts,whicharesummarizedinthefollowingdiagram:
Taxonomyofsoftwaretestinginthreecategories:levels,methods,andtypes
Forexample,aswewilldiscover,aJUnittestthatexercisesamethodinaclassaccordingtoitsfunctionalbehaviourcanbeseenasanautomatedunitblack-boxtest.Whenafinalconsumerusesasoftwareproducttovalidateifworksasexpected,accordingthetaxonomybeforewecanseethisasamanualblack-boxacceptancetest.Itshouldbenoticedthannotallpossiblecombinationofthesethreeaxesisalwaysmeaningful.Forinstance,non-functionaltests(example,performance)istypicallycarriedoutautomaticallyandatsystemlevels(itwouldbeveryunlikelytodomanuallyoratunitlevel).
TestinglevelsDependingonthesizeoftheSUTandthescenarioinwhichitisexercised,testingcanbecarriedoutatdifferentlevels.Inthisbook,weclassifythedifferenttestinglevelsinfourphases:
Unittesting:Here,individualprogramunitsaretested.Unittestingshouldfocusonthefunctionalityofobjectsormethods.Integrationtesting:Here,unitsarecombinedtocreatecompositecomponents.Integrationtestingshouldfocusontestingcomponents,interfaces.Systemtesting:Here,allofthecomponentsareintegratedandthesystemistestedasawhole.Acceptancetesting:Here,consumersdecidewhetherornotthesystemisreadytobedeployedintheconsumerenvironment.Itcanbeseenasahigh-levelfunctionaltestingperformedatsystemlevelbyfinalusersorcustomers.
Thereisnouniversalclassificationinthemanydifferentformsoftesting.Regardingtestinglevels,inthisbook,weusetheaforementionedclassificationoffourlevels.Nevertheless,otherlevelsorapproachesarepresentintheliterature(forexample,systemintegrationtestingorregressiontesting).Inthelastpartofthissection,wecanfindareviewofdifferenttestingapproaches.
Thefirstthreelevels(unit,integration,andsystem)aretypicallycarriedoutduringthedevelopmentphasesofthesoftwarelifecycle.Thesetestsaretypicallyperformedbydifferentrolesofsoftwareengineers(thatis,programmers,testers,QAteam,andsoon).Theobjectiveofthesetestsistheverificationofthesystem.Ontheotherside,thefourthlevel(acceptance)isatypeofusertesting,inwhichpotentialorrealusersareusuallyinvolved(validation).Thefollowingpictureprovidesagraphicaldescriptionoftheseconcepts:
UnittestingUnittestingisamethodbywhichindividualpiecesofsourcecodearetestedtoverifythatthedesignandimplementationforthatunithavebeencorrectlyimplemented.Therearefourphasesexecutedinsequenceinaunittestcasearethefollowing:
Setup:Thetestcaseinitializesthetestfixture,thatisthebeforepicturerequiredfortheSUTtoexhibittheexpectedbehavior.Exercise:ThetestcaseinteractswiththeSUT,gettingsomeoutcomefromitasaresult.TheSUTusuallyqueriesanothercomponent,namedtheDepended-OnComponent(DOC).Verify:Thetestcasedetermineswhethertheexpectedoutcomehasbeenobtainedusingassertions(alsoknownaspredicates).Teardown:ThetestcasetearsdownthetestfixturetoputtheSUTbackintotheinitialstate.
ThesephasesanditsrelationshipwiththeSUTandDOCisillustratedasfollows:
Unittestgenericstructure
Unittestingisdonewiththeunitundertestinisolation,thatis,withoutinteractingitsDOCs.Tothataim,testdoublesareemployedtoreplaceanycomponentsonwhichtheSUTdepends.Thereareseveralkindsoftestdoubles:
AdummyobjectsimplysatisfiestherealobjectAPIbutitisneveractuallyused.Thetypicalusecasefordummyobjectsiswhentheyarepassedasparameterstomeetthemethodsignature,butthenthedummyobjectisnotactuallyused.Afakeobjectreplacestherealobjectwithasimplerimplementation,for
example,anin-memorydatabase.Astubobjectreplacestherealobjectprovidinghard-codedvaluesasresponses.Amockobjectalsoreplacestherealobject,butthistimewithprogrammedexpectationsasresponses.Aspyobjectisapartialmockobject,meaningthatsomeofitsmethodsareprogrammedwithexpectations,buttheothersusetherealobject’simplementation.
IntegrationtestingIntegrationtestingshouldexposedefectsintheinterfaces,andtheinteractionbetweenintegratedcomponentsormodules.Therearedifferentstrategiesforperformingintegrationtesting.Thesestrategiesdescribetheorderinwhichunitsaretobeintegrated,presumingthattheunitshavebeenseparatelytested.Examplesofcommonintegrationstrategiesarethefollowing:
Top-downintegration:Thisstrategystartswiththemainunit(module),thatis,therootoftheproceduraltree.Anylower-levelmodulethatiscalledbythemainunitshouldbesubstitutedbyatestdouble.Oncetestersareconvincedthatthemainunitlogiciscorrect,thestubsaregraduallyreplacedwiththeactualcode.Thisprocessisrepeatedfortherestofthelower-unitintheproceduraltree.Themainadvantageofthisapproachisthatdefectsaremoreeasilyfound.Bottom-upintegration:Thisstrategystartsthetestingprocesswiththemostelementaryunits.Largersubsystemsareassembledfromthetestedcomponents.Themainadvantageofthistypeisthattestdoublesarenotneeded.Adhocintegration:Thecomponentsareintegratedinthenaturalorderinwhicharefinished.Itallowsanearlytestingofthesystem.Testdoublesareusuallyrequired.Backboneintegration:Askeletonofcomponentsisbuiltandothersaregraduallyintegrated.Themaindisadvantageofthisapproachisthecreationofthebackbone,whichcanbelabor-intensive.
Anotherstrategycommonlyreferredintheliteratureisbig-bangintegration.Inthisstrategy,testerswaituntilallormostoftheunitsaredevelopedeintegrated.Asaresult,allthefailuresarefoundatthesametime,makingverydifficultandtime-consumingtocorrecttheunderlyingfaults.Ifpossible,thisstrategyshouldbeavoided.
SystemtestingSystemtestingduringdevelopmentinvolvesintegratingcomponentstocreateaversionofthesystemandthetestingtheintegratedsystem.Itverifiesthatthecomponentsarecompatible,interactscorrectly,andtransfertherightdataattherighttime,topicallyacrossitsuserinterfaces.Itobviouslyoverlapswithintegrationtesting,butthedifferencehereisthatsystemtestingshouldinvolveallthesystemcomponentstogetherwiththefinaluser(typicallyimpersonated).
Thereisanspecialtypeofsystemtestingcalledend-to-endtesting.Inthisapproach,thefinaluseristypicallyimpersonated,thatis,simulatedusingautomationtechniques.
TestingmethodsTestingmethods(orstrategies)definethewayfordesigningtestcases.Theycanberesponsibilitybased(black-box),implementationbased(whitebox),ornon-functional.Black-boxtechniquesdesigntestcasesonthebasisofthespecifiedfunctionalityoftheitemtobetested.White-boxonesrelyonsourcecodeanalysistodeveloptestcases.Hybridtechniques(grey-box)testingdesignstestcasesusingbothresponsibility-basedandimplementation-basedapproaches.
Black-boxtestingBlack-boxtesting(alsoknownasfunctionalorbehavioraltesting)isbasedonrequirementswithnoknowledgeoftheinternalprogramstructureordata.Black-boxtestingreliesonthespecificationofthesystemorthecomponentthatisbeingtestedtoderivetestcases.Thesystemisablack-boxwhosebehaviorcanonlybedeterminedbystudyingitsinputsandtherelatedoutputs.Therearealotofspecificblack-boxtestingtechniques;someofthemostwell-knownonesaredescribedasfollows:
Systematictesting:ThisreferstoacompletetestingapproachinwhichSUTisshowntoconformexhaustivelytoaspecification,uptothetestingassumptions.Itgeneratestestcasesonlyinthelimitingsensethateachdomainpointisasingletonsub-domain.Insidethiscategory,someofthemostcommonlyperformedareequivalencepartitioningandboundaryvalueanalysis,andalsologic-basedtechniques,suchascause-effectgraphing,decisiontable,orpairwisetesting.Randomtesting:Thisisliterallytheantithesisofsystematictesting-thesamplingisovertheentireinputdomain-.Fuzztestingisaformofblack-boxrandomtesting,whichrandomlymutateswell-formedinputsandteststheprogramontheresultingdata.Itdeliversrandomlysequencedand/orstructurallybaddatatoasystemtoseeiffailuresoccur.GraphicUserInterface(GUI)testing:Thisistheprocessofensuringthespecificationofsoftwarewithagraphicinterfaceinteractingwiththeuser.GUItestingisevent-driven(forexample,mousemovementsormenuselections)andprovidesafrontendtotheunderlyingapplicationcodethroughmessagesormethodcalls.GUItestingatunitlevelisusedtypicallyatthebuttonlevel.GUItestingatsystemlevelexercisestheevent-drivennatureoftheSUT.Model-basedtesting(MBT):Thisisatestingstrategyinwhichtestcasesarederivedinpartfromamodelthatdescribessome(ifnotall)aspectsoftheSUT.MBTisaformofblack-boxtestingbecausetestsaregeneratedfromamodel,whichisderivedfromtherequirementsdocumentation.Itcanbedoneatdifferentlevels(unit,integration,orsystem).Smoketesting:ThisistheprocessofensuringthecriticalfunctionalityoftheSUT.Asmoketestcaseisthefirsttoberunbytestersbefore
acceptingabuildforfurthertesting.Failureofasmoketestcasewillmeanthatthesoftwarebuildisrefused.Thenameofsmoketestingderiveselectricalsystemtesting,wherebythefirsttestwastoswitchonandseeifitsmoked.
Sanitytesting:ThisistheprocessofensuringthebasicfunctionalityoftheSUT.Similarlytosmoketesting,sanitytestsareperformedatthebeginningofthetestprocess,butitsobjectiveisdifferent.SanitytestsaresupposedtoensurethattheSUTbasicfeaturescontinueworkingasexpected(i.e.therationalityoftheSUT),beforeconductingmoreexhaustivetests.
Smokeandsanitytestingareusuallyconfusingtermsinthesoftwaretestingcommunity.Itiscommonlyacceptedthatbothkindoftestsareperformedtoavoidwastingeffortinrigoroustestingwhenthesetestsfail,beingthemaindifferencetheirtarget(criticalvs.basicfunctionality).
White-boxtestingWhite-boxtesting(alsoknownasstructuraltesting)isbasedonknowledgeoftheinternallogicofanapplication’scode.Itdeterminesiftheprogram-codestructureandlogicisfaulty.White-boxtestcasesareaccurateonlyifthetesterknowswhattheprogramissupposedtodo.
Black-boxtestingusesonlythespecificationtoidentifyusecases,whilewhite-boxtestingusestheprogramsourcecode(implementation)asthebasisoftestcaseidentification.Bothapproaches,usedinconjunction,shouldbenecessaryinordertoselectagoodsetoftestcasesfortheSUT.Someofthemostsignificantwhite-boxtechniquesareasfollows:
Codecoveragedefinesthedegreeofsourcecode,whichhasbeentested,forexample,intermsofpercentageofLOCs.Thereareseveralcriteriaforthecodecoverage:1. Statementcoverage:Thelineofcodecoveragegranularity.2. Decision(branch)coverage:Controlstructure(forexample,if-else)
coveragegranularity.3. Conditioncoverage:Booleanexpression(true-false)coverage
granularity.4. Pathscoverage:Everypossibleroutecoveragegranularity.5. Functioncoverage:Programfunctionscoveragegranularity.6. Entry/exitcoverage:Callandreturnofthecoveragegranularity.
Faultinjectionistheprocessofinjectingfaultsintosoftwaretodeterminehowwell(orbadly)someSUTbehaves.Defectscanbesaidtopropagate,andinthatcase,theireffectsarevisibleinprogramstatesbeyondthestateinwhichtheerrorexisted(afaultbecameafailure).MutationtestingvalidatestestsandtheirdatabyrunningthemagainstmanycopiesoftheSUTcontainingdifferent,single,anddeliberatelyinsertedchanges.Mutationtestinghelpstoidentifyomissionsinthecode.
Non-functionaltestingThenon-functionalaspectsofasystemcanrequireconsiderableefforttotest.Withinthisgroupitcanbefounddifferentmeansoftesting,forexample,performancetestingconductedtoevaluatethecomplianceofaSUTwithspecifiedperformancerequirements.Theserequirementsusuallyincludeconstraintsaboutthetimebehaviorandresourceusage.Performancetestingmaymeasureresponsetimewithasingleuserexercisingthesystemorwithmultipleusersexercisingthesystem.Loadtestingisfocusedonincreasingtheloadonthesystemtosomestatedorimpliedmaximumload,toverifythesystemcanhandlethedefinedsystemboundaries.Volumetestingisoftenconsideredsynonymouswithloadtesting,yetvolumetestingfocusesondata.Stresstestingexercisesbeyondnormaloperationalcapacitytotheextentthatthesystemfails,identifyingactualboundariesatwhichthesystembreaks.Theaimofstresstestingistoobservehowthesystemfailsandwherethebottlenecksare.
Securitytestingtriestoensurethefollowingconcepts:confidentiality(protectionagainstthedisclosureofinformation),integrity(ensuringthecorrectnessoftheinformation),authentication(ensuringtheidentityoftheuser),authorization(determiningthatauserisallowedtoreceiveaserviceorperformanoperation),availability(ensuringthatthesystemperformsitsfunctionalitywhenrequired),andnon-repudiation(ensuringthedenialthatanactionhappened).Authorizedattemptsforevaluatingthesecurityofsysteminfrastructureisoftenknownaspenetrationtesting.
Usabilitytestingfocusesonfindinguserinterfaceproblems,whichmaymakethesoftwaredifficulttouseormaycauseuserstomisinterprettheoutput.Accessibilitytestingisthetechniqueofmakingsurethatourproductisaccessibility(theabilitytoaccessthesystemfunctionality)compliant.
TestingtypesTherearetwomaintypestocarryingoutsoftwaretesting:
Manualtesting:ThisistheprocessofassessingtheSUTisdonebyahuman,typicallyasoftwareengineerorthefinalconsumer.Inthistypeoftesting,wecanfindtheso-calledexploratorytesting,whichisatypeofmanualtestinginwhichhumantestersevaluatethesystembyinvestigatingandfreelyevaluatingthesystemusingitspersonalperception.Automatedtesting:ThisistheprocessofassessingtheSUTinwhichthetestingprocess(testexecution,reporting,andsoon)iscarriedoutwithspecialsoftwareandinfrastructurefortesting.ElfriedeDustin,inherbookImplementingAutomatedSoftwareTesting:HowtoSaveTimeandLowerCostsWhileRaisingQuality(2009),definedAutomatedSoftwareTesting(AST)asthe:
Applicationandimplementationofsoftwaretechnologythroughouttheentiresoftwaretestinglifecyclewiththegoaltoimproveefficienciesandeffectiveness.
ThemainbenefitsofASTare:anticipatedcostsavings,shortenedtestduration,heightenedthoroughnessofthetestsperformed,improvementoftestaccuracy,improvementofresultreportingaswellasstatisticalprocessing,andsubsequentreporting.
AutomatedtestsaretypicallyexecutedinbuildserversinthecontextofContinuousIntegration(CI)processes.Moredetailsaboutthisareprovidedinchapter7,TestingManagement.
ASTismosteffectivewhenimplementedwithinaframework.Testingframeworksmaybedefinedasasetofabstractconcepts,processes,proceduresandenvironmentsinwhichautomatedtestswillbedesigned,created,andimplemented.Thisframeworkdefinitionincludesthephysicalstructuresusedfortestcreationandimplementation,aswellasthelogicalinteractionsamongthosecomponents.
Strictlyspeaking,thatdefinitionofframeworkisnotveryfarfromwhatwecanunderstandbylibrary.Inordertomakethedifferenceclearer,considerthefollowingquotefromthewell-knownsoftwareengineeringguruMartin
Folwer:
Alibraryisessentiallyasetoffunctionsthatyoucancall,thesedaysusuallyorganizedintoclasses.Eachcalldoessomeworkandreturnscontroltotheclient.Aframeworkembodiessomeabstractdesign,withmorebehaviorbuiltin.Inordertouseityouneedtoinsertyourbehaviorintovariousplacesintheframeworkeitherbysubclassingorbyplugginginyourownclasses.Theframework’scodethencallsyourcodeatthesepoints.
Visualexplanationofthedifferencebetweenlibraryandframework
Frameworksarebecomingmoreandmoreimportantinmodernsoftwaredevelopment.Theyprovideacapabilityhighlydesiredinsoftware-intensivesystems:reusability.Thisway,largeapplicationswillendupconsistingoflayersofframeworksthatcooperatewitheachother.
OthertestingapproachesAsintroducedatthebeginningofthissection,thereisnoanuniversaldefinitionforthedifferentformsoftesting.Inthissectionwereviewsomeofthemostcommonlyvarietiesoftestingavailableintheliteraturenotcoveredsofar.Forinstance,whenthetestingprocessisperformedtodeterminewhetherthesystemmeetsitsspecifications,itisknownasconformancetesting.Whenanewfeatureorfunctionalityisintroducedtoasystem(wecancallitabuild),thewayoftestingthisnewfeatureinknownasprogressiontesting.Inadditiontothat,tocheckthatthenewintroducedchangesdonotaffectthecorrectnessoftherestofthesystem,theexistingtestcasesareexercised.Thisapproachiscommonlyknownasregressiontesting.
Whenthesysteminteractswithanyexternalorthird-partysystem,anothertestingcouldbedone,knownassystemintegrationtesting.Thiskindoftestingverifiesthatthesystemisintegratedtoanyexternalsystemsproperly.
Userorcustomertestingisastageinthetestingprocessinwhichusersorcustomersprovideinputandadviceforsystemtesting.Acceptancetestingisatypeofusertesting,buttherecanalsobedifferenttypesofusertesting:
Alphatesting:Thistakesplaceatdevelopers’sites,workingtogetherwiththesoftware’sconsumers,beforeitisreleasedtoexternalusersorcustomers.Betatesting:Thistakesplaceatcustomer’ssitesandinvolvestestingbyagroupofcustomerswhousethesystemattheirownlocationsandprovidefeedback,beforethesystemisreleasedtoothercustomers.Operationaltesting:Thisisperformedbytheenduserinitsnormaloperatingenvironment.
Finally,releasetestingreferstotheprocessoftestingaparticularreleaseofasystemperformedbyaseparateteamoutsidethedevelopmentteam.Theprimarygoalofthereleasetestingprocessistoconvincethesupplierofthesystemthatisgoodenoughforuse.
TestingframeworksfortheJVMJUnitisatestingframeworkwhichallowstocreateautomatedtests.ThedevelopmentofJUnitwasstartedbyKentBeckandErichGammainlate1995.Sincethen,thepopularityoftheframeworkhasbeengrowing.Nowadays,itisbroadlyconsideredasthedefactostandardfortestingJavaapplications.
JUnitwasdesignedtobeaunit-testingframework.Nevertheless,itcanbeusedtoimplementnotjustunittests,butalsootherkindsoftests.Aswewilldiscoverinthebodyofthisbook,dependingonhowthetestlogicexercisesthepieceofsoftwareundertest,atestcaseimplementedwithJUnitcanbeconsideredasanunit,integration,system,andevenacceptancetest.Allinall,wecanthinkofJUnitasamulti-purposetestingframeworkforJava.
JUnit3SincetheearlyversionsofJUnit3,theframeworkcanworkwithJava2andhigher.JUnit3isopensourcesoftware,releasedunderCommonPublicLicense(CPL)Version1.0andhostedonSourceForge(https://sourceforge.net/projects/junit/).ThelatestversionofJUnit3wasJUnit3.8.2,releasedonMay14,2007.ThemainrequirementsintroducedbyJUnitintheworldoftestingframeworkswerethefollowing:
1. Itshouldbeeasytodefinewhichtestswillrun.2. Theframeworkshouldbeabletoruntestsindependentlyofallother
tests.3. Theframeworkshoulddetectandreporterrorstestbytest.
StandardtestsinJUnit3InJUnit3,inordertocreatetestcases,weneedtoextendtheclassjunit.framework.TestCase.ThisbaseclassincludestheframeworkcodethatJUnitneedstoautomaticallyrunthetests.Then,wesimplymakesurethatthemethodnamefollowsthetestXXX()pattern.Thisnamingconventionmakesitcleartotheframeworkthatthemethodisaunittestandthatitcanberunautomatically.
Thetestlifecycleiscontrolledinthesetup()andtearDown()methods.TheTestCasecallssetup()beforerunningeachofitstestsandthencallsteardown()wheneachtestiscomplete.Onereasontoputmorethanonetestmethodintothesametestcaseistosharethesametestfixture.
Finally,inordertoimplementtheverificationstageinthetestcase,JUnit3definesseveralassertmethodsinautilityclassnamedjunit.framework.Assert.Thefollowingtablesummarizesthemainassertionsprovidedbythisclass:
Method Description
assertTrueAssertsthataconditionistrue.Ifitisn’t,themethodthrowsanAssertionFailedErrorwiththegivenmessage(ifany).
assertFalseAssertsthataconditionisfalse.Ifitisn’t,themethodthrowsanAssertionFailedErrorwiththegivenmessage(ifany).
assertEqualsAssertsthattwoobjectsareequal.Iftheyarenot,themethodthrowsanAssertionFailedErrorwiththegivenmessage(ifany).
assertNotNullAssertsthatanobjectisnotnull.Ifitis,themethodthrowsanAssertionFailedErrorwiththemessage(ifany).
assertNullAssertsthatanobjectisnull.Ifitisn’t,themethodthrowsanAssertionFailedErrorwiththegivenmessage(ifany).
assertSame
Assertsthattwoobjectsrefertothesameobject.Iftheydonot,themethodthrowsanAssertionFailedErrorwiththegivenmessage(ifany).
assertNotSame
Assertsthattwoobjectsdonotrefertothesameobject.Iftheydo,themethodthrowsanAssertionFailedErrorwiththegivenmessage(ifany).
failFailsatest(throwingAssertionFailedError)withthegiven
message(ifany).
ThefollowingclassshowsasimpletestimplementedwithJUnit3.8.2.Aswecansee,thistestcasecontainstwotests.Beforeeachtest,themethodsetUp()willbeinvokedbytheframework,andaftertheexecutionofeachtest,themethodtearDown()willbealsoinvoked.Thisexamplehasbeencodedsothatthefirsttest,namedtestSuccess()finishescorrectly,andthesecondtestnamedtestFailure()endswithanerror(theassertionthrowsanexception):
packageio.github.bonigarcia;
importjunit.framework.TestCase;
publicclassTestSimpleextendsTestCase{
//Phase1:Setup(foreachtest)
protectedvoidsetUp()throwsException{
System.out.println("<Setup>");
}
//Test1:Thistestisgoingtosucceed
publicvoidtestSuccess(){
//Phase2:Simulationofexercise
intexpected=60;
intreal=60;
System.out.println("**Test1**");
//Phase3:Verify
assertEquals(expected+"shouldbeequalsto"
+real,expected,real);
}
//Test2:Thistestisgoingtofail
publicvoidtestFailure(){
//Phase2:Simulationofexercise
intexpected=60;
intreal=20;
System.out.println("**Test2**");
//Phase3:Verify
assertEquals(expected+"shouldbeequalsto"
+real,expected,real);
}
//Phase4:Teardown(foreachtest)
protectedvoidtearDown()throwsException{
System.out.println("</Ending>");
}
}
AllthecodeexamplesexplainedinthisbookareavailableontheGitHubrepositoryhttps://github.com/bonigarcia/mastering-junit5.
TestexecutioninJUnit3JUnit3allowstoruntestcasesbymeansofJavaapplicationscalledtestrunners.JUnit3.8.2providesthreedifferenttestrunnersoutofthebox:twographical(SwingandAWTbased)andonetextualthatcanbeusedfromthecommandline.TheJUnitframeworkprovidesseparateclassloadersforeachtest,inordertoavoidsideeffectsamongtests.
Itisacommonpracticethatbuildtools(suchasAntorMaven)andIntegratedDevelopmentEnvironments-IDE-(suchasEclipseandIntelliJ)implementitsownJUnittestrunner.
ThefollowingimageshowswhattheprevioustestlookslikewhenweusetheJUnitSwingrunner,andalsowhenweuseEclipsetorunthesametestcase.
ExecutionofanJUnit3testcaseusingthegraphicalSwingtestrunnerandalsowiththeEclipsetestrunner
WhenatestisnotsucceededinJUnit,itcanbefortworeasons:afailureoranerror.Ontheonehand,afailureiscausedbyanassertion(Assertclass)whichisnotmeet.Ontheotherhand,anerrorisanunexpectedconditionnotexpectedbythetest,suchasaconventionalexceptioninthesoftwareundertest.
AnotherimportantcontributionofJUnit3istheconceptofthetestsuite,whichisaconvenientwaytogroupteststhatarerelated.TestsuitesareimplementedbymeansoftheJUnitclassjunit.framework.TestSuite.Thisclass,inthesamewayasTestCase,implementstheframeworkinterfacejunit.framework.Test.
AdiagramcontainingthemainclassesandmethodsofJUnit3isdepictedasfollows:
CoreJUnit3classes
ThefollowingsnippetshowsanexampleoftheuseoftestsuitesinJUnit3.Inshort,wecancreateagroupoftestssimplyinstantiatingaTestSuiteobject,andthenaddsingletestcasesusingthemethodaddTestSuite():
packageio.github.bonigarcia;
importjunit.framework.Test;
importjunit.framework.TestSuite;
publicclassTestAll{
publicstaticTestsuite(){
TestSuitesuite=newTestSuite("Alltests");
suite.addTestSuite(TestSimple.class);
suite.addTestSuite(TestMinimal.class);
returnsuite;
}
}
Thistestsuitecanbelaterexecutedusingatestrunner.Forexample,wecouldusethecommand-linetestrunner(junit.textui.TestRunner)andthecommandline,asfollows:
JUnit4JUnit4isstillanopensourceframework,thoughthelicensechangedwithrespecttoJUnit3,fromCPLtoEclipsePublicLicense(EPL)Version1.0.ThesourcecodeofJUnit4ishostedonGitHub(https://github.com/junit-team/junit4/).
OnFebruary18,2006,JUnit4.0wasreleased.Itfollowsthesamehigh-levelguidelinesthanJUnit3,thatis,easilydefinetest,theframeworkruntestsindependently,andtheframeworkdetectsandreporterrorsbythetest.
OneofthemaindifferencesofJUnit4withrespecttoJUnit3isthewaythatJUnit4allowstodefinetests.InJUnit4,Javaannotationsareusedtomarkmethodsastests.Forthisreason,JUnit4canonlybeusedforJava5orlater.AsthedocumentationofJUnit4.0statedbackin2006:
ThearchitectureofJUnit4.0isasubstantialdeparturefromthatofearlierreleases.Insteadoftaggingtestclassesbysubclassingjunit.framework.TestCaseandtaggingtestmethodsbystartingtheirnamewith‘test’,younowtagtestmethodswiththe@Testannotation.
StandardtestsinJUnit4InJUnit4,the@Testannotation(containedinpackageorg.junit)representsatest.Anypublicmethodcanbeannotatedwith@Testtomakeitatestmethod.
Inordertosetupthetestfixture,JUnit4providesthe@Beforeannotation.Thisannotationcanbeusedinanypublicmethod.Similarly,anypublicmethodannotatedwith@Aftergetsexecutedaftereachtestmethodexecution.JUnit4providestwomoreannotationstoenhancethetestlifecycle:@[email protected],beforeandafteralltests,respectively.ThefollowingpicturedepictsthelifecycleofaJUnit4testcase:
JUnit4testlifecycle
@Beforeand@Aftercanbeappliedtoanypublicvoidmethods.@AfterClassand@BeforeClasscanbeappliedtoonlypublicstaticvoidmethods.
ThefollowingtablesummarizesthemaindifferencesbetweenJUnit3andJUnit4seensofar:
Feature JUnit3 JUnit4
Testdefinition testXXXpattern @Testannotation
Runbeforethefirsttest Notsupported @BeforeClassannotation
Runafterallthetests Notsupported @AfterClassannotation
Runbeforeeachtest OverridesetUp()method @Beforeannotation
Runaftereachtest OverridetearDown()method @Afterannotation
Ignoretests Notsupported @Ignoreannotation
Theorg.junit.Assertclassprovidesstaticmethodstocarryoutassertions(predicates).Thefollowingarethemostusefulassertionmethods:
assertTrue:Iftheconditionbecomesfalse,theassertionfailsandAssertionErroristhrown.assertFalse:Iftheconditionbecomestrue,theassertionfailsandAssertionErroristhrown.assertNull:Thischeckswhethertheargumentisnull,otherwisethrowsAssertionErroriftheargumentisnotnull.assertNotNull:Thischeckswhethertheargumentisnotnull;otherwise,itthrowsAssertionErrorassertEquals:Thiscomparestwoobjectsorprimitivetypes.Moreover,iftheactualvaluedoesn’tmatchtheexpectedvalue,AssertionErroristhrown.assertSame:Thissupportsonlyobjectsandcheckstheobjectreferenceusingthe==operator.assertNotSame:ThisistheoppositeofassertSame.
ThefollowingsnippetsprovideasimpleexampleofaJUnit4testcase.Aswecansee,itistheequivalenttestcaseasseenintheprevioussection,thistimeusingtheJUnit4programmingmodel,thatis,using@Testannotationtoidentifytestsandotherannotations(@AfterAll,@After,@BeforeAll,@Before)toimplementthetestlifecycle(setupandteardowntestfixture):
packageio.github.bonigarcia;
importstaticorg.junit.Assert.assertEquals;
importorg.junit.After;
importorg.junit.AfterClass;
importorg.junit.Before;
importorg.junit.BeforeClass;
importorg.junit.Test;
publicclassTestSimple{
//Phase1.1:Setup(foralltests)
@BeforeClass
publicstaticvoidsetupAll(){
System.out.println("<SetupClass>");
}
//Phase1.2:Setup(foreachtest)
@Before
publicvoidsetupTest(){
System.out.println("<SetupTest>");
}
//Test1:Thistestisgoingtosucceed
@Test
publicvoidtestSuccess(){
//Phase2:Simulationofexercise
intexpected=60;
intreal=60;
System.out.println("**Test1**");
//Phase3:Verify
assertEquals(expected+"shouldbeequalsto"
+real,expected,real);
}
//Test2:Thistestisgoingtofail
@Test
publicvoidtestFailure(){
//Phase2:Simulationofexercise
intexpected=60;
intreal=20;
System.out.println("**Test2**");
//Phase3:Verify
assertEquals(expected+"shouldbeequalsto"
+real,expected,real);
}
//Phase4.1:Teardown(foreachtest)
@After
publicvoidteardownTest(){
System.out.println("</EndingTest>");
}
//Phase4.2:Teardown(foralltest)
@AfterClass
publicstaticvoidteardownClass(){
System.out.println("</EndingClass>");
}
}
TestexecutioninJUnit4TheconceptofthetestrunnerisalsopresentinJUnit4,butitwasslightlyimprovedwithrespecttoJUnit3.InJUnit4,atestrunnerisaJavaclassusedtomanageatest’slifecycle:instantiation,callingsetupandteardownmethods,runningthetest,handlingexceptions,sendingnotifications,andsoon.ThedefaultJUnit4testrunneriscalledBlockJUnit4ClassRunner,anditimplementstheJUnit4standardtestcaseclassmodel.
ThetestrunnertobeusedinaJUnit4testcasecanbechangedsimplyusingtheannotation@RunWith.JUnit4providesacollectionofbuilt-intestrunnersthatallowstochangethenatureofthetestclass.Inthissection,wearegoingtoreviewthemostimportantones.
Torunagroupoftests(thatis,atestsuite)JUnit4providestheSuiterunner.Inadditiontotherunner,theclassSuite.SuiteClassesallowstodefinetheindividualtestclassesbelongingtothesuite.Forexample:
packageio.github.bonigarcia;
importorg.junit.runner.RunWith;
importorg.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({TestMinimal1.class,TestMinimal2.class})
publicclassMySuite{
}
Parameterizedtestsareusedtospecifydifferentinputdatathatisgoingtobeusedinthesametestlogic.Toimplementthiskindoftests,JUnit4providestheParameterizedrunner.Todefinethedataparametersinthistypeoftest,weneedtoannotateastaticmethodoftheclasswiththeannotation@Parameters.ThismethodshouldreturnaCollectionofthetwo-dimensionalarrayprovidinginputparametersforthetest.Now,therewillbetwooptionstoinjecttheinputdataintothetest:1. Usingtheconstructorclass.2. Annotatingclassattributeswiththeannotation@Parameter.
Thefollowingsnippetsshowanexampleofthelatter:packageio.github.bonigarcia;
importstaticorg.junit.Assert.assertTrue;
importjava.util.Arrays;
importjava.util.Collection;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.junit.runners.Parameterized;
importorg.junit.runners.Parameterized.Parameter;
importorg.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
publicclassTestParameterized{
@Parameter(0)
publicintinput1;
@Parameter(1)
publicintinput2;
@Parameter(2)
publicintsum;
@Parameters(name="{index}:input1={0}input2={1}sum={2}?")
publicstaticCollection<Object[]>data(){
returnArrays.asList(
newObject[][]{{1,1,2},{2,2,4},{3,3,9}});
}
@Test
publicvoidtestSum(){
assertTrue(input1+"+"+input2+"isnot"+sum,
input1+input2==sum);
}
}
TheexecutionofthistestonEclipsewouldbeasfollows:
ExecutionofaParameterizedtestinEclipse
JUnittheoriesareanalternativetoJUnit’sparameterizedtests.AJUnittheoryisexpectedtobetrueforalldatasets.Thus,inJUnittheories,wehaveamethodprovidingdatapoints(thatis,theinputvaluestobeusedforthetest).Then,weneedtospecificamethodannotatedwith@Theorywhichtakesparameters.Thetheoriesinaclassgetexecutedwitheverypossiblecombinationofdatapoints:
packageio.github.bonigarcia;
importstaticorg.junit.Assert.assertTrue;
importorg.junit.experimental.theories.DataPoints;
importorg.junit.experimental.theories.Theories;
importorg.junit.experimental.theories.Theory;
importorg.junit.runner.RunWith;
@RunWith(Theories.class)
publicclassMyTheoryTest{
@DataPoints
publicstaticint[]positiveIntegers(){
returnnewint[]{1,10,100};
}
@Theory
publicvoidtestSum(inta,intb){
System.out.println("Checking"+a+"+"+b);
assertTrue(a+b>a);
assertTrue(a+b>b);
}
}
Takealookattheexecutionofthisexample,againinEclipse:
ExecutionofaJUnit4theoryinEclipse
AdvancedfeaturesofJUnit4OneofthemostsignificantinnovationsintroducedinJUnit4wastheuseofrules.Rulesallowflexibleadditionorredefinitionofthebehaviorofeachtestmethodinatestclass.Aruleshouldbeincludedinatestcasebyannotatingaclassattributewiththeannotation@Rule.ThetypeofthisattributeshouldinherittheJUnitinterfaceorg.junit.rulesTestRule.ThefollowingrulesareprovidedoutoftheboxinJUnit4:
ErrorCollector:ThisruleallowsexecutionofatesttocontinueafterthefirstproblemisfoundExpectedException:ThisruleallowstoverifythatatestthrowsaspecificexceptionExternalResource:ThisruleprovidesabaseclassforRulesthatsetupanexternalresourcebeforeatest(afile,socket,server,databaseconnection,andsoon)andguaranteetotearitdownafterwardTestName:ThisrulemakesthecurrenttestnameavailableinsidetestmethodsTemporaryFolder:ThisruleallowscreationoffilesandfoldersthatshouldbedeletedwhenthetestmethodfinishesTimeout:ThisruleappliesthesametimeouttoalltestmethodsinaclassTestWatcher:Itisabaseclassforrulesthatwillkeepalogofeachpassingandfailingtest
AnotheradvanceJUnit4featuresallowto:
Executetestsisagivenorder,usingtheannotation@FixMethodOrder.CreateassumptionsusingtheclassAssume.Thisclassoffersmanystaticmethods,suchasassumeTrue(condition),assumeFalse(condition),assumeNotNull(condition),andassumeThat(condition).Beforeexecutingatest,JUnitcheckstheassumptionspresentinthetest.Ifoneoftheassumptionsfail,theJUnitrunnerignoresthetestswithfailingassumptions.JUnitprovidesatimeoutvalue(inmilliseconds)inthe@Testannotationtomakesurethatifatestrunslongerthanthespecifiedvalue,thetestfails.CategorizetestsusingthetestrunnerCategoriesandidentifythetypesoftestannotatingthetestsmethodwiththeannotationCategory.
MeaningfulexamplesforeachofoneoftheearliermentionedfeaturescanbefoundintheGitHubrepository(https://github.com/bonigarcia/mastering-junit5).
JUnitecosystemJUnitisoneofthemostpopulartestframeworksfortheJVM,anditisconsideredoneofthemostinfluentialframeworksinsoftwareengineering.WecanfindseverallibrariesandframeworksthatprovideadditionalfunctionalityontopofJUnit.Someexamplesoftheseecosystemenhancersare:
Mockito(http://site.mockito.org/):Thisisthemockframework,whichcanbeusedinconjunctionwithJUnit.AssertJ(http://joel-costigliola.github.io/assertj/):ThisisthefluentassertionslibraryforJava.Hamcrest(http://hamcrest.org/):Thisisthelibrarywithmatchersthatcanbecombinedtocreateflexibleandreadableassertions.Cucumber(https://cucumber.io/):ThisisthetestingframeworkthatallowstorunautomatedacceptancetestswritteninaBehavior-DrivenDevelopment(BDD)style.FitNesse(http://www.fitnesse.org/):Thisisthetestingframeworkdesignedtosupportacceptancetestingbyfacilitatingdetailedreadabledescriptionsofsystemfunctions.
WhileJUnitisthelargesttestingframeworkfortheJVM,itisnottheonlyone.ThereareseveralothertestingframeworksavailablefortheJVM.Someexamplesare:
TestNG(http://testng.org/):ThisisthetestingframeworkinspiredfromJUnitandNUnit.Spock(http://spockframework.org/):ThisisthetestingandspecificationframeworkforJavaandGroovyapplications.Jtest(https://www.parasoft.com/product/jtest/):ThisistheautomatedJavatestingandstaticanalysisframeworkmadeanddistributedbythecompanyParasoft.Scalatest(http://www.scalatest.org/):ThisisthetestingframeworkforScala,Scala.js(JavaScript),andJavaapplications.
ThankstoJUnit,testinghasmovedtoacentralpartofprogramming.Consequently,theunderlyingtestingmodelimplementedinJUnit,hasbeenportedtoasetoftestingframeworksoutsidetheboundaryoftheJVM,inthe
so-calledxUnitfamily.Inthismodel,wefindtheconceptsoftestcase,runner,fixture,suite,testexecution,report,andassertion.Tonameafew,considerthefollowingframeworks.AllofthemfallintothexUnitfamily:
GoogleTest(https://github.com/google/googletest):Google’sC++testingframework.JSUnit(http://www.jsunit.net/):UnittestingframeworkforJavaScript.Mocha(https://mochajs.org/):UnittestingframeworkrunningonNode.js.NUnit(https://www.nunit.org/):UnittestingframeworkforMicrosoft.NET.PHPUnit(https://phpunit.de/):UnittestingframeworkforPHP.SimplyVBUnit(http://simplyvbunit.sourceforge.net/):UnittestingframeworkforVB.NET.Unittest(https://docs.python.org/3/library/unittest.html):UnittestingframeworkforPython.
SummarySoftwarequalityisakeyconceptinsoftwareengineering,sinceitdeterminesthedegreeinwhichasoftwaresystemmeetsitsrequirementsanduserexpectations.VerificationandValidationisthenamegiventosetofactivitiesaimedtoassessasoftwaresystem.ThegoalofV&Vistoensurethequalityofapieceofsoftwarewhilereducingthenumberofdefects.ThetwocoreactivitiesinV&Varesoftwaretesting(evaluationofarunningpieceofsoftware)andstaticanalysis(assessmentofsoftwareartefactswithoutitsexecution).
Automatedsoftwaretestinghasexperiencedbiggestadvancesinthelastfewdecades.Inthisarena,theJUnitframeworkhasaremarkableposition.JUnitwasdesignedtobeaunitframeworkfortheJVM.Nowadays,itisafactthatJUnitisthemostpopulartestframeworksintheJavacommunity,providingacomprehensiveprogrammingmodeltocreateandexecutetestcases.Inthenextsection,wewilldiscoverthefeaturesandcapabilitiesprovidedbythenewversionoftheframework,JUnit5.
What’sNewInJUnit5Thosewhocanimagineanything,cancreatetheimpossible.
-AlanTuring
JUnitisthemostimportanttestingframeworkfortheJVMandoneofthemostinfluentialinsoftwareengineeringingeneral.JUnit5isthenextgenerationofJUnit,anditsfirstGeneralAvailability(GA)version(5.0.0)wasreleasedonSeptember10,2017.Aswewilldiscover,JUnit5supposesasmallrevolutionwithrespecttoJUnit4,providingacompletelynewarchitecture,programming,andextensionmodel.Thischaptercoversthefollowingcontent:
RoadtoJUnit5:Inthefirstsection,wewilldiscoverthemotivationtocreateanewmajorversionofJUnit(thatis,thelimitationsofJUnit4),thedesignprinciplesguidingthedevelopmentofJUnit5,andfinallythedetailsoftheJUnit5opensourcecommunity.JUnit5architecture:JUnit5isamodularframeworkcomposedofthreemajorcomponents,namedPlatform,Jupiter,andVintage.RunningtestsinJUnit5:WewilldiscoverhowtorunJUnit5testsusingpopularbuildtools,suchasMavenorGradle,andalsowithIDEssuchasIntelliJorEclipse.TheextensionmodelofJUnit5:Theextensionmodelallowsforthird-partylibrariesandframeworkstoextendtheJUnit5programmingmodelwiththeirownadditions.
RoadtoJUnit5SoftwaretestinghaschangedalotsincethefirstreleaseofJUnit4in2006.Sincethen,notonlyhaveJavaandtheJVMhasevolved,butalsoourtestingneedsmatured.Wearenotwritingjustunittestsanymore.Instead,inadditiontoverifyingasinglepieceofcode,softwareengineersandtestersdemandotherkindsoftests,suchasintegrationandend-to-endtests.
Inaddition,ourexpectationsabouttestingframeworkshavegrown.Nowadays,wedemandadvancedcapabilitiesfortheseframeworks,suchasextensibilityormodularity,tonameafew.Inthissection,wediscoverthemainlimitationsofJUnit4,thevisionofJUnit5,andthecommunitysupportingitsdevelopment.
JUnit5motivationAccordingtoseveralstudies,JUnit4isthemostusedlibraryforJavaprojects.Forinstance,TheTop100JavalibrariesonGitHubisawell-knownreportpublishedbyOverOps(@overopshq),asoftwareanalyticscompanyfocusedonlarge-scaleJavaandScalacodebases.
Initseditionof2017,thisreportanalyzedtheimportstatementsofuniqueJavalibrariesthatareusedbythetop1,000JavaprojectsonGitHub(bystars).Inthelightoftheresults,JUnit4istheundisputedkingofJavaLibraries:theimportsofthepackagesorg.junitandorg.junit.runnerappearinthefirstandsecondposition,respectively:
TheTop20JavalibrariesonGitHub
Despitethisfact,JUnit4isaframeworkcreatedmorethanadecadeago,andthereareimportantseverallimitationsthatimposeacompleteredesignoftheframework.
ModularityFirstofall,JUnit4isnotmodular.Asdepictedinthefollowingpicture,thearchitectureofJUnit4iscompletelymonolithic.AllthecapabilitiesofJUnit4areprovidedbythejunit.jardependency.Asaresult,differenttestmechanisms,suchastestdiscoveryandexecution,aretightlycoupledinJUnit4.
TheJUnit4Architecture
JohannesLink,oneoftheJUnit5coreteammembers,summarizesthisprobleminaninterviewforJaxmagazineonAugust13,2015(duringtheinceptionofJUnit5):
ThesuccessofJUnitasaplatformpreventsthedevelopmentofJUnitasatesttool.ThebasicproblemwewanttosolveisexecutingtestcasesbyseparatingasufficientlypowerfulandstableAPI.
JUnit4runnersTheJUnit4’srunnerAPIalsohasanimportantdeterrent.Asdescribedinchapter1,RetrospectiveonsoftwarequalityandJavatesting,inJUnit4arunnerisaJavaclassusedtomanageatest’slifecycle.TherunnerAPIinJUnit4isquitepowerful,nevertheless,ithasanimportantdrawback:runnersarenotcomposable,thatis,wecanonlyuseasinglerunneratatime.
Forexample,aparameterizedtestcannotbecombinedwiththeSpringtestsupport,duetothefactthatbothtestswouldusetheirownrunnerimplementation.ThinkinginJava(seethesnippetsgivenfollow),eachtestcaseusesitsownunique@RunWithannotation.ThefirstoneusestheParameterizedrunner:
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.junit.runners.Parameterized;
@RunWith(Parameterized.class)
publicclassMyParameterizedTest{
@Test
publicvoidmyFirstTest(){
//mytestcode
}
}
WhilethissecondexampleisusingtheSpringJUnit4ClassRunnerrunner,itwouldnotbecombinedwiththepreviousoneduetoalimitationonJUnit4(runnersarenotcomposable):
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
publicclassMySpringTest{
@Test
publicvoidyetAnotherTest(){
//mytestcode
}
}
JUnit4rulesDuetothestrictlimitationofuniquenessofaJUnit4runnerwithinthesametestclass,version4.7ofJUnitintroducedtheconceptofmethod-levelrules,whichareannotatedfieldsinatestclasswith@Rule.Theserulesallowforadditionorredefinitionoftestbehaviorbyexecutingsomecodebeforeandaftertheexecutionofthetest.JUnit4.9alsoincorporatestheconceptofclass-levelrules,whicharerulesthatareexecutedbeforeandafteralltestswithintheclass.Theserulesareidentifiedbyannotatingstaticfieldswith@ClassRule,asshowninthefollowingexample:
importorg.junit.ClassRule;
importorg.junit.Test;
importorg.junit.rules.TemporaryFolder;
publicclassMyRuleTest{
@ClassRule
publicstaticTemporaryFoldertemporaryFolder=newTemporaryFolder();
@Test
publicvoidanotherTest(){
//mytestcode
}
}
Whilerulesaresimplerandmostlycompostable,theyhaveotherdrawbacks.ThemaininconveniencewhenusingJUnit4rulesforcomplextestsisthatwearenotabletouseasingleruleentityformethod-levelandclass-level.Attheendoftheday,thisimposeslimitationstocustomizethelifecyclemanagement(thebefore/afterbehavior).
JUnit5inceptionEventhoughJUnit4wasthedefaulttestingframeworkformillionsofJavadevelopersworldwide,noneoftheactiveJUnitmaintainerswerepaidbytheiremployertodothatwork.Forthatreason,andinordertoovercomethedrawbacksofJUnit4,inJuly2015JohannesLinkandMarcPhilippstartedtheJUnitLambdacrowdfundingcampaign(http://junit.org/junit4/junit-lambda-campaign.html)onIndiegogo(aninternationalcrowdfundingwebsite):
JUnitLambdaCrowdfundingCampaign
JUnitLambdawasthenamegiventotheproject,whichwastheseedofthecurrentJUnit5framework.TheinclusionofthewordlambdaintheprojectnameenforcestheideaofusingJava8fromtheverybeginningoftheproject.QuotingtheJUnitLambdaprojectsite:
Thegoalistocreateanup-to-datefoundationfordeveloper-sidetestingontheJVM.ThisincludesfocusingonJava8andabove,aswellasenablingmanydifferentstylesoftesting.
TheJUnitLambdaCrowdfundingCampaignranfromJulytoOctober2015.
Itwasasuccess,raising53,937eurosfrom474individualsandcompaniesworldwide.Fromthispoint,theJUnit5kick-offteamwascreated,joiningpeoplefromEclipse,Gradle,IntelliJ,orSpring.
TheJUnitLambdaprojectbecameJUnit5,andthedesignprinciplesguidingthedevelopmentprocesswerethefollows:
Modularization:Asintroducedbefore,JUnit4wasnotmodular,andthiscausessomeproblems.Fromitsinception,JUnit5architectureismuchcompletelymodular,allowingdeveloperstousethespecificpartsoftheframeworktheyrequire.Powerfulextensionmodelwithfocusoncomposability:Extensibilityisamustformoderntestingframeworks.Therefore,JUnit5shouldprovideseamlessintegrationwiththird-partyframeworks,suchasSpringorMockito,tonameafew.APIsegregation:Decoupletestdiscoveryandexecutionfromtestdefinition.Compatibilitywitholderreleases:SupportingtheexecutionoflegacyJava3andJava4inthenewJUnit5platform.Modernprogrammingmodelforwritingtests(Java8):Nowadays,moreandmoredeveloperswritecodewithJava8newfeatures,suchaslambdaexpressions.JUnit4wasbuiltonJava5,butJUnit5hasbeencreatedfromscratchusingJava8.
JUnit5communityThesourcecodeofJUnit5ishostedonGitHub(https://github.com/junit-team/junit5).AllmodulesoftheJUnit5frameworkhavebeenreleasedunderthetermsoftheopensourcelicenseEPLv1.0.Thereisoneexceptiontothisrule,sincethemodulecalledjunit-platform-surefire-provider(describedlater)hasbeenreleasedusingApacheLicensev2.0.
TheroadmapoftheJUnitdevelopment(https://github.com/junit-team/junit5/wiki/Roadmap)andthedefinitionandstatusofthedifferentreleasesandmilestones(https://github.com/junit-team/junit5/milestones/)arepubliconGitHub.Thefollowingtablesummarizesthisroadmap:
Phase Date Release
0.Crowdfunding
FromJuly2015toOctober2015 -
1.Kickoff FromOctober20to22,2015 -
2.Firstprototype
FromOctober23,2015totheendofNovember2015
-
3.Alphaversion February1,2016 5.0Alpha
4.Firstmilestone
July9,20165.0M1:Stable,documentedIDE-facingAPIs(LauncherAPIandEngineSPI),dynamictests
5.Additionalmilestones
July23,2016(5.0M2)
November30,2016(5.0M3)
April1,2017(5.0M4)
5.0M2:Bugfixandminorimprovementrelease
5.0M3:JUnit4interoperability,additionaldiscoveryselectors
5.0M4:Testtemplates,repeatedtests,andparameterizedtests
5.0M5:Dynamiccontainersandminor
July5,2017(5.0M5)
July16,2017(5.0M6)
APIchanges
5.0M6:Java9compatibility,scenariotests,additionalextensionAPIsforJUnitJupiter
6.Releasecandidate(RC)
July30,2017
July30,2017
August23,2017
5.0RC1:Finalbugfixesanddocumentationimprovements
5.0RC2:FixGradleconsumptionofjunit-jupiter-engine
5.0RC3:Configurationparametersandbugfixes
7.Generalavailability(GA)
September10,2017 5.0GA:Firststablerelease
TheJUnit5contributorsaremorethanjustdevelopers.Contributorsarealsotesters,maintainers,andcommunicators.Atthetimeofwriting,thetopJUnit5contributorsonGitHubare:
SamBrannen(@sam_brannen):CoreSpringFrameworkandJUnit5committer.EnterpriseJavaConsultantatSwiftmind.Spring&JUnittrainer.Conferencespeaker.MarcPhilipp(@marcphilipp):SeniorSoftwareEngineeronLogMeIn,activecontributortoopensourceprojectssuchasJUnitorUsus.Conferencespeaker.JohannesLink(@johanneslink):Programmerandsoftwaretherapist.JUnit5supporter.MatthiasMerdes:LeadDeveloperatHeidelbergMobilGmbH,Germany.
TopJUnit5contributorsonGitHub
ThefollowinglistprovidesacollectionofonlineJUnit5resources:
Officialwebsite(http://junit.org/junit5/).Sourcecode(https://github.com/junit-team/junit5/).JUnit5developerguide(http://junit.org/junit5/docs/current/user-guide/).Referencedocumentation.TwitteroftheJUnitteam(https://twitter.com/junitteam).Usually,thetweetsaboutJUnit5aretaggedwith#JUnit5(https://twitter.com/hashtag/JUnit5).Issues(https://github.com/junit-team/junit5/issues).ProblemsorsuggestionsforadditionalfunctionalityonGitHub.QuestionsonStackOverflow(https://stackoverflow.com/questions/tagged/junit5).StackOverflowisapopularquestion-and-answerwebsiteforcomputerprogramming.Thetagjunit5shouldbeusedtoaskquestionsaboutJUnit
5.JUnit5JavaDoc(http://junit.org/junit5/docs/current/api/).JUnit5Gitter(https://gitter.im/junit-team/junit5),aninstantmessagingandchatroomsystemusedtodiscussdirectlywiththeJUnit5teammembersandotherpractitioners.OpenTestAlliancefortheJVM(https://github.com/ota4j-team/opentest4j).ItisaninitiativestartedbytheJUnit5team,anditsobjectiveistoprovideaminimalcommonfoundationfortestinglibraries(JUnit,TestNG,Spock,andsoon)andthird-partyassertionlibraries(Hamcrest,AssertJ,andsoon)ontheJVM.TheideaistouseacommonsetofexceptionsthatIDEsandbuildtoolscansupportinaconsistentmanneracrossalltestingscenarios(sofarthereisnostandardfortestingontheJVM,andtheonlycommonbuildingblockistheJavaexceptionjava.lang.AssertionError).
JUnit5architectureTheJUnit5frameworkhasbeendesignedtobeconsumedbydifferentprogrammaticclients.ThefirstgroupofclientsareJavatests.ThesetestscanbebasedonJUnit4(testswhichusethetestlegacyprogrammingmodel),JUnit5(testswhichusethebrandnewprogrammingmodel),andevenotherkindsofJavatests(thirdparty).Thesecondgroupofclientsarebuildtools(suchasMavenorGradle)andIDEs(suchasIntelliJorEclipse).
Inordertoachievetheintegrationofallthesepiecesinalooselycoupledmanner,JUnit5wasdesignedtobemodular.Asdepictedinthefollowingpicture,theJUnit5frameworkiscomposedofthreemajorcomponents,calledPlatform,Jupiter,andVintage:
JUnit5Architecture:high-levelcomponent
Thehigh-levelcomponentsoftheJUnit5architectureareenumeratedasfollows:
Thefirsthigh-levelcomponentiscalledJupiter.Itprovidesthebrand-newprogrammingandextensionmodeloftheJUnit5framework.InthecoreofJUnit5,wefindtheJUnitPlatform.ThiscomponentisaimedtobecomethefoundationforanytestingframeworkexecutedintheJVM.Inotherwords,itprovidesmechanismstorunJupitertests,legacyJUnit4,andalsothird-partytests(forexample,Spock,FitNesse,andsoon).Thelasthigh-levelcomponentoftheJUnit5architectureiscalled
Vintage.ThiscomponentallowsrunninglegacyJUnittestsontheJUnitPlatformoutofthebox.
Let’stakeacloserlookatthedetailsofeachcomponenttofindouttheirinternalmodules:
JUnit5Architecture:modules
Ascanbeseeninthepicturepreceding,therearethreetypesofmodule:
TestAPIs:Thesearethemodulesfacingusers(thatis,softwareengineerandtesters).ThesemodulesprovidetheprogrammingmodelforaparticularTestEngine(forexample,junit-jupiter-apiforJUnit5testsandjunitforJUnit4tests).TestEngines:Thesemodulesallowtoexecuteakindoftest(Jupitertests,legacyJUnit4,orotherJavatests)withintheJUnitPlatform.TheyarecreatedbyextendingthegeneralPlatformEngine(junit-platform-engine).TestLauncher:ThesemodulesprovidetheabilityoftestdiscoveryinsidetheJUnitplatformforexternalbuildtoolsandIDEs.ThisAPIisconsumedbytoolssuchasMaven,Gradle,IntelliJ,andsoon,usingthejunit-platform-launchermodule.
Asaresultofthismodulararchitecture,theJUnitframeworkexposesasetof
interfaces:
AnAPI(ApplicationProgrammingInterface)towritetests,theJupiterAPI.ThedetaileddescriptionofthisAPIiswhatitisknownastheJupiterprogrammingmodelanditisdescribedindetailinchapters3,JUnit5StandardTestsandchapter4,SimplifyingTestingWithAdvancedJUnitFeaturesofthisbook.AnSPI(ServiceProviderInterface)todiscoverandexecutetests,theEngineSPI.ThisSPIistypicallyextendedbytestengines,whichintheendprovidetheprogrammingmodelstowritetests.AnAPIfortestdiscoveryandexecution,theLauncherAPI.ThisAPIistypicallyconsumedbyprogrammaticclients,thatareIDEsandbuildtools.
APIandSPIarebothasetsofassets(typicallyclassesandinterfaces)usedbysoftwareengineersforagivenpurpose.ThedifferenceisthatAPIiscalledwhileSPIisextended.
TestEngineSPITheTestEngineSPIallowsforcreatingtestexecutorsontopoftheJVM.IntheJUnit5framework,therearetwoTestEngineimplementationsoutofthebox:
Thejunit-vintage-engine:ThisallowsrunningJUnit3and4testsintheJUnitplatform.Thejunit-jupiter-engine:ThisallowsrunningJUnit5testsintheJUnitplatform.
Moreover,third-partytestlibraries(forexample,Spock,TestNG,andsoon)canplugintotheJUnitPlatformbyprovidingacustomTestEngine.Todothat,theseframeworksshouldcreateitsownTestEnginebyextendingtheJUnit5interfaceorg.junit.platform.engine.TestEngine.Inordertoextendthisinterface,threemandatorymethodsmustbeoverridden:
getId:Theuniqueidentifierforthetestengine.discover:Thelogictofindandfilterthetest(s).execute:Thelogictorunthepreviouslyfoundtest(s).
ThefollowingexampleprovidestheskeletonforacustomTestEngine:packageio.github.bonigarcia;
importorg.junit.platform.engine.EngineDiscoveryRequest;
importorg.junit.platform.engine.ExecutionRequest;
importorg.junit.platform.engine.TestDescriptor;
importorg.junit.platform.engine.TestEngine;
importorg.junit.platform.engine.UniqueId;
importorg.junit.platform.engine.support.descriptor.EngineDescriptor;
publicclassMyCustomEngineimplementsTestEngine{
publicstaticfinalStringENGINE_ID="my-custom-engine";
@Override
publicStringgetId(){
returnENGINE_ID;
}
@Override
publicTestDescriptordiscover(EngineDiscoveryRequestdiscoveryRequest,
UniqueIduniqueId){
//Discovertest(s)andreturnaTestDescriptorobject
TestDescriptortestDescriptor=newEngineDescriptor(uniqueId,
"Mytest");
returntestDescriptor;
}
@Override
publicvoidexecute(ExecutionRequestrequest){
//UseExecutionRequesttoexecuteTestDescriptor
TestDescriptorrootTestDescriptor=
request.getRootTestDescriptor();
request.getEngineExecutionListener()
.executionStarted(rootTestDescriptor);
}
}
AlistofexistingTestEngines(forexample,Specsy,Spek,andothers)ismaintainedbythecommunityinthewikilocatedintheGitHubsiteoftheJUnit5team:https://github.com/junit-team/junit5/wiki/Third-party-Extensions.
TestLauncherAPIOneofthegoalsofJUnit5istomaketheinterfacebetweenJUnitanditsprogrammaticclients(buildtoolsandIDEs)morepowerfulandstable.Tothataim,theTestLauncherAPIhasbeenimplemented.ThisAPIisusedbyIDEsandbuildtoolsfordiscovering,filtering,andexecutingtests.
LookingcloseratthedetailsofthisAPI,wefindtheclassLauncherDiscoveryRequest,whichexposesafluentAPItoselectthelocationoftests(forexampleclasses,methods,orpackages).Thisgroupoftestscanbefiltered,forexample,usingamatchpattern:
importstatic
org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
importstaticorg.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
importstaticorg.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
importorg.junit.platform.launcher.Launcher;
importorg.junit.platform.launcher.LauncherDiscoveryRequest;
importorg.junit.platform.launcher.TestPlan;
importorg.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
importorg.junit.platform.launcher.core.LauncherFactory;
//Discoverandfiltertests
LauncherDiscoveryRequestrequest=LauncherDiscoveryRequestBuilder
.request()
.selectors(selectPackage("io.github.bonigarcia"),
selectClass(MyTest.class))
.filters(includeClassNamePatterns(".*Test")).build();
Launcherlauncher=LauncherFactory.create();
TestPlanplan=launcher.discover(request);
Afterthat,theresultingtestsuitecanbeexecutedusingtheclassTestExecutionListener.Thisclasscanbealsousedtogetfeedbackandreceiveevents:
importorg.junit.platform.launcher.TestExecutionListener;
importorg.junit.platform.launcher.listeners.SummaryGeneratingListener;
//Executingtests
TestExecutionListenerlistener=newSummaryGeneratingListener();
launcher.registerTestExecutionListeners(listener);
launcher.execute(request);
RunningtestsinJUnit5Atthetimeofwriting,Jupitertestscanbeexecutedinseveralways:
Usingabuildtool:Maven(implementedinthemodulejunit-plaform-surefire-provider)orGradle(implementedinthemodulejunit-platform-gradle-plugin).UsingtheConsoleLauncher:Acommand-lineJavaapplicationthatallowstolaunchtheJUnitPlatformfromtheconsole.UsinganIDE:IntelliJ(sinceversion2016.2)andEclipse(sinceversion4.7,Oxygen).
Aswearegoingtodiscover,andduetothemodulararchitectureofJUnit5,weneedtoincludethreedependenciesinourprojects:onefortheTestAPI(toimplementtests),anotherfortheTestEngine(toruntests),andthelastoneoftheTestLauncher(todiscovertests).
JupitertestswithMavenInordertorunJupitertestswithinaMavenproject,weneedtoconfigurethepom.xmlfileproperly.Firstofall,weneedtoincludethejunit-jupiter-apimoduleasadependency.Thisisneededtowriteourtest,andtypicallywithtestscope:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Ingeneral,itisrecommendedtousethelatestversionofthedependencies.Inordertocheckwhatitthatversion,wecancheckitonMavenCentral(http://search.maven.org/)
Then,themaven-surefire-pluginhastobedeclared.Internally,thispluginneedstwodependencies:theTestLauncher(junit-platform-surefire-provider)andtheTestEngine(junit-jupiter-engine):
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
AllthesourcecodeofthisbookispubliclyavailableontheGitHubrepositoryathttps://github.com/bonigarcia/mastering-junit5.
Lastbutnotleast,weneedtocreateaJupitertestcase.Sofar,wehavenotlearnedhowtoimplementJupitertests(thispartiscoveredinchapter3,JUnit5StandardTests).Nevertheless,thetestweexecutehereisthesimplesttesttodemonstratetheexecutionoftheJUnit5framework.AJupitertest,inits
minimalexpression,isjustaJavaclassinwhichone(ormore)ofitsmethodsareannotatedwith@Test(packageorg.junit.jupiter.api).Thefollowingsnippetprovidesanexample:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importorg.junit.jupiter.api.Test;
classMyFirstJUnit5Test{
@Test
voidmyFirstTest(){
Stringmessage="1+1shouldbeequalto2";
System.out.println(message);
assertEquals(2,1+1,message);
}
}
JUnitrequiresJava8(orhigher)atruntime.However,wecanstilltestcodethathasbeencompiledwithpreviousversionsofJava.
Asshowninthefollowingpicture,thistestcanbeexecutedusingthecommandmvntest:
JupitertestswithGradleNow,wearegoingtostudythesameexample,butthistimeexecutedwithGradle.Therefore,weneedtoconfigurethebuild.gradlefile.Inthisfile,weneedtodefine:
ThedependencyfortheJupiterAPI(junit-jupiter-api).ThedependencyfortheTestEngine(junit-jupiter-engine).ThepluginfortheTestLauncher(junit-platform-gradle-plugin).
Thecompletesourceofbuild.gradleisasfollows:buildscript{
repositories{
mavenCentral()
}
dependencies{
classpath("org.junit.platform:junit-platform-gradle-
plugin:${junitPlatformVersion}")
}
}
repositories{
mavenCentral()
}
applyplugin:'java'
applyplugin:'eclipse'
applyplugin:'idea'
applyplugin:'org.junit.platform.gradle.plugin'
compileTestJava{
sourceCompatibility=1.8
targetCompatibility=1.8
options.compilerArgs+='-parameters'
}
dependencies{
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
WeusethecommandgradletesttorunourJupitertestfromthecommandlinewithGradle:
LegacytestswithMavenThefollowingistheimagewewanttorunthelegacytest(JUnit4inthiscase)insidetheJUnitPlaform:
packageio.github.bonigarcia;
importstaticorg.junit.Assert.assertEquals;
importorg.junit.Test;
publicclassLegacyJUnit4Test{
@Test
publicvoidmyFirstTest(){
Stringmessage="1+1shouldbeequalto2";
System.out.println(message);
assertEquals(message,2,1+1);
}
}
Tothataim,inMaven,wefirstneedtoincludetheoldJUnit4dependencyinourpom.xml,asfollows:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
Then,weneedtoincludemaven-surefire-plugin,usingthefollowingdependenciesfortheplugin:theTestEngine(junit-vintage-engine)andtheTestLauncher(junit-platform-surefire-provider):
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.vintage.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
LegacytestswihGradleIfwewanttoexecutethesametestpresentedintheexamplebefore(io.github.bonigarcia.LegacyJUnit4Test),butthistimeusingGradle,weneedtoincludethefollowinginourbuild.gradlefile:
ThedependencyforJUnit4.12.ThedependencyfortheTestEngine(junit-vintage-engine).ThepluginfortheTestLauncher(junit-platform-gradle-plugin).
Thus,thecompletesourceofbuild.gradlewouldbeasfollows:buildscript{
repositories{
mavenCentral()
}
dependencies{
classpath("org.junit.platform:junit-platform-gradle-
plugin:${junitPlatformVersion}")
}
}
repositories{
mavenCentral()
}
applyplugin:'java'
applyplugin:'eclipse'
applyplugin:'idea'
applyplugin:'org.junit.platform.gradle.plugin'
compileTestJava{
sourceCompatibility=1.8
targetCompatibility=1.8
options.compilerArgs+='-parameters'
}
dependencies{
testCompile("junit:junit:${junitLegacy}")
testRuntime("org.junit.vintage:junit-vintage-engine:${junitVintageVersion}")
}
Theexecutionfromthecommandlinewouldbeasfollows:
TheConsoleLauncherTheConsoleLauncherisacommand-lineJavaapplicationthatallowslaunchingtheJUnitPlatformfromtheconsole.Forexample,itcanbeusedtorunVintageandJupitertestsfromthecommandline.
AnexecutableJARwithalldependenciesincludedispublishedinthecentralMavenrepositoryunderthejunit-platform-console-standaloneartifact.ThestandaloneConsoleLaunchercanbeexecutedasfollows:
java-jarjunit-platform-console-standalone-version.jar<Options>
TheexampleGitHubrepositoryjunit5-console-launchercontainsasimpleexamplefortheuseoftheConsoleLauncher.Asdepictedinthefollowingpicture,arunconfigurationentryhasbeencreatedinEclipse,runningthemainclass,org.junit.platform.console.ConsoleLauncher.Then,thetestclassnameispassedasanargumentusingtheoption--select-classandthequalifiedclassname(inthisexample,io.github.bonigarcia.EmptyTest).Afterthat,wecanruntheapplication,obtainingthetestresultintheintegratedconsoleofEclipse:
JupitertestsinJUnit4JUnit5hasbeendesignedtobeforwardandbackwardcompatible.Ontheonehand,theVintagecomponentsupportsrunninglegacycodeonJUnit3and4.Ontheotherhand,JUnit5providesaJUnit4runnerthatallowstorunJUnit5inIDEsandbuildsystemsthatsupportJUnit4,butdoesnotyetsupportthenewJUnitPlatform5directly.
Let’sseeoneexample.ImaginewewanttorunaJupitertestinanIDEdoesnotsupportJUnit5,forexample,anoldversionofEclipse.Inthiscase,weneedtoannotateourJupitertestwith@RunWith(JUnitPlatform.class).TheJUnitPlatformrunnerisaJUnit4-basedrunner,whichenablestorunanytestwhoseprogrammingmodelissupportedontheJUnitPlatforminaJUnit4environment.Therefore,ourtestwouldresultasfollows:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importorg.junit.jupiter.api.Test;
importorg.junit.platform.runner.JUnitPlatform;
importorg.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
publicclassJUnit5CompatibleTest{
@Test
voidmyTest(){
Stringmessage="1+1shouldbeequalto2";
System.out.println(message);
assertEquals(2,1+1,message);
}
}
IfthistestiscontainedinaMavenproject,ourpom.xmlshouldcontainthefollowingdependencies:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>${junit.platform.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Ontheotherhand,foraGradleproject,ourbuild.gradleisthefollowing:buildscript{
repositories{
mavenCentral()
}
dependencies{
classpath("org.junit.platform:junit-platform-gradle-
plugin:${junitPlatformVersion}")
}
}
repositories{
mavenCentral()
}
applyplugin:'java'
applyplugin:'eclipse'
applyplugin:'idea'
applyplugin:'org.junit.platform.gradle.plugin'
compileTestJava{
sourceCompatibility=1.8
targetCompatibility=1.8
options.compilerArgs+='-parameters'
}
dependencies{
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
testCompile("org.junit.platform:junit-platform-runner:${junitPlatformVersion}")
}
IntelliJIntelliJ2016.2+hasbeenthefirstIDEwhichsupportstheexecutionofJupitertestsnatively.Asshowninthefollowingscreenshot,anyJupitertestcanbeexecutedusingtheintegratedfunctionsoftheIDE:
RunningaJupitertestinIntelliJ2016.2+
EclipseEclipse4.7(Oxygen)hasbetasupportforJUnit5.Thankstothis,EclipseprovidestheabilityofrunningJupitertestsdirectlyinEclipse,asshowninthefollowingscreenshot:
RunningaJupitertestinEclipse4.7+
Moreover,Eclipse4.7(Oxygen)providesawizardtocreateJupitertestsinasimpleway,asshowninthefollowingpictures:
TheextensionmodelofJUnit5Asintroducedbefore,JupiteristhenamegiventothenewprogrammingmodelofJUnit5,describedindetailinchapter3,JUnit5standardtestsandchapter4,SimplifyingtestingwithadvancedJUnitfeatures,togetherwiththeextensionmodel.TheextensionmodelallowstoextendtheJupiterprogrammingmodelwithcustomadditions.Thankstothis,third-partyframeworks(suchasSpringorMockito,tonameafew)canachieveinteroperabilitywithJUnit5inaseamlessway.Theextensionsprovidedbytheseframeworkswillbestudiedinchapter5,IntegrationofJUnit5withexternalframeworks.Inthecurrentsection,weanalyzethegeneralperformanceoftheextensionmodelandalsotheextensionsprovidedoutoftheboxinJUnit5.
IncontrasttoformerextensionpointsinJUnit4(thatis,testrunnersandrules),theJUnit5extensionmodelconsistsofasingle,coherentconcept:theExtensionAPI.ThisAPIallowstoextendthecorefunctionalityofJUnit5byanythirdparty(toolvendor,developers,andsoon).ThefirstthingweneedtounderstandaboutextensionsinJupiteristhateachnewextensionimplementsaninterfacecalledExtension.Thisinterfaceisamarkerinterface,thatis,aJavainterfacewithnofieldormethods:
packageorg.junit.jupiter.api.extension;
importstaticorg.apiguardian.api.API.Status.STABLE;
importorg.apiguardian.api.API;
/**
*Markerinterfaceforallextensions.
*
*@since5.0
*/
@API(status=STABLE,since="5.0")
publicinterfaceExtension{
}
InordertomakeeasethecreationofJupiterextensions,JUnit5providesasetofextensionspointswhichallowstoexecutecustomcodeindifferentpartsofthetestlifecycle.ThefollowingtablecontainsasummaryoftheextensionpointsinJupiter,anditsdetailsarepresentedinthenextsections:
Extensionpoint Implementedbyextensionswhichwantto…
TestInstancePostProcessorProvideadditionalbehaviorjustafterthetest
instantiation
BeforeAllCallbackProvideadditionalbehaviorbeforealltestsareinvokedinatestcontainer
BeforeEachCallbackProvideadditionalbehaviortotestsbeforeeachtestisinvoked
BeforeTestExecutionCallbackProvideadditionalbehaviortotestsimmediatelybeforeeachtestisexecuted
TestExecutionExceptionHandler Handleexceptionsthrownduringtestexecution
AfterAllCallbackProvideadditionalbehaviortotestcontainersafteralltestshavebeeninvoked
AfterEachCallbackProvideadditionalbehaviortotestsaftereachtesthasbeeninvoked
AfterTestExecutionCallbackProvideadditionalbehaviortotestsimmediatelyaftereachtesthasbeenexecuted
ExecutionCondition Conditionatethetestexecutionatruntime
ParameterResolver Resolveparametersatruntime
Oncewecreatedanextension,inordertouseit,weneedtousetheannotationExtendWith.Thisannotationcanbeusedtoregisteroneormoreextensions.Itcanbedeclaredoninterfaces,classes,methods,fields,andeveninotherannotations:
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
publicclassMyTest{
@ExtendWith(MyExtension.class)
@Test
publicvoidtest(){
//Mytestlogic
}
}
TestlifecycleThereareasetofextensionpointsaimedatcontrollingthelifecycleoftests.Firstofall,theTestInstancePostProcessorcanbeusedtoexecutesomelogicafterthetestinstantiation.Afterthat,therearedifferentextensionswhichcontrolthepre-teststage:
TheBeforeAllCallbackdefinesthelogicexecutedbeforealltests.TheBeforeEachCallbackdefinesthelogicexecutedbeforeatestmethod.TheBeforeTestExecutionCallbackdefinesthelogicexecutedimmediatelybeforeatestmethod.
Similarly,thereareextensionstocontrolthepost-testphases:
TheAfterAllCallbackdefinesthelogicexecutedafteralltests.TheAfterEachCallbackdefinesthelogicexecutedafteratestmethod.TheAfterTestExecutionCallbackdefinesthelogicexecutedimmediatelyafteratestmethod.
InbetweentheBefore*andAfter*callbacks,thereisanextensionthatprovidesawayforcollectingexceptions:theTestExecutionExceptionHandler.
Allthesecallbacks,andtheirorderinthetestlifecyclaredepictedinthefollowingpicture:
Lifecycleofextensioncallbacks
Let’sseeanexample.WecreatedanextensioncalledIgnoreIOExceptionExtension,whichimplementsTestExecutionExceptionHandler.Inthisexample,theextension
checkswhetherornottheexceptionisIOException.Ifso,theexceptionisdiscarded:
packageio.github.bonigarcia;
importjava.io.IOException;
importorg.junit.jupiter.api.extension.ExtensionContext;
importorg.junit.jupiter.api.extension.TestExecutionExceptionHandler;
publicclassIgnoreIOExceptionExtension
implementsTestExecutionExceptionHandler{
@Override
publicvoidhandleTestExecutionException(ExtensionContextcontext,
Throwablethrowable)throwsThrowable{
if(throwableinstanceofIOException){
return;
}
throwthrowable;
}
}
Considerthefollowingtestclass,whichcontainstwotests(@Test).Thefirstoneisannotatedwith@ExtendWithandourcustomextension(IgnoreIOExceptionExtension):
packageio.github.bonigarcia;
importjava.io.IOException;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
publicclassExceptionTest{
@ExtendWith(IgnoreIOExceptionExtension.class)
@Test
publicvoidfirstTest()throwsIOException{
thrownewIOException("IOException");
}
@Test
publicvoidsecondTest()throwsIOException{
thrownewIOException("MyIOException");
}
}
Whenexecutingthistestclass,thefirsttestissucceededduetothefactthattheIOExceptionhasbeeninternallyhandledbyourextension.Ontheotherhand,thesecondwillfailsincethatexceptionisnothandled.
Theexecutionofthistestclassintheconsolecanbeseeninthenextscreenshot.NotethatweselectthetesttobeexecutedusingtheMavencommandmvntest-Dtest=ExceptionTest:
ConditionalextensionpointsInordertocreateextensionsthatactivateordeactivatetestsdependingonagivencondition,JUnit5providesoneconditionalextensionpointcallledExecutionCondition.Thefollowingsnippetshowsthedeclarationofthisextensionpoint:
packageorg.junit.jupiter.api.extension;
importstaticorg.apiguardian.api.API.Status.STABLE;
importorg.apiguardian.api.API;
@FunctionalInterface
@API(status=STABLE,since="5.0")
publicinterfaceExecutionConditionextendsExtension{
ConditionEvaluationResultevaluateExecutionCondition
ExtensionContextcontext);
}
Theextensioncanbeusedtodeactivateeitheralltestsinacontainer(likelyaclass)orindividualtests(likelyatestmethod).ExamplesofthisextensionareprovidedinthesectionConditionalTestExecutionofchapter3,JUnit5StandardTests.
DependencyinjectionTheParameterResolverextensionprovidesdependencyinjectionatmethodlevel.Inthisexample,wecanseehowanargumentisinjectedinthetestmethodwithacustomimplementationofParameterResolvercalledMyParameterResolver.Followingthecode,wecanseethatthisresolverwillsimplyinjecthard-codedStringparameterswiththevaluemyparameter:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.extension.ExtensionContext;
importorg.junit.jupiter.api.extension.ParameterContext;
importorg.junit.jupiter.api.extension.ParameterResolutionException;
importorg.junit.jupiter.api.extension.ParameterResolver;
publicclassMyParameterResolverimplementsParameterResolver{
@Override
publicbooleansupportsParameter(ParameterContextparameterContext,
ExtensionContextextensionContext)
throwsParameterResolutionException{
returntrue;
}
@Override
publicObjectresolveParameter(ParameterContextparameterContext,
ExtensionContextextensionContext)
throwsParameterResolutionException{
return"myparameter";
}
}
Then,thisparameterresolvercanbeusedinatest,declaringitasusualusingtheannotation@ExtendWith:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
publicclassDependencyInjectionTest{
@ExtendWith(MyParameterResolver.class)
@Test
publicvoidtest(Objectparameter){
System.out.println("Myparameter"+parameter);
}
}
Finally,ifweexecutethistest(forexampleusingMavenandthecommandline),wecanseehowtheinjectedparameterisloggedinthestandardoutput:
Third-partyextensionsIntherealworld,extensionstypicallyimplementseveralofthepreviouslyexplainedextensionpoints.Forexample,SpringExtension(explainedindetailinchapter5,IntegrationofJUnit5withexternalframeworks)implementstheextensionspointsBeforeAllCallback,TestInstancePostProcessor,ParameterResolver,amongothers.ThefollowingsnippetprovidesthestructureofSpringExtension:
packageorg.springframework.test.context.junit.jupiter;
importorg.junit.jupiter.api.extension.*;
publicclassSpringExtensionimplementsBeforeAllCallback,
AfterAllCallback,
TestInstancePostProcessor,BeforeEachCallback,AfterEachCallback,
BeforeTestExecutionCallback,AfterTestExecutionCallback,
ParameterResolver{
@Override
publicvoidafterTestExecution(TestExtensionContextcontext)
throwsException{
//implementation
}
//Restofmethods
}
AlistofexistingJUnit5extensions(forexample,Spring,Selenium,Docker,andothers)ismaintainedbythecommunityinthewikilocatedintheGitHubsiteoftheJUnit5team:https://github.com/junit-team/junit5/wiki/Third-party-Extensions.Someofthemarealsodetailedinchapter5,IntegrationofJUnit5withexternalframeworks.
SummaryThischapterprovidesanoverviewoftheJUnit5testingframework.DuetothelimitationsofJUnit4(monolithicarchitecture,impossibilityofcomposetestrunners,andlimitationsoftestrules),anewmajorversionoftheframeworkwasneeded.Inordertocarryouttheimplementations,theJUnitLambdaprojectstartedacrowdfundingcampaignin2015.Asaresult,theJUnit5developmentteamwasborn,andtheGAreleaseoftheframeworkwasreleasedonSeptember10,2017.
JUnit5wasdesignedtobemodern(thatis,usingJava8andJava9compliantfromtheverybeginning)andmodular.ThethreemajorcomponentswithinJUnit5are:Jupiter(newprogramminganextensionmodel),Platform(foundationforanytestingframeworkexecutedintheJVM),andVintage(integrationwithlegacyJUnit3and4tests).Atthetimeofthiswriting,JUnit5testscanbeexecutedusingbuildtools(MavenorGradle)andalsowithIDEs(IntelliJ2016.2+orEclipse4.7+).
TheextensionmodelofJUnit5allowstoextendthecorefunctionalityofJUnit5byanythirdparty.InordertocreateJUnit5extensions,weneedtoimplementoneorseveralJUnitextensionpoints(suchasBeforeAllCallback,ParameterResolver,orExecutionCondition,amongothers),andthenregistertheextensioninourtestsusingtheannotation@ExtendWith.
Inthenextchapter3,JUnit5StandardTests,wearegoingtolearnthebasicsoftheJupiterprogrammingmodel.Inotherwords,wearegoingtolearnhowtocreatestandardJUnit5tests.
JUnit5StandardTestsTalkischeap.Showmethecode.
-LinusTorvalds
JUnit5providesabrand-newprogrammingmodelcalledJupiter.WecanseethisprogrammingmodelasanAPIforsoftwareengineersandtesterswhichallowtocreateJUnit5tests.ThesetestsarelaterexecutedontheJUnitPlatform.Aswewilldiscover,theJupiterprogrammingmodelallowstocreatemanydifferenttypesoftests.ThischaptertacklesthebasicsofJupiter.Tothataim,thischapterisstructuredasfollows:
Testlifecycle:Inthissection,weanalyzethestructureoftheJupitertests,describingtheannotationsinvolvedinthemanagementofthetestlifecycleintheJUnit5programmingmodel.Then,wediscoverhowtoskiptests,andalsohowtoannotatetestswithacustomdisplayname.Assertions:Inthissection,firstwepresentabriefoverviewoftheverificationassets,calledassertions(alsoknownaspredicates).Second,westudyhowtheassertionshavebeenimplementedinJupiter.Finally,wepresentseveralthird-partylibrariesaboutassertions,providingsomeexamplesforHamcrest.Taggingandfilteringtests:Inthissection,firstwewilllearnhowtolabelJupitertests,thatis,howtocreatetagsinJUnit5.Then,wewilllearnhowtofilterourtestsusingMavenandGradle.Finally,wearegoingtoanalyzehowtocreatemeta-annotationsusingJupiter.Conditionaltestexecution:Inthissection,wewilllearnhowtodisabletestsbasedonagivencondition.Afterthat,wemakeareviewoftheso-calledassumptionsinJupiter,whichareamechanismprovidedoutoftheboxbyJupitertoruntestsonlyifcertainconditionsareasexpected.Nestedtests:ThissectionpresentshowJupiterallowstoexpresstherelationshipamongagroupoftests,callednestedtests.Repeatedtests:ThissectionreviewshowJupiterprovidestheabilitytorepeatatestaspecifiednumberoftimes.MigrationfromJUnit4toJUnit5:ThissectionprovidesasetofhintsaboutthemaindifferencesbetweenJUnit5anditsimmediateantecessor,thatis,JUnit4.Then,thissectionpresentsthesupportforseveralJUnit4ruleswithinJupitertests.
TestlifecycleAswesawinChapter1,RetrospectiveonsoftwarequalityandJavatesting,aunittestcaseiscomposedoffourstages:
1. Setup(optional):First,thetestinitializesthetestfixture(beforethepictureoftheSUT).
2. Exercise:Second,thetestinteractswiththeSUT,gettingsomeoutcomefromitasaresult.
3. Verify:Third,theoutcomefromthesystemundertestiscomparedtotheexpectedvalueusingoneorseveralassertions(alsoknownaspredicates).Asaresult,atestverdictiscreated.
4. Teardown(optional):Finally,thetestreleasesthetestfixturetoputtheSUTbackintotheinitialstate.
InJUnit4,thereweredifferentannotationstocontrolthesetestphases.JUnit5followsthesameapproach,thatis,JavaannotationsareusedtoidentifydifferentmethodswithinJavaclasses,implementingthetestlifecycle.InJupiter,alltheseannotationsarecontainedinthepackageorg.junit.jupiter.api.
ThemostbasicJUnitannotationis@Test,whichidentifiesthemethodsthathavetobeexecutedastests.Therefore,aJavamethodannotatedwithorg.junit.jupiter.api.Testwillbetreatedasatest.ThedifferenceofthisannotationwithrespecttoJUnit4’[email protected],[email protected],@Testcandeclarethetesttimeout(aslongattributewiththetimeoutinmilliseconds),ontheotherhand,inJUnit5,neithertestclassesnortestmethodsneedtobepublic(thiswasarequirementinJUnit4).
TakealookatthefollowingJavaclass.Possibly,itisthesimplesttestcasewecancreatewithJupiter.Ithassimplyamethodwiththe@Testannotation.Thetestlogic(thatistheexerciseandverifystagesasdescribedbefore)wouldbecontainedinsidethemethodmyTest.
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Test;
classSimpleJUnit5Test{
@Test
voidmySimpleTest(){
//Mytestlogichere
}
}
TheJupiterannotations(alsolocatedinthepackageorg.junit.jupiter.api)aimedtocontrolthesetupandteardownstagesinJUnit5testsaredescribedinthefollowingtable:
JUnit5annotation Description JUnit4’s
equivalence
@BeforeEachMethodexecutedbeforeeach@Testinthecurrentclass
@Before
@AfterEachMethodexecutedaftereach@Testinthecurrentclass
@After
@BeforeAllMethodexecutedbeforeall@Testinthecurrentclass
@BeforeClass
@AfterAllMethodexecutedafterall@Testinthecurrentclass
@AfterClass
Methodsannotatedwiththeseannotations(@BeforeEach,@AfterEach,@AfterAll,and@BeforeAll)arealwaysinherited.
ThefollowingpicturedepictstheorderofexecutionoftheseannotationsinaJavaclass:
Jupiterannotationstocontrolthetestlyfecycle
Let’sgobacktothegenericstructurefortestswesawatthebeginningofthissection.Now,weareabletomaptheJupiterannotationstocontrolthetestlifecyclewiththedifferentpartsofatestcase.Asillustratedinthefollowing
picture,wecarryoutthesetupstagebyannotatingmethodswith@[email protected],wecarryouttheexerciseandverifystagesinmethodsannotatedwith@Test.Finally,wecarryouttheteardownprocessinthemethodswith@AfterEachand@AfterAll.
RelationshipamongtheunittestcasesstagesandtheJupiterannotations
Let’sseeasimpleexample,whichusesalltheseannotationsinasingleJavaclass.Thisexampledefinestwotests(thatis,twomethodsannotatedwith@Test),andwedefineadditionalmethodsfortherestofthetestlifecyclewiththeannotations@BeforeAll,@BeforeEach,@AfterEach,and@AfterAll:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.AfterAll;
importorg.junit.jupiter.api.AfterEach;
importorg.junit.jupiter.api.BeforeAll;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Test;
classLifecycleJUnit5Test{
@BeforeAll
staticvoidsetupAll(){
System.out.println("SetupALLTESTSintheclass");
}
@BeforeEach
voidsetup(){
System.out.println("SetupEACHTESTintheclass");
}
@Test
voidtestOne(){
System.out.println("TEST1");
}
@Test
voidtestTwo(){
System.out.println("TEST2");
}
@AfterEach
voidteardown(){
System.out.println("TeardownEACHTESTintheclass");
}
@AfterAll
staticvoidteardownAll(){
System.out.println("TeardownALLTESTSintheclass");
}
}
Ifwerunthistestclass,[email protected],thetwotestmethodswillbeexecutedsequentially,thatis,thefirstoneandthentheother.Ineachexecution,thesetupmethodannotatedwith@BeforeEachwillbeexecutedbeforethetest,andthenthe@AfterEachmethod.ThefollowingscreenshotshowsanexecutionofthetestsusingMavenandthecommandline:
ExecutionofaJupitertestwhichcontrolsitslifecycle
TestinstancelifecycleInordertoprovideexecutioninisolation,theJUnit5frameworkcreatesanewtestinstancebeforeexecutingtheactualtest(thatis,themethodannotatedwith@Test).Thisper-methodtestinstancelifecycleisthebehaviorintheJupitertestandalsoinitsantecessors(JUnit3and4).Asanovelty,thisdefaultbehaviorcanbechangedinJUnit5,simplybyannotatingatestclasswith@TestInstance(Lifecycle.PER_CLASS).Usingthismode,thetestinstancewillbecreatedonceperclass,insteadofoncepertestmethod.
Thisper-classbehaviorimpliesthatitispossibletodeclarethe@BeforeAlland@AfterAllmethodsasnon-static.Thisisbeneficialtobeusedinconjunctionwithsomeadvancedcapabilities,suchasnestedtestordefaulttestinterfaces(explainedinthenextchapter).
Allinall,andtakingintoaccounttheextensioncallback(asexplainedintheTheextensionmodelofJUnit5sectionofChapter2,What’snewinJUnit5),therelativeexecutionorderofusercodeandextensionsisdepictedinthefollowingpicture:
Relativeexecutionorderofusercodeandextensions
SkippingtestsTheJupiterannotation@Disabled(locatedinthepackageorg.junit.jupiter.api)canbeusedtoskiptests.Itcanbeusedatclasslevelormethodlevel.Thefollowingexampleusestheannotation@Disabledatmethodlevelandthereforeitforcestoskipthetest:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Disabled;
importorg.junit.jupiter.api.Test;
classDisabledTest{
@Disabled
@Test
voidskippedTest(){
}
}
Asshowninthefollowingscreenshot,whenweexecutethisexample,thetestwillbecountedasskipped:
Disabledtestmethodconsoleoutput
Inthisotherexample,theannotation@Disabledisplacedattheclasslevelandthereforeallthetestscontainedintheclasswillbeskipped.Notethatacustommessage,typicallywiththereasonofthedisabling,canbespecifiedwithintheannotation:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Disabled;
importorg.junit.jupiter.api.Test;
@Disabled("Alltestinthisclasswillbeskipped")
classAllDisabledTest{
@Test
voidskippedTestOne(){
}
@Test
voidskippedTestTwo(){
}
}
Thefollowingscreenshotshowshowthetestcaseisskippedwhenitisexecuted(inthisexampleusingMavenandthecommandline):
Disabledtestclassconsoleoutput
DisplaynamesJUnit4identifiedtestsbasicallywiththenameofthemethodannotatedwith@Test.Thisimposesalimitationonnametests,sincethesenamesareconstrainedbythewayofdeclaringmethodsinJava.
Toovercomethisproblem,Jupiterprovidestheabilityofdeclaringacustomdisplayname(differenttothetestname)fortests.Thisisdonewiththeannotation@DisplayName.Thisannotationdeclaresacustomdisplaynameforatestclassoratestmethod.Thisnamewillbedisplayedbytestrunnersandreportingtools,anditcancontainspaces,specialcharacters,andevenemojis.
Takealookatthefollowingexample.Weareannotatingthetestclass,andalsothethreetestmethodsdeclaredinsidetheclasswithacustomtestnameusing@DisplayName:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Test;
@DisplayName("Aspecialtestcase")
classDisplayNameTest{
@Test
@DisplayName("Customtestnamecontainingspaces")
voidtestWithDisplayNameContainingSpaces(){
}
@Test
@DisplayName("(╯°Д°)╯")voidtestWithDisplayNameContainingSpecialCharacters(){
}
@Test
@DisplayName(" ")
voidtestWithDisplayNameContainingEmoji(){
}
}
Asaresult,weseetheselabelswhenexecutingthistestinaJUnit5compliantIDE.ThefollowingpictureshowstheexecutionoftheexampleonIntelliJ2016.2+:
Executionofatestcaseusing@DisplayNameinIntelliJ
Ontheotherhand,thedisplaynamecanbealsoseeninEclipse4.7(Oxygen)ornewer:
Executionofatestcaseusing@DisplayNameinEclipse
AssertionsAsweknow,thegeneralstructureofatestcaseiscomposedoffourstages:setup,exercise,verify,andteardown.Theactualtesthappensduringthesecondandthirdstage,whenthetestlogicinteractswiththesystemundertest,gettingsomekindofoutcomefromit.Thisoutcomeiscomparedwiththeexpectedresultintheverifystage.Inthisstage,wefindwhatwecallassertions.Inthissection,wetakeacloserlookatthem.
Anassertion(alsoknownasapredicate)isabooleanstatementtypicallyusedtoreasonaboutsoftwarecorrectness.Fromatechnicalpointofview,anassertioniscomposedofthreeparts(seetheimageafterthelist):
1. First,wefindtheexpectedvalue,whichcomesfromwhatwecalltestoracles.Atestoracleisareliablesourceofexpectedoutputs,forexample,thesystemspecification.
2. Second,wefindtherealoutcome,whichcomesfromtheexercisestagemadebythetestagainsttheSUT.
3. Finally,thesetwovaluesarecomparedusingsomelogiccomparator.Thiscomparisoncanbedoneinmanydifferentways,forexample,wecancomparetheobjectidentity(equalsornot),themagnitude(higherorlowervalue),andsoon.Asaresult,weobtainatestverdict,which,intheend,isgoingtodefineifthetesthassucceededorfailed.
Schematicviewofanassertion
JupiterassertionsLet’smoveontotheJUnit5programmingmodel.JupitercomeswithmanyoftheassertionmethodssuchastheonesinJUnit4,andalsoaddsseveralthatcanbeusedwithJava8lambdas.AllJUnitJupiterassertionsarestaticmethodsintheAssertionsclasslocatedinorg.junit.jupiterpackage.
Thefollowingpictureshowsthecompletelistofthesemethods:
CompletelistofJupiterassertions(classorg.junit.jupiter.Assertions)
ThefollowingtablereviewsthedifferenttypesofbasicassertionsinJupiter:
Assertion Description
fail Failsatestwithagivenmessageand/orexception
assertTrue Assertsthatasuppliedconditionistrue
assertFalse Assertsthatasuppliedconditionisfalse
assertNull Assertsthatasuppliedobjectisnull
assertNotNull Assertsthatasuppliedobjectisnotnull
assertEquals Assertsthattwosuppliedobjectsareequal
assertArrayEquals Assertsthattwosuppliedarraysareequal
assertIterableEquals Assertsthattwoiterableobjectsaredeeplyequal
assertLinesMatch AssertsthattwolistsofStringsareequals
assertNotEquals Assertsthattwosuppliedobjectsarenotequal
assertSame Assertsthattwoobjectsarethesame,comparedwith==
assertNotSame Assertsthattwoobjectsaredifferent,comparedwith!=
Foreachoftheassertionscontainedinthetable,anoptionalfailuremessage(String)canbeprovided.Thismessageisalwaysthelastparameterintheassertionmethod.ThisisasmalldifferencewithrespecttoJUnit4,inwhichthismessagewasthefirstparameterinthemethodinvocation.
ThefollowingexampleshowsatestusingtheassertEquals,assertTrue,andassertFalseassertion.Notethatweareimportingthestaticassertionmethodsatthebeginningoftheclassinordertoimprovethereadabilityofthetestlogic.Intheexample,wefindtheassertEqualsmethod,inthiscasecomparing
twoprimitivetypes(itcouldalsobeusedforobjects).Second,themethodassertTrueevaluatesifabooleanexpressionistrue.Third,themethodassertFalseevaluatesifaBooleanexpressionisfalse.Inthiscase,noticethatthemessageiscreatedasaLamdbaexpression.Thisway,assertionmessagesarelazilyevaluatedtoavoidconstructingcomplexmessagesunnecessarily:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.junit.jupiter.api.Assertions.assertFalse;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.api.Test;
classStandardAssertionsTest{
@Test
voidstandardAssertions(){
assertEquals(2,2);
assertTrue(true,
"Theoptionalassertionmessageisnowthelastparameter");
assertFalse(false,()->"Really"+"expensive"+"message"
+".");
}
}
ThefollowingpartsofthissectionreviewtheadvanceassertionsprovidedbyJupiter:assertAll,assertThrows,assertTimeout,andassertTimeoutPreemptively.
GroupofassertionsAnimportantJupiterassertionisassertAll.Thismethodallowstogroupdifferentassertionsatthesametime.Inagroupedassertion,allassertionsarealwaysexecuted,andanyfailureswillbereportedtogether.
ThemethodassertAllacceptsavargargsoflambdaexpressions(Executable…)orastreamofthose(Stream<Executable>).Optionally,thefirstparameterofassertAllcanbeaStringmessageaimedtolabeltheassertiongroup.
Let’sseeanexample.Inthefollowingtest,wearegroupingacoupleofassertEqualsusinglambdaexpressions:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertAll;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importorg.junit.jupiter.api.Test;
classGroupedAssertionsTest{
@Test
voidgroupedAssertions(){
Addressaddress=newAddress("John","Smith");
//Inagroupedassertionallassertionsareexecuted,andany
//failureswillbereportedtogether.
assertAll("address",()->assertEquals("John",
address.getFirstName()),
()->assertEquals("User",address.getLastName()));
}
}
Whenexecutingthistest,allassertionsofthegroupwillbeevaluated.Sincethesecondassertionfails(lastnamedoesnotmatch),onefailureisreportedinthefinalverdict,ascanbeseeninthefollowingscreenshot:
AssertingexceptionsAnotherimportantJupiterassertionisassertThrows.Thisassertionallowstoverifyifagivenexceptionisraisedinapieceofcode.Tothataim,themethodassertThrowsacceptstwoarguments.First,theexceptionclassexpected,andsecond,anexecutableobject(lambdaexpression),inwhichtheexceptionissupposedtohappen:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.junit.jupiter.api.Assertions.assertThrows;
importorg.junit.jupiter.api.Test;
classExceptionTest{
@Test
voidexceptionTesting(){
Throwableexception=
assertThrows(IllegalArgumentException.class,
()->{
thrownewIllegalArgumentException("amessage");});
assertEquals("amessage",exception.getMessage());
}
}
TheisexpectingIllegalArgumentExceptiontobethrown,andthisisactuallyhappeninginsidethislambdaexpression.Thefollowingscreenshotshowsthatthetestactuallysucceeds:
ConsoleoutputofassertThrowsexample
AssertingtimeoutsToassesstimeoutsinJUnit5tests,Jupiterprovidestwoassertions:assertTimeoutandassertTimeoutPreemptively.Ontheonehand,assertTimeout,allowsustoverifythetimeoutofagivenoperation.Inthisassertion,theexpectedtimeisdefinedusingtheclassDurationofthestandardJavapackagejava.time.
Wearegoingtoseeseveralrunningexamplestoclarifytheuseofthisassertionmethod.Inthefollowingclass,wefindtwotestsusingassertTimeout.Thefirsttestisdesignedtobesucceeded,duetothefactthatweareexpectingthatagivenoperationtakeslessthan2minutes,andwearedoingnothingthere.Ontheotherside,thesecondtestwillfail,sinceweareexpectingthatagivenoperationtakesamaximumof10milliseconds,andweareforcingittolast100milliseconds.
packageio.github.bonigarcia;
importstaticjava.time.Duration.ofMillis;
importstaticjava.time.Duration.ofMinutes;
importstaticorg.junit.jupiter.api.Assertions.assertTimeout;
importorg.junit.jupiter.api.Test;
classTimeoutExceededTest{
@Test
voidtimeoutNotExceeded(){
assertTimeout(ofMinutes(2),()->{
//Performtaskthattakeslessthan2minutes
});
}
@Test
voidtimeoutExceeded(){
assertTimeout(ofMillis(10),()->{
Thread.sleep(100);
});
}
}
Whenweexecutethistest,thesecondtestisdeclaredasfailedbecausethetimeouthasbeenexceededin90milliseconds:
ConsoleoutputofassertTimeoutfirstexample
Let’sseeacouplemoretestsusingassertTimeout.Inthefirsttest,assertTimeoutevaluatesapieceofcodeasalambdaexpressioninagiventimeout,obtainingitsresult.Inthesecondtest,assertTimeoutevaluatesamethodinagiventimeout,obtainingitsresult:
packageio.github.bonigarcia;
importstaticjava.time.Duration.ofMinutes;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.junit.jupiter.api.Assertions.assertTimeout;
importorg.junit.jupiter.api.Test;
classTimeoutWithResultOrMethodTest{
@Test
voidtimeoutNotExceededWithResult(){
StringactualResult=assertTimeout(ofMinutes(1),()->{
return"hithere";
});
assertEquals("hithere",actualResult);
}
@Test
voidtimeoutNotExceededWithMethod(){
StringactualGreeting=assertTimeout(ofMinutes(1),
TimeoutWithResultOrMethodTest::greeting);
assertEquals("helloworld!",actualGreeting);
}
privatestaticStringgreeting(){
return"helloworld!";
}
}
Inbothcases,theteststakelesstimethanexpectedandthereforebothofthemaresucceeded:
ConsoleoutputofassertTimeoutsecondexample
TheotherJupiterassertionfortimeoutsiscalledassertTimeoutPreemptively.ThedifferencewithassertTimeoutPreemptivelywithrespecttoassertTimeoutisthatassertTimeoutPreemptivelydoesnotwaituntiltheendoftheoperation,andtheexecutionisabortedwhentheexpectedtimeoutisexceeded.
Inthisexample,thetestwillfailsincewearesimulatinganoperationwhichlasts100milliseconds,andwehavedefinedatimeoutof10milliseconds:
packageio.github.bonigarcia;
importstaticjava.time.Duration.ofMillis;
importstaticorg.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
importorg.junit.jupiter.api.Test;
classTimeoutWithPreemptiveTerminationTest{
@Test
voidtimeoutExceededWithPreemptiveTermination(){
assertTimeoutPreemptively(ofMillis(10),()->{
Thread.sleep(100);
});
}
}
Inthisexample,whenthetimeoutof10msisreached,instantlythetestisdeclaredasafailure:
ConsoleoutputofassertTimeoutPreemptivelyexample
Third-partyassertionlibrariesAswehaveseen,thebuilt-inassertionsprovidedoutoftheboxforJupiteraresufficientformanytestingscenarios.Nevertheless,therearetimeswhenmoreadditionalfunctionality,suchasmatchers,canbedesiredorrequired.Insuchsituations,theJUnitteamrecommendstheuseofthefollowingthird-partyassertionlibraries:
Hamcrest(http://hamcrest.org/):anassertionframeworktowritematcherobjectsallowingrulestobedefineddeclaratively.AssertJ(http://joel-costigliola.github.io/assertj/):fluentassertionsforJava.Truth(https://google.github.io/truth/):anassertionsJavalibrarydesignedtomaketestassertionsandfailuremessagesmorereadable.
Inthissection,wearegoingtomakeabriefreviewofHamcrest.ThislibraryprovidedtheassertionassertThat,whichallowstocreatereadablehighlyconfigurableassertions.ThemethodassertThatacceptstwoarguments:firsttheactualobject,andsecondaMatcherobject.Thismatcherimplementstheinterfaceorg.hamcrest.Matcher,andenablesapartialoranexactmatchforanexpectation.Hamcrestprovidesdifferentmatcherutilities,suchasis,either,or,not,andhasItem.TheMatchermethodsusethebuilderpattern,allowingtocombineoneormorematcherstobuildamatcherchain.
InordertouseHamcrest,firstweneedtoimportthedependencyinourproject.InaMavenproject,thismeansthatwehavetoincludethefollowingdependencyinourpom.xmlfile:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
IfweareusingGradle,weneedtoaddtheequivalentconfigurationwithinthebuild.gradlefile:
dependencies{
testCompile("org.hamcrest:hamcrest-core:${hamcrest}")
}
Asusual,itisrecommendedusingthelatestversionofHamcrest.WecancheckitontheMavencentralweb(http://search.maven.org/).
ThefollowingexampledemonstrateshowtouseHamcrestinsideaJupitertest.Concretely,thistestusestheassertionassertThattogetherwiththematcherscontainsString,equalTo,andnotNullValue:
packageio.github.bonigarcia;
importstaticorg.hamcrest.CoreMatchers.containsString;
importstaticorg.hamcrest.CoreMatchers.equalTo;
importstaticorg.hamcrest.CoreMatchers.notNullValue;
importstaticorg.hamcrest.MatcherAssert.assertThat;
importorg.junit.jupiter.api.Test;
classHamcrestTest{
@Test
voidassertWithHamcrestMatcher(){
assertThat(2+1,equalTo(3));
assertThat("Foo",notNullValue());
assertThat("Helloworld",containsString("world"));
}
}
Asshowninthefollowingscreenshot,thistestisexecutedwithnofailure:
ConsoleoutputofexampleusingtheHamcrestassertionlibrary
TaggingandfilteringtestsTestclassesandmethodscanbetaggedintheJUnit5programmingmodelbymeansoftheannotation@Tag(packageorg.junit.jupiter.api).Thosetagscanlaterbeusedtofiltertestdiscoveryandexecution.Inthefollowingexample,weseetheuseof@Tagatclasslevelandalsoatmethodlevel:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
@Tag("simple")
classSimpleTaggingTest{
@Test
@Tag("taxes")
voidtestingTaxCalculation(){
}
}
AsofJUnit5M6,thelabelfortaggingtestsshouldmeetthefollowingsyntaxrules:
Atagmustnotbenullorblank.Atrimmedtag(thatis,tagsinwhichleadingandtrailingwhitespacehavebeenremoved)mustnotcontainawhitespace.AtrimmedtagmustnotcontainISOcontrolcharactersnorthefollowingreservedcharacters:,,(,),&,|,and!.
FilteringtestswithMavenAswealreadyknow,weneedtousemaven-surefire-plugininaMavenprojecttoexecuteJupitertest.Moreover,thispluginallowsustofilterthetestexecutioninseveralways:filteringbyJUnit5tagsandalsousingtheregularinclusion/exclusionsupportofmaven-surefire-plugin.
Inordertofilterbytags,thepropertiesincludeTagsandexcludeTagsofthemaven-surefire-pluginconfigurationshouldbeused.Let’sseeanexampletodemonstratehow.ConsiderthefollowingtestscontainedinthesameMavenproject.Ontheonehand,alltestsinthisclassaretaggedwiththefunctionalword.
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
@Tag("functional")
classFunctionalTest{
@Test
voidtestOne(){
System.out.println("FunctionalTest1");
}
@Test
voidtestTwo(){
System.out.println("FunctionalTest2");
}
}
Ontheotherhand,alltestsinthesecondclassaretaggedasnon-functionalandeachindividualtestisalsolabeledwithmoretags(performance,security,usability,andsoon):
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
@Tag("non-functional")
classNonFunctionalTest{
@Test
@Tag("performance")
@Tag("load")
voidtestOne(){
System.out.println("Non-FunctionalTest1(Performance/Load)");
}
@Test
@Tag("performance")
@Tag("stress")
voidtestTwo(){
System.out.println("Non-FunctionalTest2(Performance/Stress)");
}
@Test
@Tag("security")
voidtestThree(){
System.out.println("Non-FunctionalTest3(Security)");
}
@Test
@Tag("usability")
voidtestFour(){
System.out.println("Non-FunctionalTest4(Usability)");
}
}
Asdescribedbefore,weusetheconfigurationkeywordsincludeTagsandexcludeTagsintheMavenpom.xmlfile.Inthisexample,weincludethetestwiththetagfunctionalandexcludenon-functional:
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<properties>
<includeTags>functional</includeTags>
<excludeTags>non-functional</excludeTags>
</properties>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
Asaresult,whenwetrytoexecuteallthetestswithintheproject,onlytwowillbeexecuted(thosewiththetagfunctional),andtherestarenotrecognizedastests:
MavenregularsupportTheregularinclusion/exclusionsupportoftheMavenplugincanstillbeusedtoselectwhichtestsaregoingtobeexecutedbymaven-surefire-plugin.Tothataim,weusethekeywordsincludesandexcludestoconfigurethetestnamepatternusedtofiltertheexecutionbytheplugin.Noticethatforbothinclusionsandexclusions,regularexpressionscanbeusedtospecifyapatternofthetestfilenames:
<configuration>
<includes>
<include>**/Test*.java</include>
<include>**/*Test.java</include>
<include>**/*TestCase.java</include>
</includes>
</configuration>
<configuration>
<excludes>
<exclude>**/TestCircle.java</exclude>
<exclude>**/TestSquare.java</exclude>
</excludes>
</configuration>
Thesethreepatterns,thatis,theJavafilescontainingthewordTestorendingwithTestCase,areincludedbydefaultbyamaven-surefireplugin.
FilteringtestswithGradleLet’smovenowtoGradle.Aswealreadyknow,wecanalsouseGradletorunJUnit5tests.Regardingthefilteringprocess,wecanselectthetesttobeexecutedbasedon:
Thetestengine:Usingthekeywordengineswecanincludeorexcludethetestenginetobeused(thatisjunit-jupiterorjunit-vintage).TheJupitertags:Usingthekeywordtags.TheJavapackages:Usingthekeywordpackages.Theclassnamepatterns:UsingthekeywordincludeClassNamePattern.
Bydefault,allenginesandtagsareincludedinthetestplan.OnlytheclassnamecontainingthewordTestsisapplied.Let’sseeaworkingexample.WereusethesametestspresentedintheformerMavenproject,butthistimeinaGradleproject:
junitPlatform{
filters{
engines{
include'junit-jupiter'
exclude'junit-vintage'
}
tags{
include'non-functional'
exclude'functional'
}
packages{
include'io.github.bonigarcia'
exclude'com.others','org.others'
}
includeClassNamePattern'.*Spec'
includeClassNamePatterns'.*Test','.*Tests'
}
}
Noticethatweareincludingthetagsnon-functionalandexcludingfunctional,andthereforeweexecutefourtests:
Meta-annotationsThefinalpartofthissectionisaboutthedefinitionofmeta-annotations.TheJUnitJupiterannotationscanbeusedinthedefinitionofotherannotations(thatis,canbeusedasmeta-annotations).Thatmeansthatwecandefineourowncomposedannotationthatwillautomaticallyinheritthesemanticsofitsmeta-annotations.ThisfeatureisveryconvenienttocreateourcustomtesttaxonomybyreusingtheJUnit5annotation@Tag.
Let’sseeanexample.Considerthefollowingclassificationfortestcases,inwhichweclassifyalltestsasfunctionalandnon-functional,andthenwemakeanotherlevelunderthenon-functionaltests:
Exampletaxonomyfortests(functionalandnon-functional)
Withthatschemeinmind,wearegoingtocreateourcustommeta-annotationsforleavesofthattreestructure:@Functional,@Security,@Usability,@Accessiblity,@Load,[email protected]@Tagannotations,dependingonthestructurepreviouslydefined.First,wecanseethedeclarationof@Functional:
packageio.github.bonigarcia;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importorg.junit.jupiter.api.Tag;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Tag("functional")
public@interfaceFunctional{
}
Then,wedefinetheannotation@Securitywithtagsnon-functionalandsecurity:packageio.github.bonigarcia;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importorg.junit.jupiter.api.Tag;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Tag("non-functional")
@Tag("security")
public@interfaceSecurity{
}
Similarly,wedefinetheannotation@Load,butthistimetaggingwithnon-functional,performance,andload:
packageio.github.bonigarcia;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importorg.junit.jupiter.api.Tag;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Tag("non-functional")
@Tag("performance")
@Tag("load")
public@interfaceLoad{
}
Finallywecreatetheannotation@Stress(withtagsnon-functional,performance,andstress):
packageio.github.bonigarcia;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importorg.junit.jupiter.api.Tag;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Tag("non-functional")
@Tag("performance")
@Tag("stress")
public@interfaceStress{
}
Now,wecanuseourannotationstotag(andlaterfilter)tests.Forinstance,inthefollowingexampleweareusingtheannotation@Functionalatclasslevel:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Test;
@Functional
classFunctionalTest{
@Test
voidtestOne(){
System.out.println("Test1");
}
@Test
voidtestTwo(){
System.out.println("Test2");
}
}
Wecanalsooutannotationsatmethodlevel.Inthefollowingtest,weannotatethedifferenttests(methods)withdifferentannotations(@Load,@Stress,@Security,and@Accessibility):
packageio.github.bonigarcia;
importorg.junit.jupiter.api.Test;
classNonFunctionalTest{
@Test
@Load
voidtestOne(){
System.out.println("Test1");
}
@Test
@Stress
voidtestTwo(){
System.out.println("Test2");
}
@Test
@Security
voidtestThree(){
System.out.println("Test3");
}
@Test
@Usability
voidtestFour(){
System.out.println("Test4");
}
}
Allinall,wecanfilterthetestbysimplychangingtheincludedtags.Ontheonehand,wecanfilterbythetagfunctional.Noticethatinthiscase,onlytwotestsareexecuted.ThefollowingsnippetshowstheoutputofthiskindoffilteringusingMaven:
Filteringtestbytags(functional)usingMavenandthecommandline
Ontheotherhand,wecanalsofilterwithdifferenttags,suchasnon-functional.
Thefollowingpictureshowsanexampleofthistypeoffiltering,thistimeusingGradle.Asusual,wecanplaywiththeseexamplesbyforkingtheGitHubrepository(https://github.com/bonigarcia/mastering-junit5):
>
Filteringtestbytags(non-functional)usingGradleandthecommandline
ConditionaltestexecutionInordertoestablishcustomconditionsfortestexecution,weneedtousetheJUnit5extensionmodel(introducedinChapter2,What’snewinJUnit5,inthesectionTheextensionmodelofJUnit5).Concretely,weneedtousetheconditionalextensionpointcalledExecutionCondition.Thisextensioncanbeusedtodeactivateeitheralltestsinaclassorindividualtests.
Wearegoingtoseeaworkingexampleinwhichwecreateacustomannotationtodisabletestsbasedontheoperativesystem.Firstofall,wecreateacustomutilityenumerationtoselectoneoperativesystem(WINDOWS,MAC,LINUX,andOTHER):
packageio.github.bonigarcia;
publicenumOs{
WINDOWS,MAC,LINUX,OTHER;
publicstaticOsdetermine(){
Osout=OTHER;
StringmyOs=System.getProperty("os.name").toLowerCase();
if(myOs.contains("win")){
out=WINDOWS;
}
elseif(myOs.contains("mac")){
out=MAC;
}
elseif(myOs.contains("nux")){
out=LINUX;
}
returnout;
}
}
Then,wecreateanextensionofExecutionCondition.Inthisexample,theevaluationisdonebycheckingwhetherornotthecustomannotation@DisabledOnOsispresent.Whentheannotation@DisabledOnOsispresent,thevalueoftheoperativesystemiscomparedwiththecurrentplatform.Dependingontheresultofthatcondition,thetestisdisabledorenabled.
packageio.github.bonigarcia;
importjava.lang.reflect.AnnotatedElement;
importjava.util.Arrays;
importjava.util.Optional;
importorg.junit.jupiter.api.extension.ConditionEvaluationResult;
importorg.junit.jupiter.api.extension.ExecutionCondition;
importorg.junit.jupiter.api.extension.ExtensionContext;
importorg.junit.platform.commons.util.AnnotationUtils;
publicclassOsConditionimplementsExecutionCondition{
@Override
publicConditionEvaluationResultevaluateExecutionCondition(
ExtensionContextcontext){
Optional<AnnotatedElement>element=context.getElement();
ConditionEvaluationResultout=ConditionEvaluationResult
.enabled("@DisabledOnOsisnotpresent");
Optional<DisabledOnOs>disabledOnOs=AnnotationUtils
.findAnnotation(element,DisabledOnOs.class);
if(disabledOnOs.isPresent()){
OsmyOs=Os.determine();
if(Arrays.asList(disabledOnOs.get().value())
.contains(myOs)){
out=ConditionEvaluationResult
.disabled("Testisdisabledon"+myOs);
}
else{
out=ConditionEvaluationResult
.enabled("Testisnotdisabledon"+myOs);
}
}
System.out.println("-->"+out.getReason().get());
returnout;
}
}
Moreover,weneedtocreateourcustomannotation@DisabledOnOs,whichisalsoannotatedwith@ExtendWithpointingtoourextensionpoint.
packageio.github.bonigarcia;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
importorg.junit.jupiter.api.extension.ExtendWith;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(OsCondition.class)
public@interfaceDisabledOnOs{
Os[]value();
}
Finally,weuseourannotation@DisabledOnOsinaJupitertest.importorg.junit.jupiter.api.Test;
importstaticio.github.bonigarcia.Os.MAC;
importstaticio.github.bonigarcia.Os.LINUX;
classDisabledOnOsTest{
@DisabledOnOs({MAC,LINUX})
@Test
voidconditionalTest(){
System.out.println("ThistestwillbedisabledonMACandLINUX");
}
}
IfweexecutethistestinaWindowsmachine,thetestisnotskipped,aswecanseeinthissnapshot:
AssumptionsInthispartofthissectionisabouttheso-calledassumptions.Assumptionsallowustoonlyruntestsifcertainconditionsareasexpected.AllJUnitJupiterassumptionsarestaticmethodsintheclassAssumptions,locatedinsidetheorg.junit.jupiterpackage.Thefollowingscreenshotshowsallthemethodsofthisclass:
Methodsoftheclassorg.junit.jupiter.Assumptions
Ontheonehand,themethodsassumeTrueandassumeFalsecanbeusedtoskiptestswhosepreconditionsarenotmet.Ontheotherhand,themethodassumingThatisusedtoconditiontheexecutionofapartinatest:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.fail;
importstaticorg.junit.jupiter.api.Assumptions.assumeFalse;
importstaticorg.junit.jupiter.api.Assumptions.assumeTrue;
importstaticorg.junit.jupiter.api.Assumptions.assumingThat;
importorg.junit.jupiter.api.Test;
classAssumptionsTest{
@Test
voidassumeTrueTest(){
assumeTrue(false);
fail("Test1failed");
}
@Test
voidassumeFalseTest(){
assumeFalse(this::getTrue);
fail("Test2failed");
}
privatebooleangetTrue(){
returntrue;
}
@Test
voidassummingThatTest(){
assumingThat(false,()->fail("Test3failed"));
}
}
Noticethatinthisexample,thetwofirsttests(assumeTrueTestandassumeFalseTest)areskippedsincetheassumptionsarenotmet.Nevertheless,intheassummingThatTesttest,onlythispartofthetest(alambdaexpressioninthiscase)isnotexecuted,butthewholetestisnotskipped:
Executionofassumptionstestexample
NestedtestsNestedtestsgivethetestwritermorecapabilitiestoexpresstherelationshipandorderinagroupoftests.JUnit5makesiteffortlesstonesttestclasses.Wesimplyneedtoannotateinnerclasseswith@Nestedandalltestmethodsintherewillbeexecutedaswell,goingfromtheregulartests(definedinthetop-levelclass)tothetestsdefinedineachoftheinnerclasses.
Thefirstthingweneedtotakeintoaccountisthatonlynon-staticnestedclasses(thatisinnerclasses)[email protected],andthesetupandteardownforeachtest(thatis,@BeforeEachand@AfterEachmethods)areinheritedinthenestedtests.Nevertheless,innerclassescannotdefinethe@BeforeAlland@AfterAllmethods,duetothefactthatJavadoesnotallowstaticmembersininnerclasses.However,thisrestrictioncanbeavoidedusingtheannotation@TestInstance(Lifecycle.PER_CLASS)inthetestclass.AsdescribedinthesectionTestinstancelifecycleinthischapter,thisannotationforcetoinstanceatestinstanceperclass,insteadofatestinstancepermethod(defaultbehavior).Thisway,themethods@BeforeAlland@AfterAlldonotneedtobestaticandthereforeitcanbeusedinnestedtests.
Let’sseeasimpleexamplecomposedbyaJavaclasswithtwolevelsofinnerclasses,thatis,theclasscontainstwonestedinnerclassesannotatedwith@Nested.Aswecansee,therearetestsinthethreelevelsoftheclass.Noticethatthetopclassdefinedasetupmethod(@BeforeEach),andalsothefirstnestedclass(calledInnerClass1intheexample).Inthetop-levelclass,wedefineasingletest(calledtopTest),andineachnestedclasswefindanothertest(calledinnerTest1andinnerTest2,respectively):
packageio.github.bonigarcia;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Nested;
importorg.junit.jupiter.api.Test;
classNestTest{
@BeforeEach
voidsetup1(){
System.out.println("Setup1");
}
@Test
voidtopTest(){
System.out.println("Test1");
}
@Nested
classInnerClass1{
@BeforeEach
voidsetup2(){
System.out.println("Setup2");
}
@Test
voidinnerTest1(){
System.out.println("Test2");
}
@Nested
classInnerClass2{
@Test
voidinnerTest2(){
System.out.println("Test3");
}
}
}
}
Ifweexecutethisexample,wecantracetheexecutionofthenestedtestsbysimplylookingtotheconsoletraces.Notethatthetop@BeforeEachmethod(calledsetup1)isalwaysexecutedbeforeeachtest.Therefore,thetraceSetup1isalwayspresentintheconsolebeforetheactualtestexecution.Eachtestalsowritesalinetheconsole.Aswecansee,thefirsttestlogsTest1.Afterthat,thetestsdefinedintheinnerclassesareexecuted.ThefirstinnerclassexecutesthetestinnerTest1,butafterthat,thesetupmethodofthetop-levelclassandthefirstinnerclassareexecuted(loggingSetup1andSetup2,respectively).
Finally,thetestdefinedinthelastinnerclass(innerTest2)isexecuted,butasusual,thecascadeofsetupmethodsisexecutedbeforethetest:
Consoleoutputoftheexecutionofthenestedtestexample
Nestedtestscanbeusedinconjunctionwiththedisplayname(thatis,the
annotation@DisplayName)tohelptoproduceanicelyreadabletestoutput.Thefollowingexampledemonstrateshow.Thisclasscontainsthestructuretotesttheimplementationofastack,thatis,alast-in-first-out(LIFO)collection.Theclassisdesignedtofirsttestthestackwhenitisjustinstantiated(themethodisInstantiatedWithNew).Afterthat,thefirstinnerclass(WhenNew)issupposedtotestthestackasanemptycollection(methodsisEmpty,throwsExceptionWhenPoppedandthrowsExceptionWhenPeeked).Finally,thesecondinnerclassissupposedtotestwhenthestackisnotempty(methodsisNotEmpty,returnElementWhenPopped,andreturnElementWhenPeeked):
packageio.github.bonigarcia;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Nested;
importorg.junit.jupiter.api.Test;
@DisplayName("Astacktest")
classStackTest{
@Test
@DisplayName("isinstantiated")
voidisInstantiated(){
}
@Nested
@DisplayName("whenempty")
classWhenNew{
@Test
@DisplayName("isempty")
voidisEmpty(){
}
@Test
@DisplayName("throwsExceptionwhenpopped")
voidthrowsExceptionWhenPopped(){
}
@Test
@DisplayName("throwsExceptionwhenpeeked")
voidthrowsExceptionWhenPeeked(){
}
@Nested
@DisplayName("afterpushinganelement")
classAfterPushing{
@Test
@DisplayName("itisnolongerempty")
voidisNotEmpty(){
}
@Test
@DisplayName("returnstheelementwhenpopped")
voidreturnElementWhenPopped(){
}
@Test
@DisplayName("returnstheelementwhenpeeked")
voidreturnElementWhenPeeked(){
}
}
}
}
Theobjectiveofthistypeoftestistwofolded.Ontheonehand,theclassstructureprovidesanorderfortheexecutionofthetests.Ontheotherhand,theuseof@DisplayNameimprovesthereadabilityofthetestexecution.WecanseethatwhenthetestisexecutedinanIDE,concretelyinIntelliJIDEA.
Executionofnestedtestusing@DisplayNameonIntellijIDEA
RepeatedtestsJUnitJupiterprovidesfortheabilitytorepeatatestaspecifiednumberoftimessimplybyannotatingamethodwith@RepeatedTest,specifyingthetotalnumberofrepetitionsdesired.Eachrepeatedtestbehavesexactlyasaregular@Testmethod.Moreover,eachrepeatedtestpreservesthesamelifecyclecallbacks(@BeforeEach,@AfterEach,andsoon).
ThefollowingJavaclasscontainsatestthatisgoingtoberepeatedfivetimes:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.RepeatedTest;
classSimpleRepeatedTest{
@RepeatedTest(5)
voidtest(){
System.out.println("Repeatedtest");
}
}
Duetothefactthatthistestonlywritesaline(Repeatedtest)inthestandardoutput,whenexecutingthistestintheconsole,wewillseethattracefivetimes:
Executionofrepeatedtestintheconsole
Inadditiontospecifyingthenumberofrepetitions,acustomdisplaynamecanbeconfiguredforeachrepetitionviathenameattributeofthe@RepeatedTestannotation.Thedisplaynamecanbeapatterncomposedofacombinationofstatictextanddynamicplaceholders.Thefollowingarecurrentlysupported:
{displayName}:Thisisthenameofthe@RepeatedTestmethod.
{currentRepetition}:Thisisthecurrentrepetitioncount.{totalRepetitions}:Thisisthetotalnumberofrepetitions.
Thefollowingexampleshowsaclasswiththreerepeatedtestsinwhichthedisplaynameisconfiguredwiththepropertynameof@RepeatedTest:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.RepeatedTest;
importorg.junit.jupiter.api.TestInfo;
classTunningDisplayInRepeatedTest{
@RepeatedTest(value=2,name="{displayName}
{currentRepetition}/{totalRepetitions}")
@DisplayName("Repeat!")
voidcustomDisplayName(TestInfotestInfo){
System.out.println(testInfo.getDisplayName());
}
@RepeatedTest(value=2,name=RepeatedTest.LONG_DISPLAY_NAME)
@DisplayName("Testusinglongdisplayname")
voidcustomDisplayNameWithLongPattern(TestInfotestInfo){
System.out.println(testInfo.getDisplayName());
}
@RepeatedTest(value=2,name=RepeatedTest.SHORT_DISPLAY_NAME)
@DisplayName("Testusingshortdisplayname")
voidcustomDisplayNameWithShortPattern(TestInfotestInfo){
System.out.println(testInfo.getDisplayName());
}
}
Inthistest,thedisplaynamefortheserepeatedtestswillbeasfollows:
ForthetestcustomDisplayName,thedisplaynamewillfollowthelongdisplayformat:
Repeat1outof2.Repeat2outof2.
ForthetestcustomDisplayNameWithLongPattern,thedisplaynamewillfollowthelongdisplayformat:
Repeat!1/2.Repeat!2/2.
ForthetestcustomDisplayNameWithShortPattern,thedisplaynameinthistestwillfollowtheshortdisplayformat:
Testusinglongdisplayname::repetition1of2.Testusinglongdisplayname::repetition2of2.
MigrationfromJUnit4toJUnit5JUnit5doesnotsupportJUnit4features,suchasRulesandRunners,natively.Nevertheless,JUnit5providesagentlemigrationpathviatheJUnitVintagetestengine,whichallowsustoexecutelegacytestcases(includingJUnit4butalsoJUnit3)onthetopoftheJUnitPlatform.
ThefollowingtablecanbeusedtosummarizethemaindifferencesbetweenJUnit4and5:
Feature JUnit4 JUnit5
Annotationspackage
org.junit org.junit.jupiter.api
Declaringatest @Test @Test
Setupforalltests @BeforeClass @BeforeAll
Setuppertest @Before @BeforeEach
Teardownpertest @After @AfterEach
Teardownforalltests
@AfterClass @AfterAll
Taggingandfiltering
@Category @Tag
Disableatestmethodorclass
@Ignore @Disabled
Nestedtests NA @Nested
Repeatedtest Usingcustomrule
@Repeated
Dynamictests NA @TestFactory
Testtemplates NA @TestTemaplate
Runners @RunWithThisfeatureissupersededbytheextensionmodel(@ExtendWith)
Rules @Ruleand@ClassRule
Thisfeatureissupersededbytheextensionmodel(@ExtendWith)
RulesupportinJupiterAsdescribedbefore,JupiterdoesnotsupportJUnit4rulesnatively.Nevertheless,theJUnit5teamrealizedthatJUnit4rulesarewidelyadoptedinmanytestcodebasesnowadays.InordertoprovideaseamlessmigrationfromJUnit4toJUnit5,theJUnit5teamimplementedthejunit-jupiter-migrationsupportmodule.Ifthismoduleisgoingtobeusedinaproject,themoduledependencyshouldbeimported.ExamplesforMavenareshownhere:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-migrationsupport</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
TheGradledeclarationforthisdependencyislikethis:dependencies{
testCompile("org.junit.jupiter:junit-jupiter-
migrationsupport:${junitJupiterVersion}")
}
TherulesupportinJUnit5islimitedtothoserulessemanticallycompatiblewiththeJupiterextensionmodel,includingthefollowingrules:
junit.rules.ExternalResource(includingorg.junit.rules.TemporaryFolder).junit.rules.Verifier(includingorg.junit.rules.ErrorCollector).junit.rules.ExpectedException.
InordertoenabletheserulesinJupitertests,thetestclassshouldbeannotatedwiththeclass-levelannotation@EnableRuleMigrationSupport(locatedinthepackageorg.junit.jupiter.migrationsupport.rules).Letusseeseveralexamples.First,thefollowingtestcasedefinesandusesaTemporaryFolderJUnit4rulewithinaJupitertest:
packageio.github.bonigarcia;
importjava.io.IOException;
importorg.junit.Rule;
importorg.junit.jupiter.api.AfterEach;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport;
importorg.junit.rules.TemporaryFolder;
@EnableRuleMigrationSupport
classTemporaryFolderRuleTest{
@Rule
TemporaryFoldertemporaryFolder=newTemporaryFolder();
@BeforeEach
voidsetup()throwsIOException{
temporaryFolder.create();
}
@Test
voidtest(){
System.out.println("Temporaryfolder:"+
temporaryFolder.getRoot());
}
@AfterEach
voidteardown(){
temporaryFolder.delete();
}
}
Whenexecutingthistest,thepathofthetemporaryfolderwillbeloggedonthestandardoutput:
ExecutionofJupitertestusingaJUnit4TemporaryFolderrule
ThefollowingtestdemonstratestheuseoftheErrorCollectorruleinaJupitertest.Noticethatthecollectorruleallowstheexecutionofatesttocontinueafteroneormoreproblemsarefound:
packageio.github.bonigarcia;
importstaticorg.hamcrest.CoreMatchers.equalTo;
importorg.junit.Rule;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport;
importorg.junit.rules.ErrorCollector;
@EnableRuleMigrationSupport
classErrorCollectorRuleTest{
@Rule
publicErrorCollectorcollector=newErrorCollector();
@Test
voidtest(){
collector.checkThat("a",equalTo("b"));
collector.checkThat(1,equalTo(2));
collector.checkThat("c",equalTo("c"));
}
}
Theseproblemsarereportedtogetherattheendofthetest:
ExecutionofJupitertestusingaJUnit4ErrorCollectorrule
Finally,theExpectedExceptionruleallowsustoconfigureatesttoanticipateagivenexceptiontobethrownwithinthetestlogic:
packageio.github.bonigarcia;
importorg.junit.Rule;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport;
importorg.junit.rules.ExpectedException;
@EnableRuleMigrationSupport
classExpectedExceptionRuleTest{
@Rule
ExpectedExceptionthrown=ExpectedException.none();
@Test
voidthrowsNothing(){
}
@Test
voidthrowsNullPointerException(){
thrown.expect(NullPointerException.class);
thrownewNullPointerException();
}
}
Inthisexample,evenwhenthesecondtestraisesaNullPointerException,thetestwillbemarkedashavingsucceededsincethatexceptionwasexpected.
SummaryInthischapter,weintroducedthebasicsofthebrand-newprogrammingmodeloftheJUnit5framework,knownasJupiter.ThisprogrammingmodelprovidesarichAPIthatcanbeusedbypractitionerstocreatetestcases.ThemostbasicelementofJupiteristheannotation@Test,whichidentifiesthemethodsinJavaclassestreatedastests(thatislogicwhichexercisesandverifiesaSUT).Moreover,therearedifferentannotationsthatcanbeusedtocontrolthetestlifecycle,namely,@BeforeAll,@BeforeEach,@AfterEach,[email protected]@Disabled(toskiptests),@DisplayName(toprovideatestname),@Tag(tolabelandfiltertests).
Jupiterprovidesarichsetofassertions,whicharestaticmethodsintheclassAssertionsusedtoverifyiftheoutcomeobtainedfromtheSUTcorrespondswithsomeexpectedvalue.Wecanimposeconditionsforthetestexecutioninseveralways.Ontheonehand,wecanuseAssumptionstoonlyruntests(orapartofthose)ifcertainconditionsareasexpected.
WehavelearnedhownestedtestscanbecreatedsimpleannotatinginnerJavaclasseswith@Nested.Thiscanbeusedtocreatetestexecutionsfollowinganordergiventhenestedclassesrelationship.WehavealsostudiedhoweasyistocreatedrepeatedtestusingtheJUnit5programmingmodel.Theannotation@RepeatedTestisusedtothataim,providingtheabilitytorepeatatestaspecifiednumberoftimes.Finally,wehaveseenhowJupiterprovidessupportforseverallegacyJUnit4testrules,includingExternalResource,Verifier,andExpectedException.
Inthechapter4,SimplifyingTestingWithAdvancedJUnitFeatures,wecontinuediscoveringtheJUnitprogrammingmodel.Concretely,wereviewtheadvancefeaturesofJUnit5,namely,dependencyinjection,dynamictests,testinterfaces,testtemplates,parameterizedtests,compatibilityofJUnit5andJava9.Finally,wereviewsomeoftheplannedfeaturesinthebacklogforJUnit5.1,notimplementedyetatthetimeofthiswriting.
SimplifyingTestingWithAdvancedJUnitFeatures
Simplicityistheultimatesophistication.-LeonardodaVinci
Sofar,wehavediscoveredthebasicsofJupiter,thebrand-newprogrammingmodelprovidedbytheJUnit5framework.Moreover,Jupiterprovidesarichrangeofpossibilitieswhichallowstocreatedifferenttypesoftestcases.Inthischapter,wereviewtheseadvancedfeatures.Tothataim,thischapterisstructuredasfollows:
Dependencyinjection:Thissectionfirsttakesalookatdependencyinjectionforconstructorsandmethodsintestclasses.Then,itreviewsthethreeparameterresolversprovidedoutoftheboxinJupiter.TheseresolversallowtoinjectobjectsofTestInfo,RepetitionInfo,andTestReporterinsidetests.Dynamictests:ThissectiondiscusseshowdynamictestsareimplementedinJUnit5,usingthemethodsdynamicTestandstream.Testinterfaces:ThesectionreviewstheJupiterannotationsthatcanbedeclaredontestinterfacesanddefaultmethods.Testtemplates:JUnit5introducestheconceptofatemplatefortestscases.Thesetemplateswillbeinvokedmultipletimes,dependingontheinvocationcontexts.Parameterizedtests:InthesamewayasJUnit4,JUnit5providescapabilitiestocreatetestsdrivenbydifferentinputdata,thatis,aparametrizedtest.WewilldiscoverthatthesupportforthiskindoftesthasbeensignificantlyenhancedintheJupiterprogrammingmodel.Java9:OnSeptember21,2017,Java9released.Aswewilldiscover,JUnit5hasbeenimplementedtobecompatiblewithJava9,withspecialemphasisonthemodularityfeatureofJava9.
DependencyinjectionInformerJUnitversions,testconstructorsandmethodswerenotallowedtohaveparameters.OneofthemajorchangesinJUnit5isthatbothtestconstructorsandmethodsarenowallowedtoincludeparameters.Thisfeatureenablesthedependencyinjectionforconstructorsandmethods.
AsintroducedinChapter2,What’sNewInJUnit5ofthisbook,theextensionmodelhasanextensionthatprovidesdependencyinjectionsforJupitertests,calledParameterResolver,whichdefinesanAPIfortestextensionsthatwishtodynamicallyresolveparametersatruntime.
Ifatestconstructororamethodannotatedwith@Test,@TestFactory,@BeforeEach,@AfterEach,@BeforeAll,or@AfterAllacceptsaparameter,thatparameterisresolvedatruntimebyaresolver(objectwithparentclassParameterResolver).Therearethreebuilt-inresolversregisteredautomaticallyinJUnit5:TestInfoParameterResolver,andRepetitionInfoParameterResolver,TestReporterParameterResolver.Werevieweachoneoftheseresolversinthissection.
TestInfoParameterResolverGivenatestclass,ifamethodparameterisoftypeTestInfo,theJUnit5resolverTestInfoParameterResolversuppliesaninstanceofTestInfocorrespondingtothecurrenttestasthevalueforthedeclaredparameter.TheTestInfoobjectisusedtoretrieveinformationaboutthecurrenttest,suchasthetestdisplayname,thetestclass,thetestmethod,orassociatedtags.
TestInfoactsasadrop-inreplacementfortheTestNamerulefromJUnit4.
TheclassTestInfoisplacedinthepackageorg.junit.jupiter.apiandoffersthefollowingAPI:
StringgetDisplayName():Thisreturnsthedisplaynameofthetestorcontainer.Set<String>getTags():Thisgetsthesetofalltagsforthecurrenttestorcontainer.Optional<Class<?>>getTestClass():Thisgetstheclassassociatedwiththecurrenttestorcontainer,ifavailable.Optional<Method>getTestMethod():Thisgetsthemethodassociatedwiththecurrenttest,ifavailable.
TestInfoAPI
Let’sseeanexample.Noticethatinthefollowingclass,boththemethodsannotatedwith@BeforeEachand@TestacceptsaparameterofTestInfo.ThisparameterisinjectedbyTestInfoParameterResolver:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.TestInfo;
classTestInfoTest{
@BeforeEach
voidinit(TestInfotestInfo){
StringdisplayName=testInfo.getDisplayName();
System.out.printf("@BeforeEach%s%n",displayName);
}
@Test
@DisplayName("Mytest")
@Tag("my-tag")
voidtestOne(TestInfotestInfo){
System.out.println(testInfo.getDisplayName());
System.out.println(testInfo.getTags());
System.out.println(testInfo.getTestClass());
System.out.println(testInfo.getTestMethod());
}
@Test
voidtestTwo(){
}
}
Therefore,inthebodyofeachmethod,weareabletousetheTestInfoAPItogetthetestinformationatruntime,asthefollowingscreenshotdemonstrates:
ConsoleoutputofdependencyinjectionofTestInfoobjects
RepetitionInfoParameterResolverThesecondresolverprovidedoutoftheboxinJUnit5iscalledRepetitionInfoParameterResolver.Givenatestclass,ifamethodparameterina@RepeatedTest,@BeforeEach,or@AfterEachmethodisoftypeRepetitionInfo,theRepetitionInfoParameterResolverwillsupplyaninstanceofRepetitionInfo.
RepetitionInfocanbeusedtoretrieveinformationaboutthecurrentrepetitionandthetotalnumberofrepetitionsforthecorresponding@RepeatedTest.TheAPIofRepetitionInfoofferstwomethods,asshowninthescreenshotafterthelist:
intgetCurrentRepetition():Getsthecurrentrepetitionofthecorresponding@RepeatedTestmethodintgetTotalRepetitions():Getsthetotalnumberofrepetitionsofthecorresponding@RepeatedTestmethod
RepetitionInfoAPI
TheclassherecontainsasimpleexamplefortheuseofRepetitionInfo:packageio.github.bonigarcia;
importorg.junit.jupiter.api.RepeatedTest;
importorg.junit.jupiter.api.RepetitionInfo;
classRepetitionInfoTest{
@RepeatedTest(2)
voidtest(RepetitionInforepetitionInfo){
System.out.println("**Test"+
repetitionInfo.getCurrentRepetition()
+"/"+repetitionInfo.getTotalRepetitions());
}
}
Ascanbeseeninthetestoutput,weareabletoreadtheinformationabouttherepeatedtestatruntime:
TestReporterParameterResolverThelastbuilt-inresolverinJUnit5isTestReporterParameterResolver.Again,givenatestclass,ifamethodparameterisoftypeTestReporter,theTestReporterParameterResolversuppliesaninstanceofTestReporter.
TestReporterisusedtopublishadditionaldataaboutthetestexecution.ThedatacanbeconsumedthroughthemethodreportingEntryPublished,andthen,itcanberequestedbyIDEsorincludedintestreports.EachTestReporterobjectstoresinformationasamap,thatis,akey-valuecollection:
TestReporterAPI
ThistestprovidesasimpleexampleofTestReporter.Aswecansee,weusetheinjectedtestReporterobjecttoaddcustominformationusingkey-valuepairs:
packageio.github.bonigarcia;
importjava.util.HashMap;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.TestReporter;
classTestReporterTest{
@Test
voidreportSingleValue(TestReportertestReporter){
testReporter.publishEntry("key","value");
}
@Test
voidreportSeveralValues(TestReportertestReporter){
HashMap<String,String>values=newHashMap<>();
values.put("name","john");
values.put("surname","doe");
testReporter.publishEntry(values);
}
}
DynamictestsAsweknow,inJUnit3,weidentifiedtestsbyparsingmethodnamesandcheckingwhethertheystartedwiththewordtest.Then,inJUnit4,weidentifiedtestsbycollectingmethodsannotatedwith@Test.Bothofthesetechniquessharethesameapproach:testsaredefinedatcompiletime.Thisconceptiswhatwecallstatictesting.
Statictestsareconsideredalimitedapproach,especiallyforthecommonscenarioinwhichthesametestissupposedtobeexecutedforavarietyofinputdata.InJUnit4,thislimitationwasaddressedinseveralways.Averysimplesolutiontotheproblemistolooptheinputtestdataandexercisingthesametestlogic(JUnit4examplehere).Followingthisapproach,onetestisexecuteduntilthefirstassertionfails:
packageio.github.bonigarcia;
importorg.junit.Test;
publicclassMyTest{
@Test
publicvoidtest(){
String[]input={"A","B","C"};
for(Strings:input){
exercise(s);
}
}
privatevoidexercise(Strings){
System.out.println(s);
}
}
AmoreelaboratesolutionistousetheJUnit4supportforparameterizedtests,usingtheparameterizedrunner.Thisapproachdoesnotcreatetestsatruntimeeither,itsimplyrepeatsthesametestseveraltimesdependingontheparameters:
packageio.github.bonigarcia;
importjava.util.Arrays;
importjava.util.Collection;
importorg.junit.Test;
importorg.junit.runner.RunWith;
importorg.junit.runners.Parameterized;
importorg.junit.runners.Parameterized.Parameter;
importorg.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
publicclassParameterizedTest{
@Parameter(0)
publicIntegerinput1;
@Parameter(1)
publicStringinput2;
@Parameters(name="Mytest#{index}--inputdata:{0}and{1}")
publicstaticCollection<Object[]>data(){
returnArrays
.asList(newObject[][]{{1,"hello"},{2,"goodbye"}});
}
@Test
publicvoidtest(){
System.out.println(input1+""+input2);
}
}
WecanseetheexecutionoftheprecedingexampleintheEclipseIDE:
ExecutionofJUnit4’sparameterizedtestinEclipse
Ontheotherhand,JUnit5allowstogeneratetestatruntimebyafactorymethodthatisannotatedwith@TestFactory.Incontrastto@Test,[email protected]@TestFactorymethodmustreturnaStream,Collection,Iterable,orIteratorofDynamicTestinstances.TheseDynamicTestinstancesareexecutedlazily,enablingdynamicgenerationoftestcases.
Inordertocreateadynamictest,wecanusethestaticmethoddynamicTestoftheclassDynamicTestlocatedintheorg.junit.jupiter.apipackage.Ifweinspectthesourcecodeofthisclass,wecanseethataDynamicTestiscomposedofadisplaynameinformoftheStringandoneexecutableobject,whichcanbeprovidedaslambdaexpressionsorasmethodreferences.
Let’sseeseveralexamplesofdynamictests.Inthefollowingexample,thefirstdynamictestwillfail,duetothefactwearenotreturningtheexpectedcollectionofDynamicTests.ThenextthreemethodsareverysimpleexamplesthatdemonstratethegenerationofCollection,Iterable,andIteratorofDynamicTestinstances:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importstaticorg.junit.jupiter.api.DynamicTest.dynamicTest;
importjava.util.Arrays;
importjava.util.Collection;
importjava.util.Iterator;
importjava.util.List;
importorg.junit.jupiter.api.DynamicTest;
importorg.junit.jupiter.api.TestFactory;
classCollectionTest{
//Warning:thistestwillraiseanexception
@TestFactory
List<String>dynamicTestsWithInvalidReturnType(){
returnArrays.asList("Hello");
}
@TestFactory
Collection<DynamicTest>dynamicTestsFromCollection(){
returnArrays.asList(
dynamicTest("1stdynamictest",()->
assertTrue(true)),
dynamicTest("2nddynamictest",()->assertEquals(4,2
*2)));
}
@TestFactory
Iterable<DynamicTest>dynamicTestsFromIterable(){
returnArrays.asList(
dynamicTest("3rddynamictest",()->
assertTrue(true)),
dynamicTest("4thdynamictest",()->assertEquals(4,2
*2)));
}
@TestFactory
Iterator<DynamicTest>dynamicTestsFromIterator(){
returnArrays.asList(
dynamicTest("5thdynamictest",()->
assertTrue(true)),
dynamicTest("6thdynamictest",()->assertEquals(4,2
*2))).iterator();
}
}
Theseexamplesdonotreallyexhibitdynamicbehavior,butmerelydemonstratethesupportedreturntypes.NotethatthefirsttestisgoingtofailduetoJUnitException:
Consoleoutputofthefirstexamplefordynamictestexecution
Thefollowingexampledemonstrateshoweasyitistogeneratedynamictestsforagivensetofinputdata:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.DynamicTest.dynamicTest;
importjava.util.stream.Stream;
importorg.junit.jupiter.api.DynamicTest;
importorg.junit.jupiter.api.TestFactory;
classDynamicExampleTest{
@TestFactory
Stream<DynamicTest>dynamicTestsFromStream(){
Stream<String>inputStream=Stream.of("A","B","C");
returninputStream.map(
input->dynamicTest("Displaynameforinput"+input,
()->{
System.out.println("Testing"+input);
}));
}
}
Noticethat,intheend,threetestswereexecuted,andthesethreetestswerecreatedatruntimebyJUnit5:
Consoleoutputofthesecondexamplefordynamictestexecution
ThereisanotherpossibilitytocreatedynamictestsinJUnit5,usingthestaticmethodstreamoftheclassDynamicTest.Thismethodneedsaninputgenerator,afunctionthatgeneratesadisplaynamebasedonaninputvalue,andatestexecutor.
Let’sseeanotherexample.Wecreateatestfactory,providingtheinputdataasanIterator,adisplaynamefunctionusingalambdaexpression,andfinally,atestexecutorimplementedwithanotherlambdaexpression.Inthisexample,thetestexecutorbasicallyassertswhetherornottheinputintegerisevenorodd:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importstaticorg.junit.jupiter.api.DynamicTest.stream;
importjava.util.Arrays;
importjava.util.Iterator;
importjava.util.function.Function;
importjava.util.stream.Stream;
importorg.junit.jupiter.api.DynamicTest;
importorg.junit.jupiter.api.TestFactory;
importorg.junit.jupiter.api.function.ThrowingConsumer;
classStreamExampleTest{
@TestFactory
Stream<DynamicTest>streamTest(){
//Inputdata
Integerarray[]={1,2,3};
Iterator<Integer>inputGenerator=Arrays.asList(array).iterator();
//Displaynames
Function<Integer,String>displayNameGenerator=(
input)->"Datainput:"+input;
//Testexecutor
ThrowingConsumer<Integer>testExecutor=(input)->{
System.out.println(input);
assertTrue(input%2==0);
};
//Returnsastreamofdynamictests
returnstream(inputGenerator,displayNameGenerator,
testExecutor);
}
}
Thetestwillfailforoddinputs.Aswecansee,twooutofthreetestswillfail:
TestinterfacesInJUnit5,therearedifferentrulesrelativetotheuseofannotationsinJavainterfaces.Firstofall,weneedtobeawarethat@Test,@TestFactory,@BeforeEach,and@AfterEachcanbedeclaredoninterfacedefaultmethods.
DefaultmethodsisafeatureofJavaintroducedinversion8.Thesemethods(declaredusingthereservekeyworddefault)allowstodefineadefaultimplementationforagivenmethodwithinaJavainterface.Thiscapabilitycanbeusefulforbackwardcompatibilitywithexistinginterfaces.
ThesecondruleregardingJUnit5andinterfacesisthat@BeforeAlland@AfterAllcanbedeclaredonstaticmethodsinatestinterface.Moreover,ifthetestclass,whichimplementsagiveninterface,isannotatedwith@TestInstance(Lifecycle.PER_CLASS),themethods@BeforeAlland@AfterAlldeclaredontheinterfacedonotneedtobestatic,butdefaultmethods.
ThethirdandfinalruleconcerninginterfacesinJUnit5is@ExtendWithand@Tagcanbedeclaredontestinterfacestoconfigureextensionsandtags.
Let’sseesomesimpleexamples.Inthefollowingclass,wearecreatinganinterface,notaclass.Inthisinterface,weusetheannotations@BeforeAll,@AfterAll,@BeforeEach,[email protected],wedefine@BeforeAll,@AfterAllasstaticmethods.Ontheotherhand,wearedefining@BeforeEachand@AfterEachasJava8defaultmethods:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.AfterAll;
importorg.junit.jupiter.api.AfterEach;
importorg.junit.jupiter.api.BeforeAll;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.TestInfo;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
publicinterfaceTestLifecycleLogger{
staticfinalLoggerlog=LoggerFactory
.getLogger(TestLifecycleLogger.class.getName());
@BeforeAll
staticvoidbeforeAllTests(){
log.info("beforeAllTests");
}
@AfterAll
staticvoidafterAllTests(){
log.info("afterAllTests");
}
@BeforeEach
defaultvoidbeforeEachTest(TestInfotestInfo){
log.info("Abouttoexecute{}",testInfo.getDisplayName());
}
@AfterEach
defaultvoidafterEachTest(TestInfotestInfo){
log.info("Finishedexecuting{}",testInfo.getDisplayName());
}
}
WeareusingthelibrarySimpleLoggingFacadeforJava(SLF4J)inthisexample.TakealookatthecodeonGitHub(https://github.com/bonigarcia/mastering-junit5)fordetailsonthedeclarationofdependencies.
Inthisexample,weareusingtheannotationTestFactorytodefineadefaultmethodinaJavainterface:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importstaticorg.junit.jupiter.api.DynamicTest.dynamicTest;
importjava.util.Arrays;
importjava.util.Collection;
importorg.junit.jupiter.api.DynamicTest;
importorg.junit.jupiter.api.TestFactory;
interfaceTestInterfaceDynamicTestsDemo{
@TestFactory
defaultCollection<DynamicTest>dynamicTestsFromCollection(){
returnArrays.asList(
dynamicTest("1stdynamictestintestinterface",
()->assertTrue(true)),
dynamicTest("2nddynamictestintestinterface",
()->assertTrue(true)));
}
}
Finally,weusetheannotation@Tagand@ExtendWithinanotherinterface:packageio.github.bonigarcia;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.extension.ExtendWith;
@Tag("timed")
@ExtendWith(TimingExtension.class)
publicinterfaceTimeExecutionLogger{
}
Allinall,wecanusetheseinterfacesinourJupitertests:packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importorg.junit.jupiter.api.Test;
classTestInterfaceTestimplementsTestLifecycleLogger,
TimeExecutionLogger,
TestInterfaceDynamicTestsDemo{
@Test
voidisEqualValue(){
assertEquals(1,1);
}
}
Inthistest,thefactofimplementingallthepreviouslydefinedinterfaceswillprovidetheloggingcapabilitiesimplementedinthedefaultmethods:
Consoleoutputoftestimplementingseveralinterfaces
TesttemplatesA@TestTemplatemethodisnotaregulartestcasebutatemplatefortestcases.Methodannotatedlikethiswillbeinvokedmultipletimes,dependingontheinvocationcontextreturnedbytheregisteredproviders.Thus,testtemplatesareusedtogetherwitharegisteredTestTemplateInvocationContextProviderextension.
Letseeasimpleexampleofatesttemplate.Inthefollowingsnippet,wecanseeamethodannotatedwith@TestTemplate,andalsodeclaringanextensionofthetypeMyTestTemplateInvocationContextProvider:
packageio.github.bonigarcia;
importorg.junit.jupiter.api.TestTemplate;
importorg.junit.jupiter.api.extension.ExtendWith;
classTemplateTest{
@TestTemplate
@ExtendWith(MyTestTemplateInvocationContextProvider.class)
voidtestTemplate(Stringparameter){
System.out.println(parameter);
}
}
TherequiredprovidedimplementstheJupiterinterfaceTestTemplateInvocationContextProvider.Inspectingthecodeofthisclass,wecanseehowtwoStringparametersareprovidedtothetesttemplate(inthiscase,thevaluefortheseparametersareparameter-1andparameter-2):
packageio.github.bonigarcia;
importjava.util.Collections;
importjava.util.List;
importjava.util.stream.Stream;
importorg.junit.jupiter.api.extension.Extension;
importorg.junit.jupiter.api.extension.ExtensionContext;
importorg.junit.jupiter.api.extension.ParameterContext;
importorg.junit.jupiter.api.extension.ParameterResolver;
importorg.junit.jupiter.api.extension.TestTemplateInvocationContext;
importorg.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
publicclassMyTestTemplateInvocationContextProvider
implementsTestTemplateInvocationContextProvider{
@Override
publicbooleansupportsTestTemplate(ExtensionContextcontext){
returntrue;
}
@Override
publicStream<TestTemplateInvocationContext>
provideTestTemplateInvocationContexts(
ExtensionContextcontext){
returnStream.of(invocationContext("parameter-1"),
invocationContext("parameter-2"));
}
privateTestTemplateInvocationContextinvocationContext(Stringparameter){
returnnewTestTemplateInvocationContext(){
@Override
publicStringgetDisplayName(intinvocationIndex){
returnparameter;
}
@Override
publicList<Extension>getAdditionalExtensions(){
returnCollections.singletonList(newParameterResolver(){
@Override
publicbooleansupportsParameter(
ParameterContextparameterContext,
ExtensionContextextensionContext){
returnparameterContext.getParameter().getType()
.equals(String.class);
}
@Override
publicObjectresolveParameter(
ParameterContextparameterContext,
ExtensionContextextensionContext){
returnparameter;
}
});
}
};
}
}
Whenthetestisexecuted,eachinvocationofthetesttemplatebehaveslikearegular@Test.Inthisexample,thetestisonlywritingtheparameterinthestandardoutput.
Consoleoutputoftesttemplateexample
ParameterizedtestsParameterizedtestsareaspecialkindsoftestsinwhichthedatainputisinjectedinthetestinordertoreusethesametestlogic.ThisconceptwasalreadyaddressedinJUnit4,asexplainedinChapter1,RetrospectiveOnSoftwareQualityAndJavaTesting.Aswewouldexpect,parameterizedtestsarealsoimplementedinJUnit5.
Firstofall,inordertoimplementaparameterizedtestinJupiter,weneedtoaddthejunit-jupiter-paramstoourproject.WhenusingMaven,thatmeansaddingthefollowingdependency:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
Asusual,asageneralrule,itisrecommendedtousethelatestversionoftheartifacts.Tofindoutthat,wecancheckouttheMavencentralrepository(http://search.maven.org/).
WhenusingGradle,thejunit-jupiter-paramsdependencycanbedeclaredasfollows:
dependencies{
testCompile("org.junit.jupiter:junit-jupiter-
params:${junitJupiterVersion}")
}
Then,weneedtousetheannotation@ParameterizedTest(locatedinthepackageorg.junit.jupiter.params)todeclareamethodwithinaJavaclassasaparameterizedtest.Thistypeoftestbehavesexactlythesameasaregular@Test,meaningthatallthelifecyclecallbacks(@BeforeEach,@AfterEach,andsoon)andextensionscontinueworkinginthesameway.
Nevertheless,theuseof@ParameterizedTestisnotenoughtoimplementaparameterizedtest.Togetherwith@ParameterizedTest,weneedtospecifyatleastoneargumentprovider.Aswewilldiscoverinthissection,JUnit5implementsdifferentannotationstoprovidedatainput(thatis,parametersfortests)fromdifferentsources.Theseargumentproviders(implementedasannotationsinJUnit5)aresummarizedinthefollowingtable(eachoftheseannotationsarelocatedinthepackageorg.junit.jupiter.params.provider):
Arguments
providerannotation
Description
@ValueSource UsedtospecifyanarrayofliteralvaluesofString,int,long,ordouble
@EnumSource
Argumentsourceforconstantsofaspecifiedenumeration(java.lang.Enum)
@MethodSourceProvidesaccesstovaluesreturnedbystaticmethodsoftheclassinwhichthisannotationisdeclared
@CsvSourceArgumentsourcewhichreadscomma-separatedvalues(CSV)fromitsattribute
@CsvFileSourceArgumentsourcewhichisusedtoloadCSVfilesfromoneormoreclasspathresources
@ArgumentsSource
Usedtospecifyacustomargumentprovider(thatis,aJavaclassthatimplementstheinterface)org.junit.jupiter.params.provider.ArgumentsProvider)
@ValueSourceTheannotation@ValueSourceisusedinconjunctionwith@ParameterizedTesttospecifyaparameterizedtestinwhichtheargumentsourceisanarrayofliteralvaluesofString,int,long,ordouble.Thesevaluesarespecifiedinsidetheannotation,usingtheelementsstrings,ints,longs,ordoubles.Considerthefollowingexample:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.ValueSource;
classValueSourceStringsParameterizedTest{
@ParameterizedTest
@ValueSource(strings={"Hello","World"})
voidtestWithStrings(Stringargument){
System.out.println("Parameterizedtestwith(String)parameter:"
+argument);
assertNotNull(argument);
}
}
Themethodofthisclass(testWithStrings)definesaparameterizedtestinwhichanarrayofStringisspecified.DuetothefactthattwoStringargumentsarespecifiedintheannotation@ValueSource(inthisexample"Hello"and"World"),thetestlogicwillbeexercisedtwice,oncepervalue.Thisdataisinjectedinthetestmethodusingtheargumentofthemethod,inthiscasethroughtheStringvariablenamedargument.Allinall,whenexecutingthistestclass,theoutputwillbeasfollows:
Executionofaparameterizedtestusing@ValueSourceandStringargumentprovider
Wecanalsouseintegerprimitivetypes(int,long,anddouble)withinthe@ValueSourceannotation.Thefollowingexampledemonstrateshow.Themethodsofthisexampleclass(namedtestWithInts,testWithLongs,andtestWithDoubles)usetheannotation@ValueSourcetodefinetheargumentsintheformofintegervalues,usingtheprimitivetypesint,long,anddouble,
respectively.Tothataim,theelementsints,longs,anddoublesof@ValueSourceneedtobespecified:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.ValueSource;
classValueSourcePrimitiveTypesParameterizedTest{
@ParameterizedTest
@ValueSource(ints={0,1})
voidtestWithInts(intargument){
System.out.println("Parameterizedtestwith(int)argument:"+
argument);
assertNotNull(argument);
}
@ParameterizedTest
@ValueSource(longs={2L,3L})
voidtestWithLongs(longargument){
System.out.println(
"Parameterizedtestwith(long)
argument:"+argument);
assertNotNull(argument);
}
@ParameterizedTest
@ValueSource(doubles={4d,5d})
voidtestWithDoubles(doubleargument){
System.out.println("Parameterizedtestwith(double)
argument:"+argument);
assertNotNull(argument);
}
}
Ascanbeseeninthepicturehere,eachtestisexecutedtwice,sinceineach@ValueSourceannotationwespecifytwodifferentinputparameters(typeint,long,anddouble,respectively).
Executionofaparameterizedtestusing@ValueSourceandprimitivetypes
@EnumSourceTheannotation@EnumSourceallowstospecifyaparameterizedtestinwhichtheargumentsourceisaJavaenumerationclass.Bydefault,eachvalueoftheenumerationwillbeusedtofeedtheparameterizedtest,oneatatime.
Forexample,inthefollowingtestclass,themethodtestWithEnumisannotatedwith@[email protected],thevalueofthisannotationisTimeUnit.class,whichisastandardJavaannotation(packagejava.util.concurrent)usedtorepresenttimeduration.ThepossiblevaluesdefinedinthisenumerationareNANOSECONDS,MICROSECONDS,MILLISECONDS,SECONDS,MINUTES,HOURS,andDAYS:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importjava.util.concurrent.TimeUnit;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.EnumSource;
classEnumSourceParameterizedTest{
@ParameterizedTest
@EnumSource(TimeUnit.class)
voidtestWithEnum(TimeUnitargument){
System.out.println("Parameterizedtestwith(TimeUnit)
argument:"+argument);
assertNotNull(argument);
}
}
Therefore,theexecutionofthistestwillbecarriedoutseventimes,thatis,oneperTimeUnitenumerationvalue.Wecancheckthisinthetraceoftheoutputconsolewhenexecutingthetest:
Moreover,the@EnumSourceannotationallowstofilterthemembersoftheenumerationinseveralways.Toimplementthisselection,thefollowingelementscanbespecifiedwithina@EnumSourceannotation:
mode:Constantvaluewhichdeterminesthetypeoffiltering.Thisisdefinedasanenumerationintheinnerclassorg.junit.jupiter.params.provider.EnumSource.Mode,andthepossiblevaluesare:
INCLUDE:Usedtoselectthosevalueswhosenamesaresuppliedviathenameselement.Thisisthedefaultoption.EXCLUDE:Usedtoselectallvaluesexceptthosesuppliedwiththenameselement.MATCH_ALL:Usedtoselectthosevalueswhosenamesmatchthepatternsinnameselement.MATCH_ANY:Usedtoselectthosevalueswhosenamesmatchanypatterninthenameselement.
names:Thearrayofstringwhichallowstoselectagroupofenumconstants.Thecriteriaforinclusion/exclusionisdirectlylinkedtothevalueofmode.Inaddition,thiselementalsoallowstodefineregularexpressionstoselectthenamesofenumconstantstobematched.
Considerthefollowingexample.Inthisclass,therearethreeparameterizedtests.Firstone,namedtestWithFilteredEnum,usestheclassTimeUnittofeedthe@EnumSourceargumentprovider.Moreover,theenumconstantsetisfilteredusingtheelementnames.Aswecansee,onlytheconstant"DAYS"and"HOURS"willbeusedtofeedthistest(takeintoaccountthatthedefaultmodeisINCLUDE):
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importstaticorg.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;
importstaticorg.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL;
importjava.util.concurrent.TimeUnit;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.EnumSource;
classEnumSourceFilteringParameterizedTest{
@ParameterizedTest
@EnumSource(value=TimeUnit.class,names={"DAYS","HOURS"})
voidtestWithFilteredEnum(TimeUnitargument){
System.out.println("Parameterizedtestwithsome(TimeUnit)
argument:"+argument);
assertNotNull(argument);
}
@ParameterizedTest
@EnumSource(value=TimeUnit.class,mode=EXCLUDE,names={
"DAYS","HOURS"})
voidtestWithExcludeEnum(TimeUnitargument){
System.out.println("Parameterizedtestwithexcluded(TimeUnit)
argument:"+argument);
assertNotNull(argument);
}
@ParameterizedTest
@EnumSource(value=TimeUnit.class,mode=MATCH_ALL,names=
"^(M|N).+SECONDS$")
voidtestWithRegexEnum(TimeUnitargument){
System.out.println("Parameterizedtestwithregexfiltered
(TimeUnit)argument:"+argument);
assertNotNull(argument);
}
}
Thus,whenexecutingthisclassintheconsole,theoutputweobtainisthefollowing.Regardingthefirsttest,wecanseethatonlytracesfor"DAYS"and"HOURS"arepresent:
Executionofparameterizedtestusing@EnumSourceusingfilteringcapabilities
Considernowthesecondtestmethod,namedtestWithExcludeEnum.Thistestisexactlythesameasbeforewithadifference:themodehereisEXCLUSION(insteadofINCLUSION,chosenbydefaultintheprevioustest).Allinall,intheexecution(seescreenshotbefore)whencanseethatthistestisexecutedfivetimes,peroneoftheenumconstantdifferenttoDAYSandHOURS.Tocheckthat,trackthetraceswiththesentence"Parameterizedtestwithexcluded(TimeUnit)argument".
Thethirdandlastmethodofthisclass(calledtestWithRegexEnum)definesaninclusionmode,MATCH_ALL,usingaregularexpressiontofiltertheenumeration(inthiscase,itisalsoTimeUnit).Theconcreteregularexpressionusedinthisexampleis^(M|N).+SECONDS$,whichmeansthatonlywillbeincludedinthoseenumconstantsstartingwithMorNandendingwithSECONDS.Ascanbecheckedintheexecutionscreenshot,therearethreeTimeUnitconstantsmatchingtheseconditions:NANOSECONDS,MICROSECONDS,andMILISECONDS.
@MethodSourceTheannotation@MethodSourceallowstodefinethenameofthestaticmethodinwhichtheargumentsforthetestareprovidedasaJava8Stream.Forinstance,inthefollowingexample,wecanseeaparameterizedtestinwhichtheargumentproviderisastaticmethodcalledstringProvider.Inthisexample,thismethodreturnsaStreamofString‘sandthereforetheargumentofthetestmethod(callledtestWithStringProvider)acceptsoneStringargument:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importjava.util.stream.Stream;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.MethodSource;
classMethodSourceStringsParameterizedTest{
staticStream<String>stringProvider(){
returnStream.of("hello","world");
}
@ParameterizedTest
@MethodSource("stringProvider")
voidtestWithStringProvider(Stringargument){
System.out.println("Parameterizedtestwith(String)argument:"
+argument);
assertNotNull(argument);
}
}
Whenrunningtheexample,wecanseehowthetestisexecutetwice,onceperStringcontainedintheStream.
Executionofaparameterizedtestusing@MethodSourceandStringargumentprovider
ThetypeoftheobjectscontainedintheStreamisnotrequiredtobeString.Infact,thistypecanbeanything.Let’sconsideranotherexample,inwhich@MethodSourceislinkedtoastaticmethod,whichreturnsasStreamofcustomobjects.Inthisexample,thistypeisnamedPerson,andhereitisimplemented
asaninnerclasswithtwoproperties(nameandsurname).packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importjava.util.stream.Stream;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.MethodSource;
classMethodSourceObjectsParameterizedTest{
staticStream<Person>personProvider(){
Personjohn=newPerson("John","Doe");
Personjane=newPerson("Jane","Roe");
returnStream.of(john,jane);
}
@ParameterizedTest
@MethodSource("personProvider")
voidtestWithPersonProvider(Personargument){
System.out.println("Parameterizedtestwith(Person)argument:"+
argument);
assertNotNull(argument);
}
staticclassPerson{
Stringname;
Stringsurname;
publicPerson(Stringname,Stringsurname){
this.name=name;
this.surname=surname;
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
publicStringgetSurname(){
returnsurname;
}
publicvoidsetSurname(Stringsurname){
this.surname=surname;
}
@Override
publicStringtoString(){
return"Person[name="+name+",surname="+surname+"]";
}
}
}
Asthefollowingscreenshotshows,whenexecutingthisexample,theparameterizedtestisexercisetwice,onceperPersonobjectscontainedintheStream("JohnDoe"and"JaneRoe").
Executionofparameterizedtestusing@MethodSourceandcustomobjectargumentprovider
Wecanalsouse@MethodSourcetospecifyargumentproviderswhichcontainintegerprimitivetypes,concretelyofint,double,andlong.Thefollowingclasscontainsanexample.Wecanseethreeparameterizedtests.Thefirstone(namedtestWithIntProvider)usestheannotation@MethodSourcetolinkwiththestaticmethodintProvider.Inthebodyofthismethod,weusethestandardJavaclassIntStreamtoreturnanStreamofintvalues.Thesecondandthirdtest(calledtestWithDoubleProviderandtestWithLongProvider)arequitesimilar,butusingaStreamofdoubleandlongvalues,respectively:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importjava.util.stream.DoubleStream;
importjava.util.stream.IntStream;
importjava.util.stream.LongStream;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.MethodSource;
classMethodSourcePrimitiveTypesParameterizedTest{
staticIntStreamintProvider(){
returnIntStream.of(0,1);
}
@ParameterizedTest
@MethodSource("intProvider")
voidtestWithIntProvider(intargument){
System.out.println("Parameterizedtestwith(int)argument:"+
argument);
assertNotNull(argument);
}
staticDoubleStreamdoubleProvider(){
returnDoubleStream.of(2d,3d);
}
@ParameterizedTest
@MethodSource("doubleProvider")
voidtestWithDoubleProvider(doubleargument){
System.out.println(
"Parameterizedtestwith(double)argument:"+argument);
assertNotNull(argument);
}
staticLongStreamlongProvider(){
returnLongStream.of(4L,5L);
}
@ParameterizedTest
@MethodSource("longProvider")
voidtestWithLongProvider(longargument){
System.out.println(
"Parameterizedtestwith(long)argument:"+argument);
assertNotNull(argument);
}
}
Thus,whenexecutingthisclass,therewillbesixtestsexecuted(threeparameterizedtestswithtwoargumentseach).
Inthefollowingscreenshot,wecancheckthisbyfollowingthetraceswrittenbyeachtesttothestandardoutput:
Executionofparameterizedtestusing@MethodSourceandprimitivetypesargumentprovider
Finally,withregardsto@MethodSourceparameterizedtests,itisworthittoknowthatthemethodprovidersareallowedtoreturnaStreamofdifferenttypes(objectsorprimitivetypes).Thisisveryconvenientforreal-worldtestcases.Forexample,thefollowingclassimplementsaparameterizedtestinwhichtheargumentproviderisamethodreturningargumentsofmixedtypes:Stringandint.Theseparametersareinjectedinthetestasmethodarguments(calledfirstandsecondintheexample).
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotEquals;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importjava.util.stream.Stream;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.Arguments;
importorg.junit.jupiter.params.provider.MethodSource;
classMethodSourceMixedTypesParameterizedTest{
staticStream<Arguments>stringAndIntProvider(){
returnStream.of(Arguments.of("Mastering",10),
Arguments.of("JUnit5",20));
}
@ParameterizedTest
@MethodSource("stringAndIntProvider")
voidtestWithMultiArgMethodSource(Stringfirst,intsecond){
System.out.println("Parameterizedtestwithtwoarguments:
(String)"+first+"and(int)"+second);
assertNotNull(first);
assertNotEquals(0,second);
}
}
Asusual,therewillbetestexecutionsasentriescontainedintheStream.Inthiscase,therearetwo:"Mastertering"and10,andthen"JUnit5"and20.
Executionofparameterizedtestusing@MethodSourcewithdifferenttypesofarguments
@CsvSourceand@CsvFileSourceAnotherwaytospecifythesourceofargumentsforparameterizedtestsisusingcomma-separatedvalues(CSV).Thiscanbedoneusingtheannotation@CsvSource,whichallowstoembedCSVcontentasStringinthevalueoftheannotation.
Considerthefollowingexample.ItcontainsaJupiterparameterizedtest(namedtestWithCsvSource),whichisusingtheannotation@CsvSource.ThisannotationcontainsanarrayofStrings.Ineachelementofthearray,wecanseethereisadifferentvalueseparatedbycommas.
ThecontentoftheCSVisautomaticallyconvertedtoStringandint.TofindoutmoreabouttheimplicittypeconversionmadeinparametersbyJUnit5,takealooktothesectionArgumentconversioninthischapter.
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotEquals;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.CsvSource;
classCsvSourceParameterizedTest{
@ParameterizedTest
@CsvSource({"hello,1","world,2","'happy,testing',3"})
voidtestWithCsvSource(Stringfirst,intsecond){
System.out.println("Parameterizedtestwith(String)"+first
+"and(int)"+second);
assertNotNull(first);
assertNotEquals(0,second);
}
}
Allinall,whenexecutingthistestclass,therewillbethreesingletests,eachperentryinthearray.Eachexecutionwillbeinvoked,passingtwoargumentstothetest.ThefirstoneisnamedfirstanditstypeisString,andsecondoneiscalledsecondanditstypeisint.
Executionofparameterizedtestusing@CsvSource
IftheamountofCSVdataisbig,itmightbemoreconvenientusingtheannotation@CsvFileSourceinstead.ThisannotationallowstofeedtheparameterizedtestwithaCSVfilelocatedintheclasspathoftheproject.Inthefollowingexample,weusethefileinput.csv:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotEquals;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.CsvFileSource;
classCsvFileSourceParameterizedTest{
@ParameterizedTest
@CsvFileSource(resources="/input.csv")
voidtestWithCsvFileSource(Stringfirst,intsecond){
System.out.println("Yetanotherparameterizedtestwith
(String)"+first+"and(int)"+second);
assertNotNull(first);
assertNotEquals(0,second);
}
}
Internally,theannotation@CsvFileSourcelocatesthefileusingthemethodgetResourceAsStream()ofthestandardJavaclassjava.lang.Class.Therefore,thepathofthefileisinterpretedasapathlocaltothepackageclasswearecallingitfrom.Sinceourresourceislocatedintherootoftheclasspath(intheexampleitislocatedinthefoldersrc/test/resources),weneedtolocateitas/input.csv.
Locationandcontentofinput.csvintheexamplewith@CsvFileSource
ThefollowingscreenshotshowstheoutputofthetestwhenitisexecutedwithMaven.SincetheCSVhasthreerowsofdata,therearethreetestexecutions,eachonewithtwoparameters(firstoneasStringandsecondoneasint):
Executionofparameterizedtestusing@CsvFileSource
@ArgumentsSourceThelastannotationaimedtospecifythesourceofargumentsforparameterizedtestsinJUnit5is@ArgumentsSource.Withthisannotation,wecanspecifyacustom(andreusableindifferenttests)class,whichwillcontaintheparametersforthetest.Thisclassmustimplementtheinterfaceorg.junit.jupiter.params.provider.ArgumentsProvider.
Let’sseeanexample.ThefollowingclassimplementsaJupiterparameterizedtest,inwhichtheargumentssourcewillbedefinedintheclassCustomArgumentsProvider1:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.ArgumentsSource;
classArgumentSourceParameterizedTest{
@ParameterizedTest
@ArgumentsSource(CustomArgumentsProvider1.class)
voidtestWithArgumentsSource(Stringfirst,intsecond){
System.out.println("Parameterizedtestwith(String)"+first
+"and(int)"+second);
assertNotNull(first);
assertTrue(second>0);
}
}
Thisclass(namedCustomArgumentsProvider1)hasbeenimplementedonourside,andduetothefactthatitimplementstheinterfaceArgumentsProvider,mustoverridethemethodprovideArguments,inwhichtheactualdefinitionofparametersforthetestisimplemented.Lookingatthecodeoftheexample,wecanseethatthismethodreturnsaStreamofArguments.Inthisexample,wearereturningacoupleofentriesintheStream,eachonewithtwoarguments(Stringandint,respectively):
packageio.github.bonigarcia;
importjava.util.stream.Stream;
importorg.junit.jupiter.api.extension.ExtensionContext;
importorg.junit.jupiter.params.provider.Arguments;
importorg.junit.jupiter.params.provider.ArgumentsProvider;
publicclassCustomArgumentsProvider1implementsArgumentsProvider{
@Override
publicStream<?extendsArguments>provideArguments(
ExtensionContextcontext){
System.out.println("Argumentsprovidertotest"
+context.getTestMethod().get().getName());
returnStream.of(Arguments.of("hello",1),
Arguments.of("world",2));
}
}
NoticealsothatthisargumenthasanargumentoftypeExtensionContext(packageorg.junit.jupiter.api.extension).Thisargumentisveryusefultoknowthecontextinwhichthetestisexecuted.Asillustratedinthescreenshothere,ExtensionContextAPIoffersdifferentmethodstofindoutdifferentattributesofthetestinstance(testmethodname,displayname,tags,andsoon).
Inourexample(CustomArgumentsProvider1),thecontextisusedtowritethetestmethodnameinthestandardoutput:
ExtensionContextAPI
Thus,whenexecutingthisexample,wecanseetwotestsbeingexecuted.Moreover,wecancheckthelogtracewiththetestmethod,thankstotheExtensionContextobjectinside,outArgumentsProviderinstance:
Executionofparameterizedtestusing@ArgumentsSource
Severalargumentsourcescanbeappliedtothesameparameterizedtest.Infact,thiscanbedoneintwodifferentwaysintheJupiterprogramming
model:
Usingseveralannotationof@ArgumentsSourcetogetherwiththesame@ParameterizedTest.Thiscanbedonesince@ArgumentsSourceisajava.lang.annotation.Repeatableannotation.Usingtheannotation@ArgumentsSources(noticethesourceispluralhere).Thisannotationissimplyacontainerforoneormore@ArgumentsSource.Thefollowingclassshowsasimpleexample:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.ArgumentsSource;
importorg.junit.jupiter.params.provider.ArgumentsSources;
classArgumentSourcesParameterizedTest{
@ParameterizedTest
@ArgumentsSources({
@ArgumentsSource(CustomArgumentsProvider1.class),
@ArgumentsSource(CustomArgumentsProvider2.class)})
voidtestWithArgumentsSource(Stringfirst,intsecond){
System.out.println("Parameterizedtestwith(String)"+first
+"and(int)"+second);
assertNotNull(first);
assertTrue(second>0);
}
}
Supposingthatthesecondargumentprovider(CustomArgumentsProvider2.class)specifiestwoormoresetsofargument,whenexecutingthetestclasstherewillbefourtestexecutions:
Executionofparameterizedtestusing@ArgumentsSources
ArgumentconversionTosupportusecasessuchas@CsvSourceand@CsvFileSource,Jupiterprovidesanumberofbuilt-inimplicitconverters.Moreover,theseconverterscanbeimplementedbasedonspecificneedsbymeansofexplicitconverters.Thissectioncoversbothtypesofconversions.
ImplicitconversionInternally,JUnit5handlesasetofrulesfortheconversionofparametersfromStringtotheactualargumenttype.Forexample,if@ParameterizedTestsdeclaresaparameteroftypeTimeUnit,butthedeclaredsourceisaString,internallythisStringwillbeconvertedtoTimeUnit.ThefollowingtablesummarizestherulesofimplicitconversionsinJUnit5forparameterizedtestarguments:
TargetType Example
boolean/Boolean "false"->false
byte/Byte "1"->(byte)1
char/Character "a"->'a'
short/Short "2"->(short)2
int/Integer "3"->3
long/Long "4"->4L
float/Float "5.0"->5.0f
double/Double "6.0"->6.0d
Enumsubclass "SECONDS"->TimeUnit.SECONDS
java.time.Instant "1970-01-01T00:00:00Z"->Instant.ofEpochMilli(0)
java.time.LocalDate "2017-10-24"->LocalDate.of(2017,10,24)
java.time.LocalDateTime "2017-03-14T12:34:56.789"->LocalDateTime.of(2017,3,14,12,34,56,789_000_000)
java.time.LocalTime "12:34:56.789"->LocalTime.of(12,34,56,789_000_000)
java.time.OffsetDateTime"2017-03-14T12:34:56.789Z"->OffsetDateTime.of(2017,3,14,12,34,56,789_000_000,ZoneOffset.UTC)
java.time.OffsetTime "12:34:56.789Z"->OffsetTime.of(12,34,56,789_000_000,ZoneOffset.UTC)
java.time.Year "2017"->Year.of(2017)
java.time.YearMonth "2017-10"->YearMonth.of(2017,10)
java.time.ZonedDateTime "2017-10-24T12:34:56.789Z"->ZonedDateTime.of(2017,10,24,12,34,56,789_000_000,ZoneOffset.UTC)
Thefollowingexampleshowsseveralexamplesofimplicitconversion.Thefirsttest(testWithImplicitConversionToBoolean)declaresaStringsourceas"true",butthen,theexpectedargumenttypeisBoolean.Similarly,thesecondtest("testWithImplicitConversionToInteger")makesanimplicitconversionfromStringtoInteger.Thethirdtest(testWithImplicitConversionToEnum)convertstheinputStringtoTimeUnit(enumeration),andfinallythefourthtest(testWithImplicitConversionToLocalDate)producesaconversiontoLocalDate:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importjava.time.LocalDate;
importjava.util.concurrent.TimeUnit;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.ValueSource;
classImplicitConversionParameterizedTest{
@ParameterizedTest
@ValueSource(strings="true")
voidtestWithImplicitConversionToBoolean(Booleanargument){
System.out.println("Argument"+argument+"isatypeof"
+argument.getClass());
assertTrue(argument);
}
@ParameterizedTest
@ValueSource(strings="11")
voidtestWithImplicitConversionToInteger(Integerargument){
System.out.println("Argument"+argument+"isatypeof"
+argument.getClass());
assertTrue(argument>10);
}
@ParameterizedTest
@ValueSource(strings="SECONDS")
voidtestWithImplicitConversionToEnum(TimeUnitargument){
System.out.println("Argument"+argument+"isatypeof"
+argument.getDeclaringClass());
assertNotNull(argument.name());
}
@ParameterizedTest
@ValueSource(strings="2017-07-25")
voidtestWithImplicitConversionToLocalDate(LocalDateargument){
System.out.println("Argument"+argument+"isatypeof"
+argument.getClass());
assertNotNull(argument);
}
}
Wecanchecktheactualtypeoftheargumentintheconsole.Eachtestwritesalineinthestandardoutputwiththevalueandthetypeofeachargument:
Executionofparameterizedtestsusingimplicitargumentconversion
ExplicitconversionIftheimplicitconversionprovidedbyJUnit5isnotenoughtocoverourneeds,wecanusetheexplicitconversioncapability.Thankstothisfeature,wecanspecifyaclasswhichisgoingtomakethecustomconversionofparametertypes.Thiscustomconverterisidentifiedwiththeannotation@ConvertWith,referringtotheargumenttobeconvertedwith.Considerthefollowingexample.Thisparameterizedtestdeclaresacustomconverterforitstestmethodargument:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importjava.util.concurrent.TimeUnit;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.converter.ConvertWith;
importorg.junit.jupiter.params.provider.EnumSource;
classExplicitConversionParameterizedTest{
@ParameterizedTest
@EnumSource(TimeUnit.class)
voidtestWithExplicitArgumentConversion(
@ConvertWith(CustomArgumentsConverter.class)String
argument){
System.out.println("Argument"+argument+"isatypeof"
+argument.getClass());
assertNotNull(argument);
}
}
OurcustomconvertedisaclassthatextendstheJUnit5’sSimpleArgumentConverter.Thisclassoverridesthemethodconvert,inwhichtheactualconversiontakesplace.Intheexample,wesimplytransformwhateverargumentsourcetoString.
packageio.github.bonigarcia;
importorg.junit.jupiter.params.converter.SimpleArgumentConverter;
publicclassCustomArgumentsConverterextendsSimpleArgumentConverter{
@Override
protectedObjectconvert(Objectsource,Class<?>targetType){
returnString.valueOf(source);
}
}
Allinall,whenthetestisexecuted,thesevenenumerationconstantsdefinedinTimeUnitwillbepassedasargumentstothetest,priorconversiontoStringinCustomArgumentsConverter:
CustomnamesThelastfeaturerelatedwithparameterizedtestsinJUnit5hastodowiththedisplaynameofeachexecutionoftests.Aswelearned,aparameterizedtestisusuallyexecutedasseveralsingletests.Therefore,fortheshakeoftraceability,itisgoodpracticetolinkeachtestexecutionwiththeargumentsource.
Tothataim,theannotation@ParameterizedTestacceptsanelementcallednameinwhichwecanspecifyacustomname(String)forthetestexecution.Moreover,inthisString,wecanuseseveralbuilt-inplaceholders,asdescribedinthefollowingtable:
Placeholder Description
{index} Currentinvocationindex(firstoneis1,secondis2,…)
{arguments} Comma-separatedargumentscompletelist
{0},{1},… Valueforanindividualargument(firstoneis0,secondis2,…)
Let’sseeasimpleexample.Thefollowingclasscontainsaparameterizedtestwhoseargumentsaredefinedusinga@CsvSourceannotation.Thetestmethodacceptstwoarguments(Stringandint).Inaddition,wearespecifyingtheelementnameoftheannotation@ParameterizedTestwithacustommessage,usingtheplaceholdersforthecurrenttestinvocation({index})andalsoforthevaluesofeachargument:thefirstone({0})andthesecondone({1}):
packageio.github.bonigarcia;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.CsvSource;
classCustomNamesParameterizedTest{
@DisplayName("Displaynameoftestcontainer")
@ParameterizedTest(name="[{index}]firstargument=\"{0}\",second
argument={1}")
@CsvSource({"mastering,1","parameterized,2","tests,3"})
voidtestWithCustomDisplayNames(Stringfirst,intsecond){
System.out.println("Testingwithparameters:"+first+"and"+
second);
}
}
WhenexecutingthistestinanIDE(IntelliJinthefollowingscreenshot),wecanseehowthedisplaynameisdifferentforeachtestexecution:
ExecutionofparameterizedtestsusingcustomnamesinIntelliJIDE
Java9Java9wasreleasedforGeneralAvailability(GA)onSeptember21,2017.TherearemanynewfeaturesshippedwithJava9.Amongthem,modularityisthedefiningfeatureforJava9.
Sofar,therehasbeenaproblemofmodularityinJava,especiallysignificantforlargecodebases.Everypublicclasscanbeaccessedbyanyotherclassintheclasspath,leadingtoinadvertentusageofclasses.Inaddition,theclasspathpresentspotentialproblems,suchastheinabilitytoknowwhetherornotthereareduplicatedJARs.Tosolvetheseproblems,Java9providestheJavaPlatformModuleSystem,whichallowstocreatemodularJARfiles.Thistypeofmodulescontainsanadditionalmoduledescriptorcalledmodule-info.java.Thecontentofsuchfilesisquitesimple:itdeclaresdependenciestoothermodulesusingthekeywordrequires,andexportsitsownpackageswiththekeywordexports.Allnon-exportedpackagesareencapsulatedinthemodulebydefault,forexample:
modulemymodule{
exportsio.github.bonigarcia;
requiresmydependency;
}
Wecanrepresenttherelationshipbetweenthesemodulesasfollows:
ExampleofrelationshipbetweenmodulesinJava9
OthernewcapabilitiesofJava9aresummarizedinthefollowinglist:
TheusemodulesallowtocreateaminimalruntimeJDKoptimizedforthegivenapplication,insteadofusingafullyJDKinstallation.ThiscanbeachieveusingthetoolthejlinkshippedwithJDK9.Java9providesaninteractiveenvironmenttoexecuteJavacode,directlyfromtheshell.ThistypeofutilityiscommonlyknownasRead-Eval-Print-Loop(REPL),whichiscalledJShellinJDK9.Collectionfactorymethods,Java9providesthecapabilityofcreating
collections(forexample,listsorsets)andpopulatestheminasingleline:
Set<Integer>ints=Set.of(1,2,3);
List<String>strings=List.of("first","second");
StreamAPIimprovements:StreamswasintroducedinJava8,andtheyallowtocreatedeclarativepipelinesoftransformationsoncollections.InJava9,themethodsdropWhile,takeWhile,andofNullableareaddedtotheStreamAPI.Privateinterfacemethods:Java8providesdefaultmethodsoninterfaces.ThelimitationsofaristhatdefaultmethodsinJava8mustbepublic.Now,inJava9,thesedefaultmethodscanbealsoprivate,helpingtostructurebettertheirimplementation.HTTP/2:Java9supportsoutofthebox,version2ofHTTPandalsoWebSockets.MultireleaseJARs:Thisfeatureallowstocreatealternativeversionsofclasses,dependingontheversionoftheJREexecutingtheJAR.Tothataim,underthefolderMETA-INF/versions/<java-version>,wecanspecifydifferentversionsofcompiledclasses,whichwillusedonlywhentheJREversionmatchestheversion.ImprovedJavadoc:Lastbutnotleast,Java9allowstocreateHTML5compliantJavadocwithanintegratedsearchcapability.
JUnit5andJava9compatibilitySinceM5,allJUnit5artifactsareshippedwithcompiledmoduledescriptorsforJava9,declaredinitsJARmanifest(fileMANIFEST.MF).Forexample,thecontentofthemanifestfortheartifactjunit-jupiter-apiM6isthefollowing:
Manifest-Version:1.0
Implementation-Title:junit-jupiter-api
Automatic-Module-Name:org.junit.jupiter.api
Build-Date:2017-07-18
Implementation-Version:5.0.0-M6
Built-By:JUnitTeam
Specification-Vendor:junit.org
Specification-Title:junit-jupiter-api
Implementation-Vendor:junit.org
Build-Revision:3e6482ab8b0dc5376a4ca4bb42bef1eb454b6f1b
Build-Time:21:26:15.224+0200
Created-By:1.8.0_131(OracleCorporation25.131-b11)
Specification-Version:5.0.0
WithregardstoJava9,theinterestingthingisthedeclarationAutomatic-Module-Name.ThisallowstotestmodulestorequiretheJUnit5modulesimplybyaddingthefollowinglinestoitsmoduledescriptorfile(module-info.java):
modulefoo.bar{
requiresorg.junit.jupiter.api;
}
BeyondJUnit5.0JUnit5.0GA(GeneralAvailability)wasreleasedonSeptember10,2017.Furthermore,JUnitisalivingproject,andnewfeaturesareplannedforthenextrelease,thatis,5.1(withnoreleaseagendascheduledatthetimeofwriting).ThebacklogforthenextreleaseofJUnit5canbeseenonGitHub:https://github.com/junit-team/junit5/milestone/3.Amongother,thefollowingfeaturesareplannedforJUnit5.1:
Scenariotests:Thisfeaturehastodowiththecapabilityoforderingdifferenttestmethodswithinaclass.Todothat,thefollowingannotationsareplanned:
@ScenarioTest:Aclass-levelannotationusedtodenotethatatestclasscontainsstepsthatmakeupasinglescenariotest.@Step:Amethod-levelannotationusedtodenotethatatestmethodisasinglestepwithinthescenariotest.Supportforparalleltestsexecution:ConcurrencyisoneofthemainaspectstobeimprovedinJUnit5.1,andthereforethesupportofoutoftheboxconcurrenttestexecutionisplanned.
Mechanismforterminatingdynamictestsearly:ThisisanenhancementoftheJUnit5.0supportfordynamictests,introducingatimeouttostoptheexecutionbeforeitterminatesitself(toavoiduncontrollednon-deterministicexecutions).Severalimprovementsintestreporting,suchascapturingstdout/stderrandincludeintestreports,providereliablewaytogettheclass(classname)ofexecutedtestmethods,orspecifytheorderoftestsintestreports,amongothers.
SummaryThischaptercontainsacomprehensivesummaryoftheadvancecapabilitiestowriterichJupitertestsdrivenbyexamples.First,wehavelearnedthatparameterscanbeinjectedinconstructorandmethodsintestclasses.JUnit5providesthreeparameterresolversoutofthebox,namelyresolverforparametersofthetypeTestInfo(toretrieveinformationaboutthecurrenttest),resolverforparametersofthetypeRepetitionInfo(toretrieveinformationaboutthecurrentrepetition),andresolverforparametersofthetypeTestReporter(topublishadditionaldataaboutthecurrenttestrun).
AnothernewfeatureimplementedinJupiteristheconceptofdynamictests.SofarinJUnit3and4,testsaredefinedatcompiletime(thatisstatictests).Jupiterintroducestheannotation@TestFactorythatallowstogeneratetestatruntime.AnothernewconceptprovidedbytheJupiterprogrammingmodelarethetesttemplates.Thesetemplatesredefinedusingtheannotation@TestTemplateandarenotregulartestcasesbutratheratemplatefortestcases.
JUnit5implementsanenhancementsupportforparameterizedtests.Inordertoimplementthistypeoftests,theannotation@ParameterizedTestmustbeused.Togetherwiththisannotation,anargumentprovidershouldbealsospecified.Tothataim,severalannotationsareprovidedinJupiter:@ValueSource,@EnumSource,@MethodSource,@CsvSource,@CsvFileSource,and@ArgumentSource.
Inthechapter5,IntegrationOfJUnit5WithExternalFrameworks,wearegoingtolearnhowJUnit5interactswithexternalframeworks.Concretely,wearegoingtoreviewseveralJUnit5extension,whichprovidescapabilitiestouseMockito,Spring,Selenium,Cucumber,orDocker.Moreover,wepresentaGradleplugin,whichallowstoexecutetestswithinanAndroidproject.Finally,wefindouthowtouseseveralRESTlibraries(forexample,RESTAssuredorWireMock)totestRESTfulservices.
IntegrationOfJUnit5WithExternalFrameworks
IfIhaveseenfurtherthanothers,itisbystandingupontheshouldersofgiants.
-IsaacNewton
AsdescribedinChapter2,What’sNewinJUnit,theextensionmodelofJUnit5allowsustoextendthecorefunctionalityofJUnit5byathirdparty(toolvendor,developers,andsoon).IntheJupiterextensionmodel,anextensionpointisacallbackinterfacethattheextensionimplementsandthenregisters(activates)intheJUnit5framework.Aswewilldiscoverinthischapter,theJUnit5extensionmodelcanbeusedtoprovideseamlessintegrationwithexistingthird-partyframeworks.Concretely,inthischapter,wereviewJUnit5extensionforthefollowingtechnologies:
Mockito:Mock(testdouble)unittestingframework.Spring:AJavaframeworkforbuildingenterpriseapplications.Selenium:Atestingframeworktoautomatethenavigationandassessmentofwebapplications.Cucumber:TestingframeworkwhichallowsustocreateacceptancetestswrittenfollowingaBehavior-DrivenDevelopment(BDD)style.Docker:Asoftwaretechnologywhichallowsustopackandrunanyapplicationasalightweightandportablecontainer.
Moreover,wediscoverthattheJUnit5extensionmodelisnottheonlywaytointegratewiththeexternalworld.Concretely,westudyhowJUnit5canbeusedtogetherwiththefollowing:
Android(mobileoperatingsystembasedonLinux):WecanrunJupitertestsinanAndroidprojectusingaGradlepluginforJUnit5.REST(architecturalstylefordesigningdistributedsystems):WecaninteractandverifyRESTservicessimplyusingthird-partylibraries(suchasRESTAssuredorWireMock),orusingthefullyintegratedapproachofSpring(teststogetherwiththeserviceimplementation).
MockitoMockito(http://site.mockito.org/)isanopensourcemockunittestingframeworkforJava,firstreleasedinApril2008.Ofcourse,MockitoisnottheonlymockframeworkforJava;thereareothers,suchasthefollowing:
EasyMock(http://easymock.org/).JMock(http://www.jmock.org/).PowerMock(http://powermock.github.io/).JMockit(http://jmockit.org/).
Wecansaythat,atthetimeofwriting,MockitoisthepreferredmockframeworkinJavatestsforthemostdevelopersandtesters.Tojustifythatclaim,weusethefollowingscreenshot,whichshowstheevolutionofthetermsMockito,EasyMock,JMock,PowerMock,andJMockitinGoogleTrends(https://trends.google.com/)from2004to2017.Atthebeginningofthisperiod,wecanseetherewasasignificantinterestonEasyMockandJMock;nevertheless,Mockitowasmoreindemandcomparedwiththerestoftheframeworks:
MockitoinanutshellAsintroducedinChapter1,RetrospectiveonSoftwareQualityandJavaTesting,therearedifferentlevelsofsoftwaretesting,suchasunit,integration,system,oracceptance.Regardingunittests,theyshouldbeexecutedinisolationforasinglepieceofsoftware,forexample,anindividualclass.Theobjectiveinthisleveloftestsistoverifythefunctionalityoftheunitandnotofitsdependencies.
Inotherwords,wewanttotestwhatisknownastheSystemUnderTest(SUT)butnotitsDepended-OnComponents(DOCs).Toachievethisisolation,weusetypicallytestdoublestoreplacetheseDOCs.Mockobjectsareakindoftestdouble,whichareprogrammedwithexpectationsabouttherealDOC.
Infewwords,Mockitoisatestingframeworkthatallowsmockobjectcreation,stubbing,andverification.Tothataim,MockitoprovidesanAPItoisolatetheSUTanditsDOCs.Generallyspeaking,usingMockitoinvolvesthreedifferentsteps:
1. Mockingobjects:InordertoisolateourSUT,weusetheMockitoAPItocreatemocksofitsassociatedDOC(s).Thisway,weguaranteethattheSUTisnotdependingonitsrealDOC(s),andourunittestisactuallyfocusedontheSUT.
2. Settingexpectations:Thedifferentialaspectofmocksobjectwithrespecttoothertestdoubles(suchasstub)isthatmockobjectscanbeprogrammedwithcustomexpectationsaccordingtotheneedsoftheunittest.ThisprocessintheMockitojargonisknownasstubbingmethods,inwhichthesemethodsbelongtothemocks.Bydefault,mockobjectsmimicthebehaviorofrealobjects.Inpracticalterms,itmeansthatmockobjectsreturnappropriatedummyvaluessuchasfalseforBooleantypes,nullforobjects,0forintegerorlongreturntypes,andsoon.MockitoallowsustochangethisbehaviorwitharichAPI,whichallowsstubbingtoreturnaspecificvaluewhenamethodiscalled.
Whenamockobjectisnotprogrammedwithanyexpectation(thatis,ithasnostubbingmethod),technicallyspeaking,itisnotamockobjectbutadummyobject(takealookatChapter1,RetrospectiveonSoftwareQualityandJavaTestingforthe
definition).
3. Verification:Attheendoftheday,wearecreatingtests,andthus,weneedtoimplementsomekindofverificationfortheSUT.MockitoprovidesapowerfulAPItocarryoutdifferenttypesofverifications.WiththisAPI,weassesstheinteractionswiththeSUTandDOCs,verifyingtheinvocationorderwithamock,orcapturingandverifyingtheargumentpassedtoastubbedmethod.Furthermore,theverificationcapabilitiesofMockitocanbecomplementedwiththebuilt-inassertioncapabilitiesofJUnitorusingathird-partyassertionlibrary(forexample,Hamcrest,AssertJ,orTruth).SeesectionAssertionswithinChapter3,JUnit5StandardTests.
ThefollowingtablesummarizestheMockitoAPIsgroupedbytheaforementionedphases:
MockitoAPI Description Phase
@Mock
ThisannotationidentifiesamockobjecttobecreatedbyMockito.ThisisusedtypicallyforDOC(s).
1.Mockingobjects
@InjectMocks
Thisannotationidentifiestheobjectinwhichthemocksaregoingtobeinjected.Thisisusedtypicallytotheunitwewanttotest,thatis,ourSUT.
1.Mockingobjects
@Spy
Inadditiontomocks,Mockitoallowsustocreatespyobjects(thatis,apartialmockimplementation,sincetheyusetherealimplementationinnon-stubbedmethods).
1.Mockingobjects
Mockito.when(x).thenReturn(y)
Mockito.doReturn(y).when(x)
Thesemethodsallowustospecifythevalue(y)thatshouldbereturnedbythe
2.Settingexpectations(stubbing
stubbedmethod(x)ofagivenmockobject.
methods)
Mockito.when(x).thenThrow(e)
Mockito.doThrow(e).when(x)
Thesemethodsallowustospecifytheexception(e)thatshouldbethrownwhencallingastubbedmethod(x)ofagivenmockobject.
2.Settingexpectations(stubbingmethods)
Mockito.when(x).thenAnswer(a)
Mockito.doAnswer(a).when(x)
Unlikereturningahardcodedvalue,adynamicuser-definedlogic(Answera)isexecutedwhenagivenmethod(x)ofthemockisinvoked.
2.Settingexpectations(stubbingmethods)
Mockito.when(x).thenCallRealMethod()
Mockito.doCallRealMethod().when(x)
Thismethodallowsustherealimplementationofamethodinsteadthemockedone.
2.Settingexpectations(stubbingmethods)
Mockito.doNothing().when(x)
Whenusingaspy,thedefaultbehavioriscallingtherealmethodsoftheobject.Inordertoavoidtheexecutionofavoidmethodx,thismethodisused.
2.Settingexpectations(stubbingmethods)
BDDMockito.given(x).willReturn(y)
BDDMockito.given(x).willThrow(e)
BDDMockito.given(x).willAnswer(a)
BDDMockito.given(x).willCallRealMethod()
Behaviour-drivendevelopmentisatestmethodologyinwhichtestsarespecifiedintermsofscenariosandimplementedasgiven(initialcontext),when(eventoccurs),andthen(ensuresomeoutcomes).MockitosupportsthistypeofteststhroughtheclassBDDMockito.Thebehaviorofthestubbedmethods(x)isequivalenttoMockito.when(x).
2.Settingexpectations(stubbingmethods)
Mockito.verify()
Thismethodverifiestheinvocationofmockobjects.Thisverificationcanbeoptionallyenhancedusingthefollowingmethods:
times(n):Thestubbedmethodisinvokedexactlyntimes.never():Thestubbedmethodisnevercalled.atLeastOnce():Thestubbedmethodisinvokedatleastonce.atLeast(n):Thestubbedmethodiscalledatleastntimes.atMost(n):Thestubbedmethodiscalledatthemostntimes.only():Amockfailsifanyothermethodiscalledonthemockobject.timeout(m):Thismethodiscalledinmmillisecondsatthemost.
3.Verification
Mockito.verifyZeroInteractions()
Mockito.verifyNoMoreInteractions()
Thesetwomethodsverifythatastubbedmethodhasnointeractions.Internally,theyusethesameimplementation.
3.Verification
@Captor
ThisannotationallowsustodefineanArgumentChaptorobject,aimedtoverifytheargumentspassedtoastubbedmethod.
3.Verification
Itfacilitatesverifying
Mockito.inOrder whetherinteractionswithamockwereperformedinagivenorder.
3.Verification
Theuseofthedifferentannotationsdepictedinprecedingthetable(@Mock,@InjectMocks,@Spy,and@Captor)isoptional,althoughitisrecommendablefortheshakeoftestreadability.Inotherwords,therearealternativestotheuseofannotationusingdifferentMockitoclasses.Forinstance,inordertocreateaMock,wecanusetheannotation@Mockasfollows:
@Mock
MyDocdocMock;
ThealternativetothiswouldbeusingthemethodMockito.mock,asfollows:MyDocdocMock=Mockito.mock(MyDoc.class)
ThefollowingsectionscontainscomprehensiveexamplesusingtheMockitoAPIsdescribedinprecedingtablewithinJupitertests.
JUnit5extensionforMockitoAtthetimeofthiswriting,thereisnoofficialJUnit5extensiontouseMockitoinJupitertests.Nevertheless,theJUnit5teamprovidesasimplereadytouseJavaclassimplementingasimplebuteffectiveextensionforMockito.ThisclasscanbefoundintheJUnit5userguide(http://junit.org/junit5/docs/current/user-guide/),anditscodeisthefollowing:
importstaticorg.mockito.Mockito.mock;
importjava.lang.reflect.Parameter;
importorg.junit.jupiter.api.extension.ExtensionContext;
importorg.junit.jupiter.api.extension.ExtensionContext.Namespace;
importorg.junit.jupiter.api.extension.ExtensionContext.Store;
importorg.junit.jupiter.api.extension.ParameterContext;
importorg.junit.jupiter.api.extension.ParameterResolver;
importorg.junit.jupiter.api.extension.TestInstancePostProcessor;
importorg.mockito.Mock;
importorg.mockito.MockitoAnnotations;
publicclassMockitoExtension
implementsTestInstancePostProcessor,ParameterResolver{
@Override
publicvoidpostProcessTestInstance(ObjecttestInstance,
ExtensionContextcontext){
MockitoAnnotations.initMocks(testInstance);
}
@Override
publicbooleansupportsParameter(ParameterContextparameterContext,
ExtensionContextextensionContext){
return
parameterContext.getParameter().isAnnotationPresent(Mock.class);
}
@Override
publicObjectresolveParameter(ParameterContextparameterContext,
ExtensionContextextensionContext){
returngetMock(parameterContext.getParameter(),extensionContext);
}
privateObjectgetMock(Parameterparameter,
ExtensionContextextensionContext){
Class<?>mockType=parameter.getType();
Storemocks=extensionContext
.getStore(Namespace.create(MockitoExtension.class,
mockType));
StringmockName=getMockName(parameter);
if(mockName!=null){
returnmocks.getOrComputeIfAbsent(mockName,
key->mock(mockType,mockName));
}else{
returnmocks.getOrComputeIfAbsent(mockType.getCanonicalName(),
key->mock(mockType));
}
}
privateStringgetMockName(Parameterparameter){
StringexplicitMockName=
parameter.getAnnotation(Mock.class).name()
.trim();
if(!explicitMockName.isEmpty()){
returnexplicitMockName;
}elseif(parameter.isNamePresent()){
returnparameter.getName();
}
returnnull;
}
}
Thisextension(amongothers)isplannedtobereleasedintheopensourceprojectJUnitPioneer(http://junit-pioneer.org/).ThisprojectismaintainedbyNicolaiParlog,JavadeveloperandauthoroftheblogCodeFX(https://blog.codefx.org/).
Inspectingtheprecedingclass,wecancheckthatitissimplyausecaseoftheJupiterextensionmodel(describedinchapter2,What’sNewInJUnit5,ofthisbook),whichimplementstheextensionscallbackTestInstancePostProcessorandParameterResolver.Thankstothefirst,afterthetestcaseisinstantiated,thepostProcessTestInstancemethodisinvoked,andinthebodyofthismethod,theinitializationofmocksiscarriedout:
MockitoAnnotations.initMocks(testInstance)
ThishasthesameeffectthatusingtheJUnit4runnerforMockito:@RunWith(MockitoJUnitRunner.class).
Inaddition,thisextensionalsoimplementstheinterfaceParameterResolver.Thatmeansthatdependencyinjectionatmethodlevelwillbeallowedintests,whichregistertheextension(@ExtendWith(MockitoExtension.class)).Inparticular,theannotationwillinjectmockobjectsfortestparametersannotatedwith@Mock(locatedinpackageorg.mockito).
Let’sseesomeexamplestoclarifytheuseofthisextensiontogetherwithMockito.Asusual,wecanfindthesourcecodeofthisexamplesontheGitHubrepositoryhttps://github.com/bonigarcia/mastering-junit5.Acopyoftheprecedingextension(MockitoExtension)iscontainedintheprojectjunit5-mockito.Toguidetheseexamples,weimplementatypicalusecaseinsoftwareapplications:theloginofauserinasoftwaresystem.
Inthisusecase,wesupposethatauserinteractswithasystemmadeupbythreeclasses:
LoginController:Theclasswhichreceivestherequestfromtheuser,returningaresponseasaresult.ThisrequestisdispatchedtotheLoginServicecomponent.LoginService:Thisclassimplementsthefunctionalityoftheusecase.To
thataim,itneedstoconfirmwhetherornottheuserisauthenticatedinthesystem.Tothat,itneedstoreadthepersistencelayer,implementedintheLoginRepositoryclass.LoginRepository:Thisclassallowstoaccessthepersistencelayerofthesystem,typicallyimplementedbymeansofadatabase.ThisclasscanalsobecalledDataAccessObject(DAO).
Intermsofcomposition,therelationshipofthesethreeclassesareisfollowing:
Loginusecaseclassdiagram(compositionrelationshipamongtheclasses)
Thesequencediagramofthetwobasicoperationsinvolvedintheusecase(loginandlogout)isdepictedinthefollowingchart:
Loginusecasesequencediagram
WeimplementthisexamplewithseveralsimpleJavaclasses.First,theLoginControllerusestheLoginServicebycomposition:
packageio.github.bonigarcia;
publicclassLoginController{
publicLoginServiceloginService=newLoginService();
publicStringlogin(UserFormuserForm){
System.out.println("LoginController.login"+userForm);
try{
if(userForm==null){
return"ERROR";
}elseif(loginService.login(userForm)){
return"OK";
}else{
return"KO";
}
}catch(Exceptione){
return"ERROR";
}
}
publicvoidlogout(UserFormuserForm){
System.out.println("LoginController.logout"+userForm);
loginService.logout(userForm);
}
}
TheUserFormobjectisasimpleJavaclass,sometimescalledPlain-OldJavaObject(POJO),withtwopropertiesusernameandpassword:
packageio.github.bonigarcia;
publicclassUserForm{
publicStringusername;
publicStringpassword;
publicUserForm(Stringusername,Stringpassword){
this.username=username;
this.password=password;
}
//Gettersandsetters
@Override
publicStringtoString(){
return"UserForm[username="+username+",password="+password
+"]";
}
}
Then,theservicedependsontherepository(LoginRepository)fordataaccess.Inthisexample,theservicealsoimplementsauserregistryusingaJavalistinwhichtheauthenticatedusersarestored:
packageio.github.bonigarcia;
importjava.util.ArrayList;
importjava.util.List;
publicclassLoginService{
privateLoginRepositoryloginRepository=newLoginRepository();
privateList<String>usersLogged=newArrayList<>();
publicbooleanlogin(UserFormuserForm){
System.out.println("LoginService.login"+userForm);
//Preconditions
checkForm(userForm);
//Sameusercannotbeloggedtwice
Stringusername=userForm.getUsername();
if(usersLogged.contains(username)){
thrownewLoginException(username+"alreadylogged");
}
//Calltorepositorytomakelogic
booleanlogin=loginRepository.login(userForm);
if(login){
usersLogged.add(username);
}
returnlogin;
}
publicvoidlogout(UserFormuserForm){
System.out.println("LoginService.logout"+userForm);
//Preconditions
checkForm(userForm);
//Usershouldbeloggedbeforehand
Stringusername=userForm.getUsername();
if(!usersLogged.contains(username)){
thrownewLoginException(username+"notlogged");
}
usersLogged.remove(username);
}
publicintgetUserLoggedCount(){
returnusersLogged.size();
}
privatevoidcheckForm(UserFormuserForm){
assertuserForm!=null;
assertuserForm.getUsername()!=null;
assertuserForm.getPassword()!=null;
}
}
Finally,theLoginRepositoryisasfollows.Forthesakeofsimplicity,insteadofaccessingarealdatabase,thiscomponentimplementsamapinwhichthecredentialsofthehypotheticaluserofthesystemarestored(wherekey=username,andvalue=password):
packageio.github.bonigarcia;
importjava.util.HashMap;
importjava.util.Map;
publicclassLoginRepository{
Map<String,String>users;
publicLoginRepository(){
users=newHashMap<>();
users.put("user1","p1");
users.put("user2","p3");
users.put("user3","p4");
}
publicbooleanlogin(UserFormuserForm){
System.out.println("LoginRepository.login"+userForm);
Stringusername=userForm.getUsername();
Stringpassword=userForm.getPassword();
returnusers.keySet().contains(username)
&&users.get(username).equals(password);
}
}
Now,wearegoingtotestoursystemusingJUnit5andMockito.Firstofall,wetestthecontrollercomponent.Sincewearedoingunittests,weneedtoisolatetheLoginControllerloginfromtherestofthesystem.Todothat,weneedtomockitsdependencies,inthisexample,theLoginServicecomponent.UsingtheSUT/DOCterminologyexplainedatthebeginning,inthistest,ourSUTis
theclassLoginControlleranditsDOCistheclassLoginService.
ToimplementourtestwithJUnit5,[email protected],wedeclaretheSUTwith@InjectMocks(classLoginController)anditsDOCwith@Mock(classLoginService).Weimplementtwotests(@Test).Firstone(testLoginOk)specifieswhenthemethodloginofmockloginServiceiscalled,thismethodshouldreturntrue.Afterthat,theSUTisactuallyexercised,anditsresponseisverified(inthiscase,thereturnedStringmustbeOK).Moreover,theMockitoAPIisusedagaintoassessthatnomoreinteractionswiththemockLoginServiceisdone.Thesecondtest(testLoginKo)isequivalent,butstubbingthemethodlogintoreturnfalseandthereforetheresponseoftheSUT(LoginController)mustbeKOinthiscase:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.mockito.Mockito.verify;
importstaticorg.mockito.Mockito.verifyNoMoreInteractions;
importstaticorg.mockito.Mockito.verifyZeroInteractions;
importstaticorg.mockito.Mockito.when;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classLoginControllerLoginTest{
//Mockingobjects
@InjectMocks
LoginControllerloginController;
@Mock
LoginServiceloginService;
//Testdata
UserFormuserForm=newUserForm("foo","bar");
@Test
voidtestLoginOk(){
//Settingexpectations(stubbingmethods)
when(loginService.login(userForm)).thenReturn(true);
//ExerciseSUT
StringreseponseLogin=loginController.login(userForm);
//Verification
assertEquals("OK",reseponseLogin);
verify(loginService).login(userForm);
verifyNoMoreInteractions(loginService);
}
@Test
voidtestLoginKo(){
//Settingexpectations(stubbingmethods)
when(loginService.login(userForm)).thenReturn(false);
//ExerciseSUT
StringreseponseLogin=loginController.login(userForm);
//Verification
assertEquals("KO",reseponseLogin);
verify(loginService).login(userForm);
verifyZeroInteractions(loginService);
}
}
Ifweexecutethistest,simplyinspectingthetracesonthestandardoutputwecancheckthattheSUThavebeenactuallyexecuted.Inaddition,weassurethattheverificationstagehasbeensucceededinbothtestssincebothofthemhavepassed:
ExecutionofunittestofLoginControllerLoginTestwithJUnit5andMockito
Let’smovenowtootherexampleinwhichthenegativescenarios(thatis,errorsituations)aretestedforthecomponentLoginController.Thefollowingclasscontainstwotests,firstone(testLoginError)isdevotedtoassesstheresponseofthesystem(itshouldbeERROR)whenanullformisused.Inthesecondtest(testLoginException),weprogramthemethodloginofthemockloginServicetoraiseanexceptionwhenanyformisusedfirst.Then,weexercisetheSUT(LoginController)andassessthattheresponseisactuallyanERROR:
Notethatweareusingtheargumentmatcherany(providedoutoftheboxbyMockito)whensettingtheexpectationsforthemockmethod.
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.mockito.ArgumentMatchers.any;
importstaticorg.mockito.Mockito.when;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classLoginControllerErrorTest{
@InjectMocks
LoginControllerloginController;
@Mock
LoginServiceloginService;
UserFormuserForm=newUserForm("foo","bar");
@Test
voidtestLoginError(){
//Exercise
Stringresponse=loginController.login(null);
//Verify
assertEquals("ERROR",response);
}
@Test
voidtestLoginException(){
//Expectation
when(loginService.login(any(UserForm.class)))
.thenThrow(IllegalArgumentException.class);
//Exercise
Stringresponse=loginController.login(userForm);
//Verify
assertEquals("ERROR",response);
}
}
Again,whenrunningthetestsintheshell,wecanconfirmthatbothoftestsarecorrectlyexecutedandtheSUTisexercised:
ExecutionofunittestofLoginControllerErrorTestwithJUnit5andMockito
Let’sseeanexampleusingtheBDDstyle.Tothataim,theclassBDDMockitoisused.Noticethatthestaticmethodgivenofthisclassisimportedintheexample.Then,fourtestsareimplemented.Infact,thesefourtestsareexactlythesameimplementedinthepreviousexamples(LoginControllerLoginTestandLoginControllerErrorTest),butthistimeusingtheBDDstyleandamorecompactstyle(one-linercommands).
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.mockito.ArgumentMatchers.any;
importstaticorg.mockito.BDDMockito.given;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classLoginControllerBDDTest{
@InjectMocks
LoginControllerloginController;
@Mock
LoginServiceloginService;
UserFormuserForm=newUserForm("foo","bar");
@Test
voidtestLoginOk(){
given(loginService.login(userForm)).willReturn(true);
assertEquals("OK",loginController.login(userForm));
}
@Test
voidtestLoginKo(){
given(loginService.login(userForm)).willReturn(false);
assertEquals("KO",loginController.login(userForm));
}
@Test
voidtestLoginError(){
assertEquals("ERROR",loginController.login(null));
}
@Test
voidtestLoginException(){
given(loginService.login(any(UserForm.class)))
.willThrow(IllegalArgumentException.class);
assertEquals("ERROR",loginController.login(userForm));
}
}
Theexecutionofthistestclasssupposesthatfourtestsareexecuted.Asshowninthefollowingscreenshot,allofthempass:
ExecutionofunittestofLoginControllerBDDTestwithJUnit5andMockito
Let’smovenowtothenextcomponentofoursystem:LoginService.Inthefollowingexample,weaimtounittestthatcomponent,andthusfirstweusetheannotation@InjectMockstoinjecttheSUTinourtest.Then,theDOC(LoginRepository)[email protected]
tests.Thefirst(testLoginOk)isdevotedtoverifytheansweroftheSUTwhenacorrectformisreceived.Thesecondtest(testLoginKo)verifiestheoppositescenario.Finally,thethirdtestalsoverifiesanerrorsituationofthesystem.Theimplementationofthisservicekeepsaregistryoftheuserslogged,andwillnotallowedtologinthesameusertwice.Forthisreason,weimplementedatest(testLoginTwice),whichverifiesthattheexceptionLoginExceptionisraisedwhenthesameusertriestologintwice:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertFalse;
importstaticorg.junit.jupiter.api.Assertions.assertThrows;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importstaticorg.mockito.ArgumentMatchers.any;
importstaticorg.mockito.Mockito.atLeast;
importstaticorg.mockito.Mockito.times;
importstaticorg.mockito.Mockito.verify;
importstaticorg.mockito.Mockito.when;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classLoginServiceTest{
@InjectMocks
LoginServiceloginService;
@Mock
LoginRepositoryloginRepository;
UserFormuserForm=newUserForm("foo","bar");
@Test
voidtestLoginOk(){
when(loginRepository.login(any(UserForm.class))).thenReturn(true);
assertTrue(loginService.login(userForm));
verify(loginRepository,atLeast(1)).login(userForm);
}
@Test
voidtestLoginKo(){
when(loginRepository.login(any(UserForm.class))).thenReturn(false);
assertFalse(loginService.login(userForm));
verify(loginRepository,times(1)).login(userForm);
}
@Test
voidtestLoginTwice(){
when(loginRepository.login(userForm)).thenReturn(true);
assertThrows(LoginException.class,()->{
loginService.login(userForm);
loginService.login(userForm);
});
}
}
Asusual,theexecutionofthetestinshellgivesusanideaofhowthingshavegone.Wecancheckthattheloginservicehasbeenexercisedfourtimes(since
inthethirdtest,wedidtwice).ButduetothefactthattheLoginExceptionwasexpected,thattestissucceeded(aswelltheothertwo):
ExecutionofunittestofLoginServiceTestwithJUnit5andMockito
Thefollowingclassprovidesasimpleexampleforcapturingtheargumentofamockobject.WedefineaclasspropertyoftypeArgumentCaptor<UserForm>,[email protected],inthebodyofthetest,theSUT(LoginServiceinthiscase)isexercisedandtheargumentofthemethodloginarecaptured.Finally,thevalueofthisargumentisassessed:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.mockito.Mockito.verify;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.ArgumentCaptor;
importorg.mockito.Captor;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classLoginServiceChaptorTest{
@InjectMocks
LoginServiceloginService;
@Mock
LoginRepositoryloginRepository;
@Captor
ArgumentCaptor<UserForm>argCaptor;
UserFormuserForm=newUserForm("foo","bar");
@Test
voidtestArgumentCaptor(){
loginService.login(userForm);
verify(loginRepository).login(argCaptor.capture());
assertEquals(userForm,argCaptor.getValue());
}
}
Onceagain,intheconsole,wecheckthattheSUTwasexercisedandthetestisdeclaredassuccessful:
ExecutionofunittestofLoginServiceChaptorTestwithJUnit5andMockito
ThelastexampleweseeinthischapterrelatedtoMockitohastodowiththeuseofanspy.Asintroducedbefore,bydefault,anspyusestherealimplementationinnon-stubbedmethods.Therefore,ifwedonotstubmethodsinanspyobject,whatwegetistherealobjectinourtest.Thisiswhathappensinthenextexample.Aswecansee,weareusingtheLoginServiceasourSUT,andthenwespytheobjectLoginRepository.Duetothefactthatinthebodyofthetestswearenotprogrammingexpectationsinthespyobject,weareassessingtherealsysteminthetest.
Allinall,thetestdataispreparedtogetalogincorrect(usingusernameasuserandpasswordasp1,whichispresentinthehardcodedvaluesintherealimplementationofLoginRepository),andthensomedummyvaluesforanunsuccessfullogin:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertFalse;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Spy;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classLoginServiceSpyTest{
@InjectMocks
LoginServiceloginService;
@Spy
LoginRepositoryloginRepository;
UserFormuserOk=newUserForm("user1","p1");
UserFormuserKo=newUserForm("foo","bar");
@Test
voidtestLoginOk(){
assertTrue(loginService.login(userOk));
}
@Test
voidtestLoginKo(){
assertFalse(loginService.login(userKo));
}
}
Intheshell,wecancheckthatbothtestswerecorrectlyexecuted,andinthiscase,therealcomponents(bothLoginServiceandLoginRepository)wereactuallyexercised:
ExecutionofunittestofLoginServiceSpyTestwithJUnit5andMockito
TheseexamplesdemonstrateseveralofthecapabilitiesofMockito,butofcoursenotall.Forfurtherinformation,visittheofficialMockitoreferenceathttp://site.mockito.org/.
SpringSpring(https://spring.io/)isanopensourceJavaframeworkforbuildingenterpriseapplications.ItwasfirstwrittenbyRodJohnsontogetherwithhisbookExpertOne-on-OneJ2EEDesignandDevelopmentinOctober2002.TheoriginalmotivationofSpringwasgettingridofthecomplexityofJ2EE,providingalight-weightinfrastructureaimedtoeasethedevelopmentofenterpriseapplicationusingsimplePOJOsasbuildingblocks.
SpringinanutshellThecoretechnologyoftheSpringFrameworkisknownasInversionofControl(IoC),whichistheprocessofinstantiatingobjectsoutsidetheclassinwhichtheseobjectsareactuallyused.TheseobjectsareknownasbeansorcomponentsintheSpringjargonandarecreatedassingletonobjectsbydefault.TheentityinchargeofthecreationofbeansisknownastheSpringIoCcontainer.ThisisachievedbyDependencyInjection(DI),whichistheprocessofprovidingdependenciesofoneobjectinsteadofconstructingthemitself.
IoCandDIareoftenusedinterchangeably.Nevertheless,asdepictedintheparagraphearlier,theseconceptsarenotexactlythesame(IoCisachievedthroughDI).
Asdepictedinthenextpartofthissection,Springisamodularframework.ThecorefunctionallyofSpring(thatis,IoC)isprovidedinthespring-contextmodule.Thismoduleprovidestheabilityofcreatingapplicationcontext,thatis,theSpring’sDIcontainer.TherearemanydifferentwaystodefineapplicationcontextsinSpring.Twoofthemostsignificanttypesarethefollowing:
AnnotationConfigApplicationContext:Applicationcontext,whichacceptsannotatedclassestoidentifytheSpringbeanstobeexecutedinthecontainer.Inthistypeofcontext,beansareidentifiedbyannotatingplainclasseswiththeannotation@Component.ItisnottheonlyonetodeclareaclassasaSpringbean.Therearefurtherstereotypesannotations:@Controller(stereotypeforpresentationlayer,usedinthewebmodule,MVC),@Repository(stereotypeforthepersistencelayer,usedinthedataaccessmodule,calledSpringData),and@Service(usedintheservicelayer).Thesethreeannotationsareusedtoseparatethelayersofanapplication.Finally,classesannotatedwith@ConfigurationallowstodefineSpringbeansbyannotatingmethodswith@Bean(theobjectreturnedbythesemethodswillbeSpringbeanslivinginthecontainer):
Springstereotypesusedtodefinebeans
ClassPathXmlApplicationContext:Applicationcontext,whichacceptsbeandefinitionsdeclaredinanXMLfilelocatedintheprojectclasspath.
Theannotation-basedcontextconfigurationwasintroducedinSpring2.5.TheSpringIoCcontaineristotallydecoupledfromtheformatinwhichconfigurationmetadata(thatis,beandefinition)isactuallywritten.Nowadaysmanydeveloperschoseannotation-basedconfigurationratherthanXMLbased.Forthisreason,inthisbook,wearegoingtouseonlyannotation-basedcontextconfigurationintheexamples.
Let’sseeasimpleexample.Firstofall,weneedtoincludethespring-contextdependencyinourproject.Forexample,asaMavendependency:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-context.version}</version>
</dependency>
Then,wecreateanexecutableJavaclass(thatis,withamainmethod).Noticethatinthisclassthereisoneannotationatclasslevel:@ComponentScan.ThisisaveryimportantannotationinSpring,sinceitallowstodeclarethepackageinwhichSpringwilllookforbeansdefinitionintheformofannotations.Ifspecificpackagesarenotdefined(justlikeintheexample),scanningwilloccurfromthepackageoftheclassthatdeclaresthisannotation(intheexamplethepackageio.github.bonigarcia).Inthebodyofthemainmethod,wecreatetheSpringapplicationcontextwithAnnotationConfigApplicationContext.Fromthatcontext,wegettheSpringcomponentwhoseclassisMessageComponent,andwewritetheresultofitsgetMessage()methodonthestandardoutput:
packageio.github.bonigarcia;
importorg.springframework.context.annotation.AnnotationConfigApplicationContext;
importorg.springframework.context.annotation.ComponentScan;
@ComponentScan
publicclassMySpringApplication{
publicstaticvoidmain(String[]args){
try(AnnotationConfigApplicationContextcontext=new
AnnotationConfigApplicationContext(
MySpringApplication.class)){
MessageComponentmessageComponent=context
.getBean(MessageComponent.class);
System.out.println(messageComponent.getMessage());
}
}
}
ThebeanMessageComponentisdefinedinthefollowingclass.Noticethatitis
declaredastheSpringcomponentsimplyusingtheannotation@Componentatclasslevel.Then,inthisexample,weareinjectinganotherSpringcomponentcalledMessageServiceusingtheclassconstructor:
packageio.github.bonigarcia;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Component;
@Component
publicclassMessageComponent{
privateMessageServicemessageService;
publicMessageComponent(MessageServicemessageService){
this.messageService=messageService;
}
publicStringgetMessage(){
returnmessageService.getMessage();
}
}
Atthispoint,itisworthreviewingthedifferentmannerstocarryoutdependencyinjectionofSpringcomponents:
1. Fieldinjection:Theinjectedcomponentisaclassfieldannotatedwith@Autowired,justliketheexamplebefore.Asabenefit,thiskindofinjectionremovescluttercodesuchassettermethodsorconstructorparameters.
2. Setterinjection:Theinjectedcomponentisdeclaredasafieldintheclass,andthenasetterforthisfieldiscreatedandannotatedwith@Autowired.
3. Constructorinjection:Thedependencyisinjectedintheclassconstructor,whichisannotatedwith@Autowired(3-ainthediagramhere).Thisisthewayshownintheexampleearlier.AsofSpring4.3,itisnotrequiredanymoretoannotatetheconstructorwith@Autowiredtocarryouttheinjection(3-b).
Thelatestwayofinjection(3-b)wasseveralbenefits,suchasthepromotionoftestabilitywithouttheneedofreflectionmechanism(implemented,forexample,bymockinglibrary).Inaddition,itcanmakedeveloperstothinkoverthedesignoftheclass,sincemanyinjecteddependenciessupposemanyconstructorparameters,andthisshouldbeavoided(Godobjectanti-pattern).
Differentwaysofdependencyinjection(Autowired)inSpring
ThelastcomponentinourexampleisnamedMessageService.NotethatisalsoaSpringcomponent,thistimeannotatedwith@Servicetoremarkitsservicenature(fromafunctionalperspective,itwouldbethesamethanannotatingtheclasswith@Component):
packageio.github.bonigarcia;
importorg.springframework.stereotype.Service;
@Service
publicclassMessageService{
publicStringgetMessage(){
return"Helloworld!";
}
}
Now,ifweexecutethemainclassofthisexample(calledMySpringApplication,seethesourcecodehere),wecreateanannotation-basedapplicationcontextwithatrywithresources(thiswaytheapplicationcontextwillbeclosedattheend).TheSpringIoCcontainerwillcreatetwobeans:MessageServiceandMessageComponet.Usingtheapplicationcontext,weseekthebeanMessageComponetandinvokeitsmethodgetMessage,whichisfinallywritteninthestandardoutput:
packageio.github.bonigarcia;
importorg.springframework.context.annotation.AnnotationConfigApplicationContext;
importorg.springframework.context.annotation.ComponentScan;
@ComponentScan
publicclassMySpringApplication{
publicstaticvoidmain(String[]args){
try(AnnotationConfigApplicationContextcontext=new
AnnotationConfigApplicationContext(
MySpringApplication.class)){
MessageComponentmessageComponent=context
.getBean(MessageComponent.class);
System.out.println(messageComponent.getMessage());
}
}
}
SpringmodulesTheSpringframeworkismodular,allowingdeveloperstouseonlytheneededmodulesprovidedbytheframework.Thecompletelistofthismodulescanbefoundonhttps://spring.io/projects.Thefollowingtablesummarizessomeofthemostimportantones:
Springproject Logo Description
SpringFramework
ProvidescoresupportforDI,transactionmanagement,webapplications(SpringMCV),dataaccess,messaging,andsoon.
SpringIOPlatform
BringstogetherthecoreSpringAPIsintoacohesiveandversionedfoundationalplatformformodernapplications.
SpringBoot
Simplifiesthecreationofstandalone,production-gradeSpring-basedapplicationswiththeminimalconfiguration.Itfollowstheconvention-over-configurationapproach.
SpringData
SimplifiesdataaccessbymeansofcomprehensiveAPIstoworkwiththerelationaldatabases,NoSQL,map-reducealgorithms,andsoon.
SpringCloud
Providesasetoflibrariesandcommonpatternsforbuildinganddeployingdistributedsystemsandmicroservices.
SpringSecurity
ProvidescustomizableauthenticationandauthorizationcapabilitiesforSpring-basedapplications.
SpringIntegration
Providesalightweight,POJO-basedmessagingforSpring-basedapplicationstointegratewithexternalsystems.
SpringBatch
Providesalightweightframeworkdesignedtoenablethedevelopmentofrobustbatchapplicationsforoperationsofenterprisesystems.
IntroductiontoSpringTestSpringamodulecalledspring-test,whichsupportsunitandintegrationtestingofSpringcomponents.Amongotherfeatures,thismoduleprovidestheabilitytocreateSpringapplicationcontextfortestingpurposesorcreatemockobjectsthattotestourcodeinisolation.Therearedifferentannotationssupportingthistestingcapabilities.Alistofthemostsignificantoneisthefollowing:
@ContextConfiguration:ThisannotationisusedtodeterminehowtoloadandconfigureanApplicationContextforintegrationtests.Forexample,itallowstoloadtheapplicationcontextfromannotatedclasses(usingtheelementclasses)orbeandefinitionsdeclaredinXMLfiles(usingtheelementlocations).@ActiveProfiles:Thisannotationisusedtoinstructthecontaineraboutwhichdefinitionprofilesshouldbeactiveduringtheapplicationcontextloading(forexample,developmentandtestprofiles).@TestPropertySource:Thisannotationisusedtoconfigurethelocationsofthepropertiesfilesandtheinlinepropertiestobeadded.@WebAppConfiguration:ThisannotationisusedtoinstructtheSpringcontextthatApplicationContextloadedisWebApplicationContext.
Inaddition,thespring-testmoduleoffersseveralcapabilitiestocarryoutdifferentactionstypicallyrequiredintests,namely:
Theorg.springframework.mock.webpackagecontainsasetofServletAPImockobjects,usefulfortestingwebcontexts.Forinstance,theobjectMockMvcallowstoperformHTTPrequests(POST,GET,PUT,DELETE,andsoon)andverifytheresponse(statuscode,contenttype,orresponsebody).Theorg.springframework.mock.jndipackagecontainsanimplementationoftheJavaNamingandDirectoryInterface(JNDI)SPI,whichcanbeusedtosetupasimpleJNDIenvironmentfortests.Forinstance,usingtheclassSimpleNamingContextBuilderwecanmakeaJNDIdatasourceavailableinourtests.Theorg.springframework.test.jdbcpackagecontainstheclassJdbcTestUtils,whichisacollectionofJDBCutilityfunctionsaimedtosimplifystandarddatabaseaccess.Theorg.springframework.test.utilpackagecontainstheclass
ReflectionTestUtils,whichisacollectionofutilitymethodstosetanon-publicfieldorinvokeaprivate/protectedsettermethodwhentestingtheapplicationcode.
TestingSpringBootapplicationsAsintroducedbefore,SpringBootisaprojectoftheSpringportfolioaimedtosimplifythedevelopmentofSpringapplications.ThemainbenefitsofusingSpringBootaresummarizedasfollows:
ASpringBootapplicationisjustaSpringApplicationContextinwhichtheprincipalconventionoverconfigurationisused.Thanktothis,itisfastertogetstartedwiththeSpringdevelopment.Theannotation@SpringBootApplicationisusedtoidentifythemainclassinaSpringBootproject.Arangeofnon-functionalfeaturesareprovidedoutofthebox:embeddedservletcontainers(Tomcat,Jetty,andUndertow),security,metrics,healthchecks,orexternalizedconfiguration.Acreationofstandalonerunningapplicationsthatjustrunusingthecommandjava-jar(evenforwebapplications).SpringBootcommandlineinterface(CLI)allowstorunGroovyscriptsforquicklyprototypingwithSpring.SpringBootworksinthesamewayasanystandardJavalibrary,thatis,touseit,wesimplyneedtoaddtheappropriatespring-boot-*.jarinourprojectclasspath(typicallyusingbuildtoolssuchasMavenorGradle).SpringBootprovidesanumberofstartersaimedtoeasetheprocessofaddingthedifferentlibrariestotheclasspath.Thefollowingtablecontainsseveralofthosestarters:
Name Description
spring-boot-
starter Corestarter,includingauto-configurationsupportandlogging
spring-boot-
starter-batch StarterforusingSpringBatch
spring-boot-
starter-cloud-
connectors
StarterforusingSpringCloudConnectors,whichsimplifiesconnectingtoservicesinCloudplatformslikeCloudFoundryandHeroku
spring-boot-
starter-data-jpa StarterforusingSpringDataJPAwithHibernate
spring-boot-
starter-
integration
StarterforusingSpringIntegration
spring-boot-
starter-jdbc StarterforusingJDBCwiththeTomcatJDBCconnectionpool
spring-boot-
starter-test
StarterfortestingSpringBootapplicationswithlibraries,includingJUnit,Hamcrest,andMockito
spring-boot-
starter-
thymeleaf
StarterforbuildingMVCwebapplicationsusingThymeleafviews
spring-boot-
starter-web
Starterforbuildingweb,includingREST,applicationsusingSpringMVC.UsesTomcatasthedefaultembeddedcontainer
spring-boot-
starter-
websocket
StarterforbuildingWebSocketapplicationsusingSpringFramework’sWebSocketsupport
ForcompleteinformationaboutSpringBootvisittheofficialreference:https://projects.spring.io/spring-boot/.
SpringBootprovidesdifferentcapabilitiestosimplifythetests.Forinstance,itprovidesthe@SpringBootTestannotation,whichisusedatclasslevelintestclasses.ThisannotationwillcreateApplicationContextforthesetests(similarlyto@ContextConfigurationbutforSpringBootbasedapplications).Aswehaveseeninthesectionbefore,inthespring-testmodule,weusetheannotation@ContextConfiguration(classes=…)tospecify,whichbeandefinition(Spring@Configuration)tobeloaded.WhentestingSpringBootapplicationsthisisoftennotrequired.SpringBoot’stestsannotationswillsearchtheprimaryconfigurationautomaticallyifnotexplicitlydefineone.Thesearchalgorithmworksupfromthepackagethatcontainsthetestuntilitfindsa@SpringBootApplicationannotatedclass.
SpringBootalsofacilitatestheuseofmocksforSpringcomponents.Tothat,theannotation@MockBeanisprovided.ThisannotationallowsdefiningaMockitomockforabeaninsideourApplicationContext.Itcanbenewbeans,butalsotoitcanreplaceasingleexistingbeandefinition.Mockbeansareautomaticallyresetaftereachtestmethod.Thismethodisusuallyknownasin-containertesting,incounterparttoout-of-container,inwhichamocklibrary(example,Mockito)isusedtounittesttheSpringcomponentsinisolationandwithouttheneedofaSpringApplicationContext.ForexampleofbothtypesofunittestsforSpringapplicationsisshowninthenextsection.
JUnit5extensionforSpringInordertointegratethespring-testcapabilitiesintoJUnit5’sJupiterprogrammingmodel,SpringExtensionhasbeendeveloped.Thisextensionispartofthespring-testmodule,asofSpring5.Let’sseeseveralexamplesofJUnit5andSpring5together.
Let’ssupposewewanttomakeanintegrationin-containertestoftheSpringapplicationdescribedintheformersection,madeupofthreeclasses:MySpringApplication,MessageComponent,andMessageService.Aswehavelearned,inordertoimplementaJupitertestagainstthisapplication,weneedtomakethefollowingsteps:
1. Annotateourtestclasswith@ContextConfigurationtospecifywhichApplicationContextneedstobeloaded.
2. Annotateourtestclasswith@ExtendWith(SpringExtension.class)toenablespring-testintoJupiter.
3. InjecttheSpringcomponentwewanttoassessinourtestclass.4. Implementourtest(@Test).
Forexample:packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.test.context.ContextConfiguration;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes={MySpringApplication.class})
classSimpleSpringTest{
@Autowired
publicMessageComponentmessageComponent;
@Test
publicvoidtest(){
assertEquals("Helloworld!",messageComponent.getMessage());
}
}
ThisisaverysimpleexampleinwhichtheSpringcomponentcalledMessageComponentisassessed.Whenthistestisstarted,ourApplicationContextisinitiatedwithandallourSpringcomponentsinside.Afterthat,inthis
example,thebeanMessageComponentisinjectedinthetest,whichisassessedsimplycallingthemethodgetMessage()andverifyingitsresponse.
Itisworthtoreviewwhichdependenciesareneededforthistest.WhenusingMaven,thesedependenciesarethefollowing:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Ontheotherside,ifweuseGradle,thedependenciesclausewouldbeasfollows:
dependencies{
compile("org.springframework:spring-context:${springVersion}")
testCompile("org.springframework:spring-test:${springVersion}")
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
Notethatinbothcasesthespring-contextdependencyisneededtoimplementtheapplication,andthenweneedspring-testandjunit-jupitertotestit.Inordertoimplementtheequivalentapplicationandtest,butthistimeusingSpringBoot,firstwewouldneedtochangeourpom.xml(whenusingMaven):
<projectxmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.bonigarcia</groupId>
<artifactId>junit5-spring-boot</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M3</version>
</parent>
<properties>
<junit.jupiter.version>5.0.0</junit.jupiter.version>
<junit.platform.version>1.0.0</junit.platform.version>
<java.version>1.8</java.version>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.source>${java.version}</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<url>https://repo.spring.io/libs-milestone</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
</pluginRepositories>
</project>
Orourbuild.gradle(whenusingGradle):buildscript{
ext{
springBootVersion='2.0.0.M3'
junitPlatformVersion='1.0.0'
}
repositories{
mavenCentral()
maven{
url'https://repo.spring.io/milestone'
}
}
dependencies{
classpath("org.springframework.boot:spring-boot-gradle-
plugin:${springBootVersion}")
classpath("org.junit.platform:junit-platform-gradle-
plugin:${junitPlatformVersion}")
}
}
repositories{
mavenCentral()
maven{
url'https://repo.spring.io/libs-milestone'
}
}
applyplugin:'java'
applyplugin:'eclipse'
applyplugin:'idea'
applyplugin:'org.springframework.boot'
applyplugin:'io.spring.dependency-management'
applyplugin:'org.junit.platform.gradle.plugin'
jar{
baseName='junit5-spring-boot'
version='1.0.0'
}
compileTestJava{
sourceCompatibility=1.8
targetCompatibility=1.8
options.compilerArgs+='-parameters'
}
dependencies{
compile('org.springframework.boot:spring-boot-starter')
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
InordertotransformourrawSpringapplicationintoSpringBoot,ourcomponents(intheexamplecalledMessageComponentandMessageService)wouldbeexactlythesame,butourmainclasswouldchangeabit(seehere).Noticethatweusetheannotation@SpringBootApplicationatclasslevel,implementingthemainmethodwiththetypicallybootstrappingmechanismofSpringBoot.Justforloggingpurposes,weareimplementingamethodannotatedwith@PostConstruct.Thismethodwillbetriggeredjustbeforetheapplicationcontextisstarted:
packageio.github.bonigarcia;
importjavax.annotation.PostConstruct;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
publicclassMySpringBootApplication{
finalLoggerlog=LoggerFactory.getLogger(MySpringBootApplication.class);
@Autowired
publicMessageComponentmessageComponent;
@PostConstruct
privatevoidsetup(){
log.info("***{}***",messageComponent.getMessage());
}
publicstaticvoidmain(String[]args)throwsException{
newSpringApplication(MySpringBootApplication.class).run(args);
}
}
Theimplementationofthetestwouldbestraightforward.Theonlychangeweneedtodoistoannotatethetestwith@SpringBootTestinsteadof@ContextConfiguration(SpringBootautomaticallylooksforandstartsourApplicationContext):
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest
classSimpleSpringBootTest{
@Autowired
publicMessageComponentmessagePrinter;
@Test
publicvoidtest(){
assertEquals("Helloworld!",messagePrinter.getMessage());
}
}
Executingthetestintheconsole,wecanseethatactuallytheapplicationisstartedbeforethetest(noticetheunmistakablespringASCIIbanneratthebeginning).
Afterthat,ourtestusestheApplicationContexttoverifyoneSpringcomponent,andasaresultthetestissucceeded:
ExecutionoftestusingSpringBoot
Tofinishwiththispart,weseeasimplewebapplicationimplementedwithSpringBoot.Withrespecttothedependencies,theonlychangeweneedtodoistoincludethestartedspring-boot-starter-web(insteadofthegenericspring-boot-starter).That’sit,wecanstartimplementingourSpring-basedwebapplication.
Wearegoingtoimplementaverysimple@Controller,thatis,theSpringbean,whichhandlestherequestfromthebrowsers.Inourexample,theonlyURLmappedbythecontrolleristhedefaultresource/:
packageio.github.bonigarcia;
importstaticorg.springframework.web.bind.annotation.RequestMethod.GET;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.stereotype.Controller;
importorg.springframework.web.bind.annotation.RequestMapping;
@Controller
publicclassWebController{
@Autowired
privatePageServicepageService;
@RequestMapping(value="/",method=GET)
publicStringgreeting(){
returnpageService.getPage();
}
}
ThiscomponentinjectsaservicecalledPageService,responsibleofreturningtheactualpagetobeloadedinresponsetotherequestto/.Thecontentofthisserviceisalsoverysimple:
packageio.github.bonigarcia;
importorg.springframework.stereotype.Service;
@Service
publicclassPageService{
publicStringgetPage(){
return"/index.html";
}
}
Byconvention(weareusingSpringBoothere),thestaticresourceforSpring-basedwebapplicationsarelocatedinafoldercalledstaticwithintheprojectclasspath.FollowingthestructureofMaven/Gradleproject,thisfolderislocatedinthesrc/main/resourcespath(seescreenshotbelow).Notethattherearetwopagesthere(weswitchfromonetotheotherinthetests,staytuned):
Contentoftheexampleprojectjunit5-spring-boot-web
Let’smoveonnottheinterestingpart:thetests.WeareimplementingthreeJupitertestsinthisproject.Thefirstoneisdevotedtoverifyadirectcalltothepage/index.html.Asdepictedbefore,thistestneedstousetheSpringextension(@ExtendWith(SpringExtension.class))andbedeclaredasSpringBoottest(@SpringBootTest).Tocarryouttherequesttowebapplication,weuseaninstanceoftheMockMvc,verifyingtheresponseinseveralways(HTTPresponsecode,content-type,andresponsecontentbody).ThisinstanceisautomaticallyconfiguredusingtheSpringBootannotation@AutoConfigureMockMvc.
OutofSpringBoot,insteadofusing@AutoConfigureMockMvc,theobjectMockMvccanbecreatedusingabuilderclasscalledMockMvcBuilders.Inthiscase,theapplicationcontextisusedasparameterforthatbuilder.
packageio.github.bonigarcia;
importstaticorg.hamcrest.core.StringContains.containsString;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
importorg.springframework.test.web.servlet.MockMvc;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
classIndexTest{
@Autowired
MockMvcmockMvc;
@Test
voidtestIndex()throwsException{
mockMvc.perform(get("/index.html")).andExpect(status().isOk())
.andExpect(content().contentType("text/html")).andExpect(
content().string(containsString("Thisisindex
page")));
}
}
Again,runningthistestintheshell,wecheckthattheapplicationisactuallyexecuted.Bydefault,theembeddedTomcatlistenstheport8080.Afterthat,testisexecutedsuccessfully:
Consoleoutputofin-containerfirsttest
Secondtestissimilar,butasadifferentialfactoritusesthetestcapability@MockBeantooverrideaspringcomponent(inthisexample,PageService)byamock.Inthebodyofthetest,firstwestubthemethodgetPageofthemocktochangethedefaultresponseofthecomponenttoredirect:/page.html.Asaresult,whenrequestingtheresource/inthetestwiththeobjectMockMvc,wewillobtainanHTTP302response(redirect)totheresource/page.html(whichisactuallyanexistingpage,asshownintheprojectscreenshot):
packageio.github.bonigarcia;
importstaticorg.mockito.Mockito.doReturn;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.boot.test.mock.mockito.MockBean;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
importorg.springframework.test.web.servlet.MockMvc;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
classRedirectTest{
@MockBean
PageServicepageService;
@Autowired
MockMvcmockMvc;
@Test
voidtest()throwsException{
doReturn("redirect:/page.html").when(pageService).getPage();
mockMvc.perform(get("/")).andExpect(status().isFound())
.andExpect(redirectedUrl("/page.html"));
}
}
Similarly,intheshellwecanconfirmthattheteststartstheSpringapplicationandthenitisexecutedcorrectly:
Consoleoutputofin-containersecondtest
Thelasttestinthisprojectisanexampleofanout-of-containertest.Intheprevioustestexamples,theSpringcontextwasusedwithinthetest.Ontheotherside,thefollowingreliescompletelyinMockitotoexercisethecomponentsofthesystem,thistimewithoutstartingtheSpringapplicationcontext.NotethatweareusingtheMockitoExtensionextensionhere,usingthecomponentWebControllerasourSUT(@InjectMocks)andthecomponentPageServiceasDOC(@Mock):
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importstaticorg.mockito.Mockito.times;
importstaticorg.mockito.Mockito.verify;
importstaticorg.mockito.Mockito.when;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
classOutOfContainerTest{
@InjectMocks
privateWebControllerwebController;
@Mock
privatePageServicepageService;
@Test
voidtest(){
when(pageService.getPage()).thenReturn("/my-page.html");
assertEquals("/my-page.html",webController.greeting());
verify(pageService,times(1)).getPage();
}
}
Thistime,intheexecutionofthetest,wedonotseespringtracessincetheapplicationcontainerwasnotstartedbeforeexecutingthetest:
Consoleoutputofout-of-containertest
SeleniumSelenium(http://www.seleniumhq.org/)isanopensourcewebtestingframework,sinceitsinceptionin2008hasestablisheditselfasthedefactowebautomationlibrary.Inthenextsection,wearegoingtoreviewthemainfeaturesofSeleniumandhowtouseitfromJUnit5tests.
SeleniuminanutshellSeleniumiscomposedbydifferentprojects.First,wefoundtheSeleniumIDE.ItisaFirefoxpluginimplementingtheRecordandPlayback(R&P)patternforwebapplications.Thus,itallowstorecordmanualinteractionswithFirefoxandtheplaybackthatrecordinginanautomatedfashion.
ThesecondprojectwasnamedSeleniumRemoteControl(RC).Thiscomponentwascapableofdrivingdifferenttypesofbrowserautomaticallyusingdifferentprogramminglanguages,suchasJava,C#,Python,Ruby,PHP,Perl,orJavaScript.ThiscomponentinjectedaJavaScriptlibrary(calledSeleniumCore)intheSUT.ThislibrarywascontrolledwithanintermediatecomponentcalledSeleniumRCServerwhichreceivesrequestsfromthetestcode(seethefollowingfigure).SeleniumRChadimportantsecurityproblemsduetosame-originpolicy.
Forthatreason,itwasdeprecatedon2016infavorofSeleniumWebDriver:
SeleniumRCschema
WereviewSeleniumRCjusttointroduceSeleniumWebDriver.Nowadays,SeleniumRCisdeprecatedanditsuseishighlydiscouraged.
Fromafunctionalpointofview,SeleniumWebDriverisequivalenttoRC(thatis,allowstocontrolbrowsersusingcode).Asadifferentialaspect,SeleniumWebDrivermakescallstothebrowserusingeachbrowser’snativesupportforautomation.ThelanguagebindingsprovidedbySeleniumWebDriver(labeledasTestinnextfigure)communicateswithandabrowser-specificbinary,whichactsasabridgebetweenrealbrowser.Forinstance,thisbinaryiscalledchromedriver(https://sites.google.com/a/chromium.org/chromedriver/)forChromeandgeckodriver(https://github.com/mozilla/geckodriver)forFirefox.The
communicationbetweentheTestandthedriverisdonewithJSONmessagesoverHTTPusingtheso-calledJSONWireProtocol.
Thismechanism,originallyproposedbytheWebDriverteamisstandardizedintheW3CWebDriverAPI(https://www.w3.org/TR/webdriver/):
SeleniumWebDriverschema
ThelastprojectoftheSeleniumportfolioiscalledSeleniumGrid.ItcanbeseenasextensionofSeleniumWebDriver,sinceitallowsdistributingbrowserexecutiononremotemachines.ThereareanumberofNodes,eachrunningondifferentoperatingsystemsandwithdifferentbrowsers.TheHubserverkeepsatrackofthenodesandproxiesrequeststothem(seefigurebelow):
SeleniumGridschema
ThefollowingtablesummarizesthemainfeaturesoftheWebDriverAPI:
WebDriverfeatureanddescription Example
WebDriverobjectcreation:
ItallowstocreateWebDriverinstances,whichareusedfromthetestcodetocontrolabrowserremotely.
WebDriverdriver=newFirefoxDriver();
WebDriverdriver=newChromeDriver();
WebDriverdriver=newOperaDriver();
Navigation:
ItallowstonavigatetoagivenURL.
driver.get("http://junit.org/junit5/");
Locateelements:
Itallowstoidentifyelementswithawebpage(WebElement)usingdifferentstrategies:byid,name,classname,CSSselector,linktext,tagname,orXPath
WebElementwebElement=
driver.findElement(By.id("id"));
driver.findElement(By.name("name"));
driver.findElement(By.className("class"));
driver.findElement(By.cssSelector("cssInput"));
driver.findElement(By.linkText("text"));
driver.findElement(By.tagName("tagname"));
driver.findElement(By.xpath("/html/body/div[4]"));
Interactwithelements:
FromagivenWebElement,wecancarryoutdifferenttypesofautomatedinteraction,suchasclickelements,typetextorclearinputfields,readattributes,andsoon.
webElement.click();
webElement.sendKeys("text");
webElement.clear();
Stringtext=webElement.getText();
Stringhref=webElement.getAttribute("href");
Stringcss=webElement.getCssValue("css");
Dimensiondim=webElement.getSize();
booleanenabled=webElement.isEnabled();
booleanselected=webElement.isSelected();
booleandisplayed=webElement.isDisplayed();
Handlewaits:
WebDrivercanhandlewaitbothexplicitandimplicitly.
//Explicit
WebDriverWaitwait=newWebDriverWait(driver,
30);
wait.until(ExpectedConditions);
//Implicitwait
driver.manage().timeouts().implicitlyWait(30,
SECONDS);
XPath(XMLPathLanguage)isalanguagetobuildexpressionstoparseandprocessXML-likedocuments(forexample,HTML)
JUnit5extensionforSeleniumInordertosimplifytheuseofSeleniumWebDriverinJUnit5,theopensourceJUnit5extensioncalledselenium-jupitercanbeused.ThisextensionhasbeenbuiltusingthedependencyinjectioncapabilityprovidedbytheextensionmodelofJUnit5.Thankstothisfeature,differenttypesobjectscanbeinjectedinJUnit5in@Testmethodsasparameters.Concretely,selenium-jupiterallowstoinjectsubtypesoftheWebDriverinterface(forexample,ChromeDriver,FirefoxDriver,andsoon).
Usingselenium-jupiterisveryeasy.First,weneedtoimportthedependencyinourproject(typicallyastestdependency).InMaven,itisdoneasfollows:
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>selenium-jupiter</artifactId>
<version>${selenium-jupiter.version}</version>
<scope>test</scope>
</dependency>
selenium-jupiterdependsonseverallibraries,whichareaddedinourprojectastransitivedependencies,namely:
Selenium-java(org.seleniumhq.selenium:selenium-java):JavalibraryforSeleniumWebDriver.WebDriverManager(io.github.bonigarcia:webdrivermanager):JavalibraryforautomaticSeleniumWebDriverbinariesmanagementinruntimeforJava(https://github.com/bonigarcia/webdrivermanager).Appium(io.appium:java-client):JavaclientforAppium,testingframeworkthatextendsSeleniumtoautomatetestingofnative,hybrid,andmobilewebapps(http://appium.io/).
Onceselenium-jupiterisincludedinourproject,weneedtodeclareselenium-jupiterextensioninourJUnit5test,simplyannotatingitwith@ExtendWith(SeleniumExtension.class).Then,weneedtoincludeoneormoreparametersinour@TestmethodswhosetypesimplementtheWebDriverinterface,andselenium-jupitercontrolthelifecycleoftheWebDriverobjectinternally.HeWebDriversubtypessupportedbyselenium-jupiterarethefollowing:
ChromeDriver:ThisisusedtocontrolGoogleChromebrowser.FirefoxDriver:ThisisusedtocontrolFirefoxbrowser.
EdgeDriver:ThisisusedtocontrolMicrosoftEdgebrowser.OperaDriver:ThisisusedtocontrolOperabrowser.SafariDriver:ThisisusedtocontrolAppleSafaribrowser(onlypossibleinOSXElCapitanorgreater).HtmlUnitDriver:ThisisusedtocontrolHtmlUnit(headlessbrowser,thatis,abrowserwithoutGUI).PhantomJSDriver:ThisisusedtocontrolPhantomJS(anotherheadlessbrowser).InternetExplorerDriver:ThisisusedtocontrolMicrosoftInternetExplorer.Althoughthisbrowserissupported,InternetExplorerisdeprecated(infavorofEdge)anditsuseishighlydiscouraged.RemoteWebDriver:Thisisusedtocontrolremotebrowsers(SeleniumGrid).AppiumDriver:Thisisusedtocontrolmobiledevices(AndroidandiOS).
Considerthefollowingclass,whichusesselenium-jupiter,thatis,declaringtheSeleniumextensionusing@ExtendWith(SeleniumExtension.class).Thisexampledefinesthreetests,whicharegoingbeexecutedusinglocalbrowsers.Firstone(namedtestWithChrome)usesChromeasbrowsers.Tothataim,andthankstothedependencyinjectionfeatureofselenium-jupiter,themethodsimplyneedstodeclareamethodargumentusingthetypeChromeDriver.Then,inthebodyofthetest,theWebDriverAPIisinvokedinthatobject.Notethatthistestsimpleopensawebpageandassertsthatthetitleisasexpected.Next,test(testWithFirefoxAndOpera)issimilar,butthistimeusingtwodifferentbrowsersatthesametime:Firefox(usinganinstanceofFirefoxDriver)andOpera(usinganinstanceofOperaDriver).Thethirdandlasttest(testWithHeadlessBrowsers)declaresandusestwoheadlessbrowsers(HtmlUnitandPhantomJS):
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertNotNull;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.openqa.selenium.chrome.ChromeDriver;
importorg.openqa.selenium.firefox.FirefoxDriver;
importorg.openqa.selenium.htmlunit.HtmlUnitDriver;
importorg.openqa.selenium.opera.OperaDriver;
importorg.openqa.selenium.phantomjs.PhantomJSDriver;
@ExtendWith(SeleniumExtension.class)
publicclassLocalWebDriverTest{
@Test
publicvoidtestWithChrome(ChromeDriverchrome){
chrome.get("https://bonigarcia.github.io/selenium-jupiter/");
assertTrue(chrome.getTitle().startsWith("selenium-jupiter"));
}
@Test
publicvoidtestWithFirefoxAndOpera(FirefoxDriverfirefox,
OperaDriveropera){
firefox.get("http://www.seleniumhq.org/");
opera.get("http://junit.org/junit5/");
assertTrue(firefox.getTitle().startsWith("Selenium"));
assertTrue(opera.getTitle().equals("JUnit5"));
}
@Test
publicvoidtestWithHeadlessBrowsers(HtmlUnitDriverhtmlUnit,
PhantomJSDriverphantomjs){
htmlUnit.get("https://bonigarcia.github.io/selenium-jupiter/");
phantomjs.get("https://bonigarcia.github.io/selenium-jupiter/");
assertTrue(htmlUnit.getTitle().contains("JUnit5extension"));
assertNotNull(phantomjs.getPageSource());
}
}
Inordertoexecuteproperlythistestclass,therequiredbrowsers(Chrome,Firefox,andOpera)shouldbeinstalledbeforehandrunningit.Ontheotherhand,theheadlessbrowsers(HtmlUnitandPhantomJS)areconsumedasJavadependencies,andsothereisnoneedtoinstallthemmanually.
Let’sseeanotherexample,thistimeusingremotebrowsers(thatis,SeleniumGrid).Again,thisclassusestheselenium-jupiterextension.Thetest(testWithRemoteChrome)declaresasingleparametercalledremoteChrome,oftypeRemoteWedbrider.Thisargumentisannotatedwith@DriverUrland@DriverCapabilities,specifyingtheSeleniumServer(orHub)URLandtherequiredcapabilitiesrespectively.Regardingthecapabilities,weareconfiguringtouseaChromebrowserversion59:
Torunthistestproperly,aSeleniumServershouldupandrunninginthelocalhost,andanode(Chrome59)needstoberegisteredintheHub.
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.openqa.selenium.remote.RemoteWebDriver;
@ExtendWith(SeleniumExtension.class)
publicclassRemoteWebDriverTest{
@Test
voidtestWithRemoteChrome(
@DriverUrl("http://localhost:4444/wd/hub")
@DriverCapabilities(capability={
@Capability(name="browserName",value="chrome"),
@Capability(name="version",value="59")})
RemoteWebDriverremoteChrome)
throwsInterruptedException{
remoteChrome.get("https://bonigarcia.github.io/selenium-
jupiter/");
assertTrue(remoteChrome.getTitle().contains("JUnit5
extension"));
}
}
Inthelastexampleofthissection,weuseAppiumDriver.Concretely,wesetupascapabilitiestheuseofaChromebrowserinanAndroidemulateddevice(@DriverCapabilities).Again,thisemulatorneedstobeupandrunninginthemachinerunningthetestbeforehand:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertTrue;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.openqa.selenium.By;
importorg.openqa.selenium.WebElement;
importorg.openqa.selenium.remote.DesiredCapabilities;
importio.appium.java_client.AppiumDriver;
@ExtendWith(SeleniumExtension.class)
publicclassAppiumTest{
@DriverCapabilities
DesiredCapabilitiescapabilities=newDesiredCapabilities();
{
capabilities.setCapability("browserName","chrome");
capabilities.setCapability("deviceName","Android");
}
@Test
voidtestWithAndroid(AppiumDriver<WebElement>android){
Stringcontext=android.getContext();
android.context("NATIVE_APP");
android.findElement(By.id("com.android.chrome:id/terms_accept"))
.click();
android.findElement(By.id("com.android.chrome:id/negative_button"))
.click();
android.context(context);
android.get("https://bonigarcia.github.io/selenium-jupiter/");
assertTrue(android.getTitle().contains("JUnit5extension"));
}
}
Forfurtherexamplesofselenium-jupiter,visithttps://bonigarcia.github.io/selenium-jupiter/.
CucumberCucumber(https://cucumber.io/)istestingframeworkaimedtoautomateacceptancetestswrittenfollowingaBehavior-DrivenDevelopment(BDD)style.CucumberhasbeenwritteninRuby,althoughimplementationsforotherlanguages(includingJava,JavaScript,andPython)areavailable.
CucumberinanutshellCucumberexecutestestsspecifiedwritteninlanguagecalledGherkin.Itisaplaint-textnaturallanguage(forexample,Englishoroneofother60+languagessupportedbyCucumber)withagivenstructure.Gherkinhasbeendesignedtobeusedbynon-programmers,typicallycustomers,businessanalysis,managers,andsoon.
TheextensionforGherkinfilesis.feature.
InaGherkinfile,non-blanklinescanstartwithakeyword,followedbytextinnaturallanguage.Themainkeywordsarethefollowing:
Feature:High-leveldescriptionofthesoftwarefeaturetobetested.Itcanbeseenasausecasedescription.Scenario:Concreteexamplethatillustratesabusinessrule.Scenariosfollowthesamepattern:
Describeinitialcontext.Describeanevent.Describetheexpectedoutcome.
TheseactionsareknownintheGherkinjargonassteps,whicharemainlyGiven,When,orThen:
Therearetwoadditionalsteps:And(usedforlogicalandfordifferentsteps)andBut(usedinfornegativeformofAnd).
Given:Preconditionsandinitialstatebeforethestartofatest.When:Actionstakenbyauserduringatest.Then:OutcomefromactionstakenintheWhenclause.Background:Toavoidrepeatstepsindifferentscenarios,thekeywordbackgroundallowstodeclaredthesesteps,whicharereusedinsubsequentscenarios.ScenarioOutline:Scenariosinwhichstepsaremarkedwithvariables(usingthesymbols<and>).Examples:Ascenariooutlinedeclarationisalwaysfollowedbyoneormoreexamplessections,whichisacontainertablewithvaluesforthe
declaredvariablesintheScenarioOutline.
Whenonelinedoesnotstartwithakeyword,thatlineisnotinterpretedbyCucumber.Itisusedtocustomdescription.
Oncewedefinedourfeaturestobetestedweneedwhatitiscalledstepsdefinition,whichallowstotranslateplaintextGherkinintoactionsthatactuallyexerciseourSUT.InJava,itcanbeeasilydonebyannotationstoannotatemethodsforthestepimplementation:@Given,@Then,@When,@And,and@But.Thestringvalueofeachstepcancontainregularexpressionwhicharemappedasfieldsinthemethod.Seeanexampleinthenextsection.
JUnit5extensionforCucumberThelatestversionsoftheCucumberartifactsforJavaincorporatesaJUnit5extensionforCucumber.ThissectioncontainsacompleteexampleofafeaturedefinedinGherkinandtheJUnit5toexecuteitwithCucumber.Asusual,thesourcecodeofthisexampleishostedonGitHub(https://github.com/bonigarcia/mastering-junit5).
Thestructureoftheprojectcontainingthisexampleisasfollows:
JUnit5withCucumberprojectstructureandcontent
Firstofall,weneedtocreateourGherkinfile,whichisaimedtotestasimplecalculatorsystem.ThiscalculatorwillbetheSUTorourtest.Thecontentofourfeaturefileisasfollows:
Feature:BasicArithmetic
Background:ACalculator
GivenacalculatorIjustturnedon
Scenario:Addition
WhenIadd4and5
Thentheresultis9
Scenario:Substraction
WhenIsubstract7to2
Thentheresultis5
ScenarioOutline:Severaladditions
WhenIadd<a>and<b>
Thentheresultis<c>
Examples:Singledigits
|a|b|c|
|1|2|3|
|3|7|10|
Then,weneedtoimplementourstepsdefinition.Asdescribedearlier,weuseannotationsandregularexpressiontomapthetextcontainedintheGherkinfiletotheactualexerciseofSUTdependingonthestep:
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importcucumber.api.java.en.Given;
importcucumber.api.java.en.Then;
importcucumber.api.java.en.When;
publicclassCalculatorSteps{
privateCalculatorcalc;
@Given("^acalculatorIjustturnedon$")
publicvoidsetup(){
calc=newCalculator();
}
@When("^Iadd(\\d+)and(\\d+)$")
publicvoidadd(intarg1,intarg2){
calc.push(arg1);
calc.push(arg2);
calc.push("+");
}
@When("^Isubstract(\\d+)to(\\d+)$")
publicvoidsubstract(intarg1,intarg2){
calc.push(arg1);
calc.push(arg2);
calc.push("-");
}
@Then("^theresultis(\\d+)$")
publicvoidthe_result_is(doubleexpected){
assertEquals(expected,calc.value());
}
}
Ofcourse,westillneedtoimplementourJUnit5test.ToachievetheintegrationofCucumberandJUnit5,theCucumberextensionneedstoberegisteredinourclassbymeansof@ExtendWith(CucumberExtension.class).Internally,CucumberExtensionimplementstheParameterResolvercallbackoftheJupiterextensionmodel.TheobjectiveistoinjectthecorrespondingtestsoftheCucumberfeatureasJupiterDynamicTestobjectsinthetests.Noticeintheexamplehowa@TestFactoryisused.
Optionally,wecanannotateourtestclasswith@CucumberOptions.ThisannotationallowstoconfiguretheCucumbersettingsforourtest.Theallowedelementsforthisannotationare:
plugin:Built-informatter:pretty,progress,JSON,usage,amongothers.
Default:{}.dryRun:Checksifallstepshavedefinitions.Default:false.features:Pathsofthefeaturesfiles.Default:{}.glue:Pathsforstepdefinitions.Default:{}.tags:Tagsinthefeaturestobeexecuted.Default{}.monochrome:Displaysconsoleoutputinareadableway.Default:false.format:Reportsformattertobeused.Default:{}.strict:Failsifthereareundefinedorpendingsteps.Default:false.
packageio.github.bonigarcia;
importjava.util.List;
importjava.util.stream.Collectors;
importjava.util.stream.Stream;
importorg.junit.jupiter.api.DynamicTest;
importorg.junit.jupiter.api.TestFactory;
importorg.junit.jupiter.api.extension.ExtendWith;
importcucumber.api.CucumberOptions;
importcucumber.api.junit.jupiter.CucumberExtension;
@CucumberOptions(plugin={"pretty"})
@ExtendWith(CucumberExtension.class)
publicclassCucumberTest{
@TestFactory
publicStream<DynamicTest>runCukes(Stream<DynamicTest>scenarios){
List<DynamicTest>tests=scenarios.collect(Collectors.toList());
returntests.stream();
}
}
Atthispoint,weareabletoexecuteourCucumbersuitewithJUnit5.InthefollowingexampleweseetheoutputwhenrunningthetestwithGradle:
DockerDocker(https://www.docker.com/)isanopensourcesoftwaretechnology,whichallowstopackandrunanyapplicationasalightweightandportablecontainer.Itprovidesacommand-lineprogram,abackgrounddaemon,andasetofremoteservicesthatsimplifiesthelifecycleofcontainers.
DockerinanutshellHistorically,UNIX-styleoperatingsystemsusedthetermjailtodescribemodifiedisolatedruntimeenvironments.TheLinuxContainers(LXC)projectstartedin2008andbroughttogethercgroups,kernelnamespaces,orchroot(amongothers)toprovidecompleteisolationexecution.TheproblemwithLXCisthedifficulty,andforthatreason,theDockertechnologyemerged.
DockerhidesinunderlyingcomplexityoftheaforementionedresourceisolationfeaturesoftheLinuxkernel(cgroups,kernelnamespaces,andsoon)toallowindependentcontainerstorunwithinasingleLinuxinstance.Dockerprovidesahigh-levelAPI,whichallowstopack,shipandrunanyapplicationasacontainer.
InDocker,acontainercontainsanapplicationanditsdependenciestogether.MultiplecontainerscanrunonthesamemachineandsharethesameOSkernelwithothercontainers.Eachcontainerisrunningasisolatedprocessinuserspace.
Unlikevirtualmachines(VMs),inDockercontainersthereisnoneedofusingahypervisor,whichisthesoftwarethatallowstocreateandrunsVM(example;VirtualBox,VMware,QEMUorVirtualPC).
ThearchitectureofVMandcontaineraredepictedinthefollowingdiagram:
Virtualmachineversuscontainer
TheDockerplatformhastwocomponents:theDockerEngine,whichis
responsibleforcreatingandrunningcontainers;andtheDockerHub(https://hub.docker.com/),acloudservicefordistributingcontainers.TheDockerHubprovidesanenormousnumberofpubliccontainerimagesfordownload.TheDockerEngineisaclient-serverapplicationcomposedbythreemajorcomponents:
Aserverimplementedasadaemonprocess(thedockerdcommand).ARESTAPI,whichspecifiesinterfacesthatprogramscanusetotalktothedaemonandinstructitwhattodo.Acommandlineinterface(CLI)client(thedockercommand).
JUnit5extensionforDockerNowadays,containersarechangingthewaywedevelop,distribute,andrunsoftware.ThisisespeciallyinterestingforContinuousIntegration(CI)testingenvironment,inwhichtheconvergencewithDockerhasadirectimpactontheimprovementofefficiency.
RegardingJUnit5,atthemomentofthiswritingthereisanopensourceJUnit5extensionforDocker,namedJUnit5-Docker(https://faustxvi.github.io/junit5-docker/).ThisextensionactsasclientoftheDockerengineandallowstostartaDockercontainer(downloadedfromtheDockerHub),beforerunningthetestsofaclass.Thatcontainerisstoppedattheendofthetests.InordertouseJUnit5-Docker,firstweneedtoaddthedependencyinourproject.InMaven:
<dependency>
<groupId>com.github.faustxvi</groupId>
<artifactId>junit5-docker</artifactId>
<version>${junit5-docker.version}</version>
<scope>test</scope>
</dependency>
InGradle:dependencies{
testCompile("com.github.faustxvi:junit5-docker:${junitDockerVersion}")
}
TheuseofJUnit5-Dockerisquitestraightforward.Wesimplyneedtoannotateourtestclasswith@Docker.Theelementsavailableinthisannotationarethefollowing:
image:Dockerimagetobestarted.ports:PortmappingfortheDockercontainer.Thisisrequiredsinceatleastoneportmustbevisibleforthecontainertobeuseful.environments:Optionalenvironmentvariablestopasstothedockercontainer.Default:{}.waitFor:Optionallogtowaitforbeforerunningthetests.Default:@WaitFor(NOTHING).newForEachCase:Booleanflag,whichdeterminesifthecontainershouldberecreatedforeachtestcase.Thisvaluewillbefalseifitshouldbecreatedonlyonceforthetestclass.Default:true.
Considerthefollowingexample.Thistestclassusesthe@DockerannotationtostartaMySqlcontainer(containerimageMySQL)andthebeginningofeach
test.Theinternalcontainerportis3306,whichwillbemappedtothehostport8801.Then,severalenvironmentattributesaredefined(MySqlrootpassword,defaultdatabase,andusernameandpassword).Theexecutionofthetestwillnotstartuntilthetracemysqld:readyforconnectionsappearsinthecontainerlog(whichindicatesthattheMySqlinstanceisupandrunning).Inthebodyofthetest,westartaJDBCconnectionagainsttheMySQLinstancerunninginthecontainer.
ThistesthasbeenexecutedinaWindowsmachine.Forthatreason,thehostoftheJDBCURLis192.168.99.100,whichistheIPfortheDockerMachine.ItisatoolwhichallowstoinstallDockerEngineonvirtualhosts,suchasWindowsorMac(https://docs.docker.com/machine/).InaLinuxmachine,thisIPcouldbe127.0.0.1(localhost).
packageio.github.bonigarcia;
importstaticorg.junit.jupiter.api.Assertions.assertFalse;
importjava.sql.Connection;
importjava.sql.DriverManager;
importorg.junit.jupiter.api.Test;
importcom.github.junit5docker.Docker;
importcom.github.junit5docker.Environment;
importcom.github.junit5docker.Port;
importcom.github.junit5docker.WaitFor;
@Docker(image="mysql",ports=@Port(exposed=8801,inner=3306),environments
={
@Environment(key="MYSQL_ROOT_PASSWORD",value="root"),
@Environment(key="MYSQL_DATABASE",value="testdb"),
@Environment(key="MYSQL_USER",value="testuser"),
@Environment(key="MYSQL_PASSWORD",value="secret"),},
waitFor=@WaitFor("mysqld:readyforconnections"))
publicclassDockerTest{
@Test
voidtest()throwsException{
Class.forName("com.mysql.jdbc.Driver");
Connectionconnection=DriverManager.getConnection(
"jdbc:mysql://192.168.99.100:8801/testdb","testuser",
"secret");
assertFalse(connection.isClosed());
connection.close();
}
}
TheexecutionofthistestintheDockerWindowsterminalisasfollows:
AndroidAndroid(https://www.android.com/)isanopensourcemobileoperatingsystembasedonamodifiedversionofLinux.ItwasoriginallydevelopedbyastartupnamedAndroid,acquiredandchampionedbyGooglein2005.
AccordingtothereportbyGartnerInc.(AmericanITresearchandadvisorycompany),in2017AndroidandiOSaccountmorethan99%ofglobalsmartphonesales,asshowninthefollowingchart:
Smartphoneoperativesystemmarket.Picturecreatedbywww.statista.com.
AndroidinanutshellAndroidisaLinux-basedsoftwarestackdividedintoseverallayers.Thoselayers,fromdowntotoparethefollowing:
Linuxkernel:ThisisthefoundationoftheAndroidplatform.Thislayercontainsallthelow-leveldevicedriversforthevarioushardwarecomponentsofanAndroiddevice.HardwareAbstractionLayer(HAL):Thislayerprovidesstandardinterfacesthatexposehardwarecapabilitiestothehigher-levelJavaAPIframework.AndroidRuntime(ART):Itprovidesaruntimeenvironmentfor.dexfiles,abytecodeformatdesignedforminimalmemoryfootprint.ARTwasthefirstreleaseonAndroid5.0(seetablebelow).Priortothatversion,DalvikwastheAndroidruntime.NativeC/C++libraries:ThislayercontainsnativelibrarieswritteninCandC++,suchasOpenGLESforhigh-performance2Dand3Dgraphicsprocessing.JavaAPIframework:Theentirefeature-setofAndroidisavailablefordevelopersthroughAPIswritteninJava.TheseAPIsarethebuildingblockforcreatingAndroidapps,forinstance:theViewSystem(forappsUIs),theResourceManager(forI18N,graphics,layouts),theNotificationManager(forcustomalertsinthestatusbar),theActivityManager(tomanagetheappslifecycle),ortheContentProvider(toenableappsaccessdatafromotherapps,suchastheContacts,andsoon).Apps:Androidcomeswithasetofcoreapps,suchasPhone,Contacts,Browser,andsoon.Inaddition,manyothersappscanbedownloadedandinstalledfromGooglePlay(formerlyAndroidMarket):
Androidlayeredarchitecture
Androidhasgonethroughquiteanumberofupdatessinceitsfirstrelease,asdescribedinthefollowingtable:
Androidversion Codename API
levelLinuxkernelversion Releasedate
1.5 Cupcake 3 2.6.27April30,2009
1.6 Donut 4 2.6.29September15,2009
2.0,2.1
Eclair 5,6,7 2.6.29 October26,
2009
2.2 Froyo 8 2.6.32 May20,2010
2.3 Gingerbread 9,10 2.6.35December6,2010
3.0,3.1,3.2 Honeycomb11,12,13 2.6.36
February22,2011
4.0 IceCreamSandwich 14,15 3.0.1 October18,
2011
4.1,4.2,4.3 JellyBean 16,17,18
3.0.31,3.0.21,3.4.0 July9,2012
4.4
KitKat 19,20 3.10 October31,
2013
5.0,5.1 Lollipop 21,22 3.16.1 November12,2014
6.0
Marshmallow 23 3.18.10 October5,2015
7.0,7.1 Nougat 24,25 4.4.1 August22,2016
8.0 AndroidO 26 TBA TBA
Fromadeveloperpointofview,Androidprovidesarichapplicationframework,whichallowstobuildappsformobiledevices.AndroidappsarewrittenintheJavaprogramminglanguage.TheAndroidSoftwareDevelopmentKit(SDK)compileoutJavacodealongwithanydataandresourcefilesintoan.apk(Androidpackage)file,whichcontainscanbeinstalledinAndroid-powereddevices,suchassmartphones,tablets,smartTVs,orsmartwatches.
ForcompleteinformationaboutAndroiddevelopment,visithttps://developer.android.com/.
AndroidStudioistheofficialIDEforAndroiddevelopment.ItisbuiltbasedonIntelliJIDEA.InAndroidStudio,thebuildprocessofAndroidprojectsismanagedbytheGradlebuildsystem.DuringtheAndroidStudioinstallation,twoadditionaltoolscanbealsoinstalled:
AndroidSDK:ThiscontainsallofthepackagesandtoolsrequiredtodevelopAndroidapps.TheSDKManagerallowstodownloadandinstallSDKfordifferentversions(seetheprecedingtable).AndroidVirtualDevice(AVD):Thisisanemulatorthatallowsustomodelanactualdevice.TheAVDManagerallowstodownloadandinstalldifferentemulatedAndroidvirtualdevicesgroupedintofourcategories:phones,tables,TV,andwears.
GradlepluginforJUnit5inAndroidprojectsAtthetimeofthiswriting,thereisnoofficialsupportforJUnit5inAndroidprojects.Tosolvethisproblem,anopensourceGradlepluginnamedandroid-junit5hasbeencreated(https://github.com/aurae/android-junit5).Tousethisplugin,firstweneedtospecifytheproperdependencyinourbuild.gradlefile:
buildscript{
dependencies{
classpath"de.mannodermaus.gradle.plugins:android-junit5:1.0.0"
}
}
Inordertousethisplugininourproject,weneedtoextendourprojectcapabilitiesusingtheclauseapplyplugininourbuild.gradlefile:
applyplugin:"com.android.application"
applyplugin:"de.mannodermaus.android-junit5"
dependencies{
testCompilejunitJupiter()
}
Theandroid-junit5pluginconfiguresthejunitPlatformtask,attachingautomaticallyattachesboththeJupiterandVintageenginesduringthetestexecutionphase.Asanexample,considerthefollowingprojectexample,asusualhostedonGitHub(https://github.com/bonigarcia/mastering-junit5/tree/master/junit5-android).ThefollowingisascreenshotofthisprojectimportedinAndroidStudio:
AndroidprojectcompatiblewithJUnit5onIntelliJ
Now,wearegoingtocreateanAndroidJUnitrunconfigurationofAndroidStudio.Ascanbeseeninthescreenshot,weusetheoptionAllinpackagereferredtothepackagecontainingthetests(io.github.bonigarcia.myapplicationinthisexample):
AndroidJUnitrunconfiguration
Ifwelaunchtheaforementionedrunconfiguration,allthetestsoftheprojectwillbeexecuted.ThesetestscanusetheJUnit4programmingmodel(Vintage)andeventheJUnit5(Jupiter)inaseamlessway:
ExecutiononJupiterandVintagetestswithinanAndroidprojectinIntelliJ
RESTRoyFieldingisanAmericancomputerscientistbornin1965.HeisoneoftheauthorsoftheHTTPprotocolandtheco-authorsoftheApacheWebserver.Intheyear2000,FieldingcoinedthetermREST(shortforREpresentationalStateTransfer)inhisdoctoraldissertationentitledArchitecturalStylesandtheDesignofNetwork-basedSoftwareArchitecture.RESTisanarchitecturalstylefordesigningdistributedsystems.It’snotastandard,butratherasetofconstraints.RESTiscommonlyusedinconjunctionwithHTTP.Ontheonehand,theimplementationswhichfollowsthestrictprinciplesofRESTareoftenreferredasRESTful.Ontheotherhand,thosewhichfollowalooseadherenceofsuchprinciplesarecalledRESTlike.
RESTinanutshellRESTfollowsaclient-serverarchitecture.Theserverisinchargeofhandlingasetofservices,listeningforrequestsmadebyclients.Thecommunicationbetweenclientandservermustbestateless,meaningthatserverdonotstoreanyrecordfromtheclientsandthereforeeachrequestdonefromtheclientmustcontainalltheinformationrequiredfortheservertoprocessitseparately.
ThebuildingblocksofRESTarchitecturesarenamedresources.Resourcesdefinethetypeofinformationthatisgoingtobetransferred.Resourcesshouldbeidentifiedinauniqueway.InHTTP,thewaytoaccesstheresourceittoprovideitsfullURL,alsoknownasAPIendpoint.Eachresourcehasarepresentation,whichisamachine-readableexplanationofthecurrentstateofaresource.Nowadays,representationsareusuallywithJSON,butitcanbedoneinotherformatssuchasXMLorYAML.
Onceweidentifiedtheresourcesandtherepresentationformat,weneedtospecifywhatcanbedonewiththem,thatis,theactions.Actionscouldpotentiallybeanything,althoughthereisasetofcommonactionsthatanyresource-orientedsystemshouldprovide:CRUD(create,retrieve,update,anddelete)actions.RESTactionscanbemappedtotheHTTPmethods(so-calledverbs),asfollows:
GET:Readsaresource.POST:Sendsanewresourcetotheserver.PUT:Updatesagivenresource.DELETE:Deletesaresource.PATCH:updatepartiallyaresource.HEAD:Asksifagivenresourceexistswithoutreturninganyofitsrepresentations.OPTIONS:Retrievesalistofavailableverbsonagivenresource.
InREST,itisimportantthenotionofidempotency.Forexample,GET,DELETE,orPUTaresaidtobeidempotent,sincetheeffectoftheserequestsshouldbethesamewhetherthecommandissentoneorseveraltimes.Ontheotherhand,POSTisnotidempotent,sinceitcreatesadifferentresourceeachtimeitisrequested.
REST,whenbasedonHTTPcanbenefitonstandardHTTPstatuscodes.A
statuscodeisanumberthatsummarizestheresponseassociatedtoit.ThetypicalHTTPstatuscodereusedinRESTare:
200OK:Therequestwentfineandthecontentrequestedwasreturned.NormallyusedonGETrequests.201Created:Theresourcewascreated.UsefulonresponsestoPOSTorPUTrequests.204Nocontent:Theactionwassuccessful,butthereisnocontentreturned.Usefulforactionsthatdonotrequirearesponsebody,suchasaDELETE.301Movedpermanently:Thisresourcewasmovedtoanotherlocationandthelocationisreturned.400Badrequest:Therequestissuedhasproblems(forexample,lackingsomerequiredparameters).401Unauthorized:Usefulforauthenticationwhentherequestedresourceisnotaccessibletotheuserowningtherequest.403Forbidden:Theresourceisnotaccessible,butunlike401,authenticationwillnotaffecttheresponse.404Notfound:TheURLprovideddoesnotidentifyanyresource.405Methodnotallowed.TheHTTPverbusedonaresourceisnotallowed.(forexample,aPUTonaread-onlyresource).500Internalservererror:Agenericerrorcodewhenanunexpectedconditionintheserverside.
Thefollowingpictureshowsanexampleofclient-serverinteractionwithREST.ThebodyoftheHTTPmessagesusesJSONbothforrequestsandresponses:
UsingRESTtestlibrarieswithJupiterRESTAPIsarebecomingmoreandmorepervasivenowadays.Forthatreason,aproperstrategyforassessingRESTservicesisdesirable.Inthissection,wearegoingtolearnhowtouseseveraltestlibrariesinourJUnit5tests.
Firstofall,wecanusetheopensourcelibraryRESTAssured(http://rest-assured.io/).RESTAssuredallowsthevalidationofRESTservicesbymeansofafluentAPIinspiredindynamiclanguagessuchasRubyorGroovy.TouseRESTAssuredinourtestproject,wesimplyneedtoaddtheproperdependencyinMaven:
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>${rest-assured.version}</version>
<scope>test</scope>
</dependency>
orinGradle:dependencies{
testCompile("io.rest-assured:rest-assured:${restAssuredVersion}")
}
Afterthat,wecanusetheRESTAssuredAPI.Thefollowingclasscontainstwotestexamples.FirstsendsarequesttothefreeonlineRESTservicehttp://echo.jsontest.com/.Thenverifiesiftheresponsecodeandthebodycontentareasexpected.ThesecondtestconsumesanotherfreeonlineRESTservice(http://services.groupkt.com/)andalsoverifiestheresponse:
packageio.github.bonigarcia;
importstaticio.restassured.RestAssured.given;
importstaticorg.hamcrest.Matchers.equalTo;
importorg.junit.jupiter.api.Test;
publicclassPublicRestServicesTest{
@Test
voidtestEchoService(){
Stringkey="foo";
Stringvalue="bar";
given().when().get("http://echo.jsontest.com/"+key+"/"+value)
.then().assertThat().statusCode(200).body(key,
equalTo(value));
}
@Test
voidtestCountryService(){
given().when()
.get("http://services.groupkt.com/country/get/iso2code/ES")
.then().assertThat().statusCode(200)
.body("RestResponse.result.name",equalTo("Spain"));
}
}
RunningthistestinconsolewithMaven,wecancheckthatbothtestssucceed:
ExecutionoftestusingRESTAssured
Inthesecondexample,wearegoingtostudy,inadditiontothetest,wearealsogoingtoimplementtheserverside,thatis,theRESTserviceimplementation.Tothataim,wearegoingtouseSpringMVCandSpringBoot,previouslyintroducedonthischapter(seesectionSpring).
TheimplementationofRESTservicesinSpringisquitestraightforward.First,wesimplyneedtoannotateaJavaclasswith@RestController.Inthebodyofthisclass,weneedtoaddmethodsannotatedwith@RequestMapping.ThesemethodswilllistentothedifferentURLs(endpoints)implementedinourRESTAPI.Theacceptedelementsforthe@RequestMappingare:
value:ThisisthepathmappingURL.method:ThisfindstheHTTPrequestmethodstomapto.params:Thisfindsparametersofthemappedrequest,narrowingtheprimarymapping.headers:hisfindstheheadersofthemappedrequest.consumes:Thisfindsconsumablemediatypesofthemappedrequest.produces:Thisfindsproduciblemediatypesofthemappedrequest.
Ascanbeseeninspectingthecodeofthefollowingclass,ourserviceexampleimplementsthreedifferentoperations:GET/books(toreadallbookinthesystem),GET/book/{index}(toreadabookgivenitsidentifier),andPOST/book(tocreateabook).
packageio.github.bonigarcia;
importjava.util.List;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.ResponseEntity;
importorg.springframework.web.bind.annotation.PathVariable;
importorg.springframework.web.bind.annotation.RequestBody;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
importorg.springframework.web.bind.annotation.RestController;
@RestController
publicclassMyRestController{
@Autowired
privateLibraryServicelibraryService;
@RequestMapping(value="/books",method=RequestMethod.GET)
publicList<Book>getBooks(){
returnlibraryService.getBooks();
}
@RequestMapping(value="/book/{index}",method=RequestMethod.GET)
publicBookgetTeam(@PathVariable("index")intindex){
returnlibraryService.getBook(index);
}
@RequestMapping(value="/book",method=RequestMethod.POST)
publicResponseEntity<Boolean>addBook(@RequestBodyBookbook){
libraryService.addBook(book);
returnnewResponseEntity<Boolean>(true,HttpStatus.CREATED);
}
}
SinceweareimplementingaJupitertestforSpring,weneedtousetheSpringExtensionandalsotheSpringBootTestannotation.Asanovelty,wearegoingtoinjectatestcomponentprovidedbyspring-test,namedTestRestTemplate.
ThiscomponentisawrapperofthestandardSpring’sRestTemplateobject,whichallowstoimplementRESTclientsinaseamlessway.Inourtest,itrequeststoourservice(whichisstartedbeforeexecutingthetests),andresponsesareusedtoverifytheoutcome.
NoticethattheobjectMockMvc(explainedinthesectionSpring)couldbealsousedtotestRESTservices.ThedifferencewithrespecttoTestRestTemplateisthattheformerisusedtotestfromtheclient-side(thatis,responsecode,body,contenttype,andsoon),whilethethelatterisusedtotesttheservicefromtheserverside.Forinstance,intheexamplehere,theresponsestotheservicecalls(getForEntityandpostForEntity)areJavaobjects,whosescopeisonlytheserverside(intheclientside,thisinformationisserializedasJSON).
packageio.github.bonigarcia;
importstaticorg.junit.Assert.assertEquals;
importstatic
org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
importstaticorg.springframework.http.HttpStatus.CREATED;
importstaticorg.springframework.http.HttpStatus.OK;
importjava.time.LocalDate;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.boot.test.web.client.TestRestTemplate;
importorg.springframework.http.ResponseEntity;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment=RANDOM_PORT)
classSpringBootRestTest{
@Autowired
TestRestTemplaterestTemplate;
@Test
voidtestGetAllBooks(){
ResponseEntity<Book[]>responseEntity=restTemplate
.getForEntity("/books",Book[].class);
assertEquals(OK,responseEntity.getStatusCode());
assertEquals(3,responseEntity.getBody().length);
}
@Test
voidtestGetBook(){
ResponseEntity<Book>responseEntity=restTemplate
.getForEntity("/book/0",Book.class);
assertEquals(OK,responseEntity.getStatusCode());
assertEquals("TheHobbit",responseEntity.getBody().getName());
}
@Test
voidtestPostBook(){
Bookbook=newBook("I,Robot","IsaacAsimov",
LocalDate.of(1950,12,2));
ResponseEntity<Boolean>responseEntity=restTemplate
.postForEntity("/book",book,Boolean.class);
assertEquals(CREATED,responseEntity.getStatusCode());
assertEquals(true,responseEntity.getBody());
ResponseEntity<Book[]>responseEntity2=restTemplate
.getForEntity("/books",Book[].class);
assertEquals(responseEntity2.getBody().length,4);
}
}
Asshowninthescreenshotbelow,ourSpringapplicationisstartedbeforerunningourtests,whichareexecutedsuccessfully:
OutputofJupitertestusingTestRestTemplatetoverifyaRESTservice.
Toconcludethissection,weseeanexampleinwhichthelibraryWireMock(http://wiremock.org/)isused.ThislibraryallowstomockRESTservices,thatis,aso-calledHTTPmockserver.Thismockservercapturesincomingrequeststotheservice,providingstubbedresponses.ThiscapabilityisveryusefultotestasystemwhichconsumesaRESTservice,buttheserviceisnotavailableduringthetests(orwecantestthecomponentthatcallstheserviceinisolation).
Asusual,weseeanexampletodemonstrateitsusage.Let’ssupposewehaveasystemwhichconsumesaremoteRESTservice.ToimplementaclientforthatserviceweuseRetrofit2(http://square.github.io/retrofit/),whichisahighlyconfigurableHTTPclientforJava.Wedefinetheinterfacetoconsumethisserviceasillustratedintheclassbelow.Noticethattheserviceexposesthreeendpointsaimedtoreadaremotefile(openfile,readstream,andclosestream):
packageio.github.bonigarcia;
importokhttp3.ResponseBody;
importretrofit2.Call;
importretrofit2.http.POST;
importretrofit2.http.Path;
publicinterfaceRemoteFileApi{
@POST("/api/v1/paths/{file}/open-file")
Call<ResponseBody>openFile(@Path("file")Stringfile);
@POST("/api/v1/streams/{streamId}/read")
Call<ResponseBody>readStream(@Path("streamId")StringstreamId);
@POST("/api/v1/streams/{streamId}/close")
Call<ResponseBody>closeStream(@Path("streamId")StringstreamId);
}
ThenweimplementtheclasswhichconsumestheRESTservice.Inthisexample,itisasimpleJavaclasswhichconnectstotheremoteservicegivenitsURLpassedasconstructorparameter:
packageio.github.bonigarcia;
importjava.io.IOException;
importokhttp3.ResponseBody;
importretrofit2.Call;
importretrofit2.Response;
importretrofit2.Retrofit;
importretrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
importretrofit2.converter.gson.GsonConverterFactory;
publicclassRemoteFileService{
privateRemoteFileApiremoteFileApi;
publicRemoteFileService(StringbaseUrl){
Retrofitretrofit=newRetrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl).build();
remoteFileApi=retrofit.create(RemoteFileApi.class);
}
publicbyte[]getFile(Stringfile)throwsIOException{
Call<ResponseBody>openFile=remoteFileApi.openFile(file);
Response<ResponseBody>execute=openFile.execute();
StringstreamId=execute.body().string();
System.out.println("Stream"+streamId+"open");
Call<ResponseBody>readStream=remoteFileApi.readStream(streamId);
byte[]content=readStream.execute().body().bytes();
System.out.println("Received"+content.length+"bytes");
remoteFileApi.closeStream(streamId).execute();
System.out.println("Stream"+streamId+"closed");
returncontent;
}
}
Finally,weimplementaJUnit5testtoverifyourservice.Notcethatwearecreatingthemockserver(newWireMockServer)andstubbingtheRESTservicecallsusingthestaticmethodsstubFor(...)providedbyWireMockinthesetupofthetest(@BeforeEach).Sinceinthiscase,theSUTisverysimpleandithasnoDOCs,wedirectlyinstantiatetheclassRemoteFileServicealsointhesetupofeachtest,usingthemockserverURLasconstructorargument.Finally,wetestourservice(whichusesthemockserver)simplyexercisingtheobjectcalledwireMockServer,inthisexample,bycallingtothemethodgetFileandassessingitsoutput.
packageio.github.bonigarcia;
importstaticcom.github.tomakehurst.wiremock.client.WireMock.aResponse;
importstaticcom.github.tomakehurst.wiremock.client.WireMock.configureFor;
importstaticcom.github.tomakehurst.wiremock.client.WireMock.post;
importstaticcom.github.tomakehurst.wiremock.client.WireMock.stubFor;
importstaticcom.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
importstaticcom.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
importstaticorg.junit.jupiter.api.Assertions.assertEquals;
importjava.io.IOException;
importjava.net.ServerSocket;
importorg.junit.jupiter.api.AfterEach;
importorg.junit.jupiter.api.BeforeEach;
importorg.junit.jupiter.api.Test;
importcom.github.tomakehurst.wiremock.WireMockServer;
publicclassRemoteFileTest{
RemoteFileServiceremoteFileService;
WireMockServerwireMockServer;
//Testdata
Stringfilename="foo";
StringstreamId="1";
StringcontentFile="dummy";
@BeforeEach
voidsetup()throwsException{
//LookforfreeportforSUTinstantiation
intport;
try(ServerSocketsocket=newServerSocket(0)){
port=socket.getLocalPort();
}
remoteFileService=newRemoteFileService("http://localhost:"+
port);
//Mockserver
wireMockServer=newWireMockServer(options().port(port));
wireMockServer.start();
configureFor("localhost",wireMockServer.port());
//Stubbingservice
stubFor(post(urlEqualTo("/api/v1/paths/"+filename+"/open-
file"))
.willReturn(aResponse().withStatus(200).withBody(streamId)));
stubFor(post(urlEqualTo("/api/v1/streams/"+streamId+
"/read"))
.willReturn(aResponse().withStatus(200).withBody(contentFile)));
stubFor(post(urlEqualTo("/api/v1/streams/"+streamId+/close"))
.willReturn(aResponse().withStatus(200)));
}
@Test
voidtestGetFile()throwsIOException{
byte[]fileContent=remoteFileService.getFile(filename);
assertEquals(contentFile.length(),fileContent.length);
}
@AfterEach
voidteardown(){
wireMockServer.stop();
}
}
Executingthetestintheconsole,inthetraceswecanseehowtheinternalHTTPservercontrolledbyWireMockisstartedbeforethetestexecution.Then,thethreeRESToperations(openstream,readbytes,closestream)areexecutedbythetest,andfinallythemockserverisdisposed:
SummaryThissectionprovidesadetailedinsightofhowJUnit5canbeusedinconjunctionwiththird-partyframeworks,libraries,andplatforms.ThankstotheJupiterextensionmodel,developerscancreateextensionswhichallowsseamlessintegrationwithexternalframeworkstoJUnit5.First,wehaveseentheMockitoExtension,anextensionprovidedbytheJUnit5teamtouseMockito(anotoriousmockframeworkforJava)inJupitertests.Then,wehaveusedtheSpringExtension,whichistheofficialextensionprovidedintheversion5oftheSpringFramework.ThisextensionintegratesSpringintotheJUnit5programmingmodel.Thisway,weareabletouseSpring’sapplicationcontexts(thatis,theSpring’sDIcontainer)inourtests.
WehavealsoreviewedtheSeleniumExtensionimplementedbyselenium-jupiter,anopensourceprojectprovidingaJUnit5extensionforSeleniumWebDriver(testingframeworkforwebapplications).Thanktothinsextension,wecanusedifferentbrowserstointeractautomaticallywithwebapplicationsandemulatedmobiledevices(usingAppium).Then,wehaveseentheCucumberExtension,allowstospecifyJUnit5acceptancetestsfollowingaBDDstyleusingtheGherkinlanguage.Finally,wehaveseenhowtheopensourceJUnit5-DockerextensioncanbeusedtostartDockercontainers(downloadingtheimagefromDockerHub)beforetheexecutionofourJUnit5tests.
Moreover,wediscoveredthattheextensionmodelisnottheonlywayofinteractingwithexternaltechnologiesbyJUnittests.Forexample,inordertorunJupitertestsinanAndroidproject,wecanusetheandroid-junit5plugin.Ontheotherhand,eventhoughthereisnocustomextensionforassessingRESTservicesusingJUnit5,theintegrationwithsuchlibrariesisstraitforward:wesimplyneedtoincludetheproperdependencyinourprojectanduseitinourtests(forexample,RESTAssured,Spring,orWireMock).
FromRequirementsToTestCasesProgramtestingcanbeusedtoshowthepresenceofbugs,butnevertoshow
theirabsence!-EdsgerDijkstra
Thischapterprovidesabaseofknowledgeaimedtohelpsoftwareengineerstowritemeaningfultestcases.Thestartingpointforthisprocessistheunderstandingoftherequirementsofthesystembeingtested.Withoutthatinformation,itisnotfeasibletodesignnorimplementvaluabletests.Afterthat,severalactionsmightbeexecutedbeforetheactualcodingofthetests,namely,testplanningandtestdesign.Oncewestartthetestcodingprocess,weneedtohaveinmindasetofprinciplestowritecoderight,andalsoasetofanti-patternsandbadsmellstobeavoided.Allthisinformationisprovidedinthischapterinformofthefollowingsections:
Theimportanceofrequirements:Thissectionprovidesageneraloverviewofthesoftwaredevelopmentprocess,startedbythestatementofsomeneedstobecoveredbyasoftwaresystem,andfollowedbyseveralstages,typicallyincludinganalysis,design,implementation,andtests.Testplanning:Adocumentcalledtestplancanbegeneratedatthebeginningofasoftwareproject.ThissectionreviewsthestructureofatestplanaccordingtotheIEEE829StandardforTestDocumentation.Aswewilldiscover,thecompletestatementofatestplanisaveryfine-grainedprocess,especiallyrecommendedforlargeprojectsinwhichthecommunicationamongtheteamisakeyaspectforthesuccessoftheproject.Testdesign:Beforestartingthecodingofthetests,itisalwaysagoodpracticetothinkabouttheblueprintofthesetests.Inthissection,wereviewthemajoraspectstobetakenintoconsiderationtodesignproperlyourtests.Weputtheaccentonthetestdata(expectedoutcome),whichfeedthetestassertions.Inthisregard,wereviewsomeblack-boxdatagenerationtechniques(equivalencepartitioningandboundaryanalysis)andwhite-box(testcoverage).Softwaretestingprinciples:Thissectionprovidesasetofbest-practiceswhichcanhelpuswriteourtests.Testanti-patterns:Finally,theoppositesideisalsoreviewed:whatare
TheimportanceofrequirementsSoftwaresystemsarebuilttosatisfysomekindofneedtoagroupofconsumers(finalusersorcustomer).Understandingthoseneedsisoneofmostchallengingproblemsinsoftwareengineeringduetothefactthatitisquitecommonthatconsumerneedsarenebulous(especiallyintheearlystagesoftheproject).Moreover,itisalsocommonthattheseneedscanbedeeplychangedthroughouttheprojectlifetime.FredBrooks,awell-knownsoftwareengineer,andcomputerscientist,definesthisprobleminhisseminalbookTheMythicalMan-Month(1975):
Thehardestsinglepartofbuildingasoftwaresystemisdecidingpreciselywhattobuild.Nootherpartoftheconceptualworkisasdifficultasestablishingthedetailedtechnicalrequirements…Nootherpartoftheworksocripplestheresultingsystemifdonewrong.Nootherpartisasdifficulttorectifylater.
Inanycase,consumer’sneedsarethetouchstoneforanysoftwareproject.Fromtheseneeds,alistoffeaturescanemerge.Wedefineafeatureasahigh-leveldescriptionofasoftwaresystemfunctionality.Fromeachfeature,oneormorerequirements(functionalandnon-functional)shouldbederived.Arequirementiseverythingthatbetrueaboutthesoftware,inordertomeettheconsumer’sexpectations.Scenarios(real-lifeexamplesratherthanabstractdescriptions)canbeusefulforaddingdetailstotherequirementsdescription.Thegroupofrequirementsand/orlistoffeaturesofthesoftwaresystemareoftenknownasspecification.
Insoftwareengineering,thestageofdefiningtherequirementsiscalledrequirementselicitation.Inthisstage,softwareengineersneedtoclarifywhatproblemtheyaretryingtosolve.Attheendofthisphase,itisacommonpracticetostartthemodelingofthesystem.Tothataim,amodelinglanguage(typicallyUML)isemployedtocreateagroupofdiagrams.TheUMLdiagrams,whichtypicallyfitsintheelicitationstageistheusecasediagram(modelofthefunctionalityofthesystemanditsrelationshipwiththeinvolvedactors).
Modelingisnotalwayscarriedoutinallsoftwareprojects.Forexample,agilemethodologiesaremorebasedontheprincipleofsketchingratherthaninaformalmodelingstrategy.
Afterelicitation,requirementsshouldberefinedintheanalysisstage.Inthis
phase,thestatedrequirementsareanalysedinordertoresolveincomplete,ambiguousofcontradictoryissues.Asaresult,inthisstageitislikelytocontinuemodeling,usingforexamplehigh-levelclassdiagramsnotlinkedtoanyspecifictechnologyyet.Oncetheanalysisisclear(thatis,thewhatofthesystem),weneedtofindouthowtoimplementit.Thisstageisknownasdesign.Inthedesignphase,theguidelinesoftheprojectshouldbeestablished.Tothataim,anarchitectureofthesoftwaresystemistypicallyderivedfromtherequirements.Again,themodelingtechniquesarebroadlyemployedtocarryoutdifferentaspectsofthedesign.ThereisabunchofUMLdiagramsthatcanbeusedatthispoint,includingstructuraldiagrams(component,deployment,object,package,andprofilediagram)andbehavioraldiagrams(activity,communication,sequence,orstatediagram).Fromthedesign,theactualimplementation(thatis,coding)canstart.
Theamountofmodelingcarriedoutinthedesignstagevariessignificantlydependingondifferentfactors,includingthetypeandsizeofthecompanyproducingthesoftware(multinationals,SMEs,governmental,andsoon),thedevelopmentprocess(waterfall,spiral,prototyping,agile,andsoon),thetypeofproject(enterprise,opensource,andsoon),thetypeofsoftware(custommadesoftware,commercialoff-the-shelf,andsoon),andeventhebackgroundofthepeopleinvolved(experience,career,andsoon).Allinall,thedesignsneedtobeunderstoodasawayofcommunicationbetweenthedifferentrolesofsoftwareengineersparticipatingintheproject.Typically,thebiggertheproject,themorenecessaryafine-graineddesignbasedondifferentmodelingdiagramsis.
Concerningtests,inordertomakeapropertestplan(seenextsectionforfurtherdetails),againweneedtousetherequirementselicitationdata,thatis,thelistofrequirementsand/orfeatures.Inotherwords,inordertoverifyoursystem,weneedtotoknowbeforehandwhatweexpectfromit.UsingtheclassicdefinitionproposedbyBarryBoehm(seechapter1,RetrospectiveOnSoftwareQualityAndJavaTesting),verificationisusedtoanswerthequestionArewebuildingtheproductright?Tothat,weneedtoknowtherequirements,oratleast,thedesiredfeatures.Inadditiontoverification,itwouldbedesirabletocarryoutsomevalidation(accordingtoBoehm:Arewebuildingtherightproduct?).Thisisnecessarysincesometimesthereisagapbetweenwhathasbeenspecified(thefeaturesandrequirements)andtherealneedsoftheconsumer.Therefore,validationisahigh-levelassessmentmethod,andtocarryoutit,thefinalconsumercanbeinvolved(validatingthesoftwaresystemonceitisdeployed).Alltheseideasaredepictedinthefollowingpicture:
Softwareengineeringgenericdevelopmentprocess
Thereisnouniversalworkflowforthetermspresentedsofar(communication,requirementelicitation,analysis,design,implementation/test,anddeployment).Intheprecedingdiagram,itfollowsalinearprocessflows,nevertheless,inpractice,itcanfollowaniterative,evolutionary,orparallelworkflow.
Toillustratethepotentialproblemsinvolvedinthedifferentphasesinsoftwareengineer(analysis,design,implementation,andsoon),itisworthtoreviewtheclassicalcartoonHowprojectreallyworks?Theoriginalsourceofthispictureisunknown(thereareversionsdatingbacktothe1960s).In2007,asitecalledProjectCartoonemerged(http://www.projectcartoon.com/),allowingtocustomizetheoriginalcartoonwithnewscenes.Thefollowingchartistheversion1.5ofthecartoonprovidedonthatsite:
Howprojectsreallywork,version1.5(illustratedcreatedbywww.projectcartoon.com)
Ifwethinkaboutthispicture,wediscoverthattherootoftheproblemscomesfromtherequirements,badlyexplainedbythecustomeratthebeginning,andworstunderstoodbytheprojectleader.Fromthatpoint,thewholesoftwareengineeringprocessturnsintotheChinesewhisperschildrengame.Tosolvealltheseproblemsisoutofthescopeofthisbook,butasagoodstart,weneedtotakespecialcareintherequirements,whichguidethewholeprocess,including,ofcourse,thetests.
TestplanningAfirststepinthetestingpathcanbethegenerationofadocumentcalledtestplan,whichistheblueprinttoconductsoftwaretesting.Thisdocumentdescribestheobjective,scope,approach,focus,anddistributionofthetestingefforts.Theprocessofpreparingsuchdocumentisausefulwaytothinkabouttheneedstoverifyofasoftwaresystem.Again,thisdocumentisespeciallyusefulwhenthesizeoftheSUTandtheinvolvedteamislarge,duetothefactthattheseparationofworkindifferentrolesmakesthecommunicationapotentialdeterrentforthesuccessoftheproject.
AwaytocreateatestplanistofollowtheIEEE829StandardforTestDocumentation.Althoughthisstandardmightbetoomuchformalforthemostofsoftwareprojects,itmightbeworthtoreviewtheguidelinesproposedinthisstandard,andusethepartsneeded(ifany)inoursoftwareprojects.ThestepsproposedinIEEE829arethefollowing:
1. Analyzetheproduct:Thispartreinforcestheideaofextractingtheunderstandingtherequirementsofthesystemfromtheconsumerneeds.Asalreadyexplained,itisnotpossibletotestasoftwareifnoinformationaboutitisavailable.
2. Designtheteststrategy:Thispartoftheplancontainsseveralparts,including:
Definescopeoftesting,thatis,thesystemcomponentstobetested(inscope)andthosepartswhichdonot(outofscope).Asexplainedlater,exhaustivetestingisnotfeasible,andweneedtochoosecarefullywhatisgoingtobetested.Thisisnotasimplechoice,anditcanbedeterminedbydifferentfactors,suchasprecisecustomerrequests,projectbudgetandtiming,andskillsoftheinvolvedsoftwareengineers.Identifytestingtype,thatis,whichlevelsoftestsshouldbeconducted(unit,integration,system,acceptance)andwhichteststrategy(blackbox,whitebox,non-functional).Documentrisks,thatis,potentialproblemswhichmightcausedifferentissuesintheproject.
3. Definethetestobjectives:Inthispartoftheplan,thelistoffeaturestobetestedarelistedtogetherwiththetargetoftestingeachone.
4. Definethetestcriteria:Thesecriteriaaretypicallymadeupbytwo
parts,namely:Suspensioncriteria,forinstancethepercentageoffailedtestsinwhichthedevelopmentofnewfeaturesissuspendeduntiltheteamsolvesallthefailures.Exitcriteria,forexample,thepercentageofcriticalteststhatshouldbepassedtoproceedtonextphaseofdevelopment.
5. Resourceplanning:Thispartoftheplanisdevotedtosummarizetheresourcesrequiredtocarryoutthetestingactivities.Itcouldbepersonnel,equipment,orinfrastructure.
6. Plantestenvironment:Itconsistsofthesoftwareandhardwaresetuponwhichtestaregoingtobeexecuted.
7. Scheduleandestimation:Inthisphase,managersaresupposedtobreakoutthewholeprojectintosmalltasksestimatingtheefforts(person-month).
8. Determinetestdeliverables:Determineallthedocumentsthathastobemaintainedtosupportthetestingactivities.
Ascanbeseen,testplanningisacomplextask,typicallycarriedoutinlargeprojectsbymanagers.Intherestofthischapterwecontinuediscoveringhowtowritetestcases,buthereinafterfromapointofviewclosesttotheactualtestcoding.
TestdesignInordertodesignproperlyatest,weneedtodefinespecificallywhatneedstobeimplemented.Tothataim,itisimportanttorememberwhatisthegenericstructureofatest,alreadyexplainedinchapter1,RetrospectiveOnSoftwareQualityAndJavaTesting.Therefore,foreachtestweneedtodefine:
Whatistestfixture,thatis,therequiredstateintheSUTtocarryoutthetest?Thisisdoneatthebeginningofthetestinthestagecalledsetup.Attheendofthetest,thetestfixturemightbereleasedinthestagecalledteardown.WhatistheSUT,andifwearedoingunittests,whichareitsDOC(s)?Unittestshouldbeinisolationandthereforeweneedtodefinetestdoubles(typicallymocksorspies)fortheDOC(s).Whataretheassertions?Thisakeypartoftests.Withoutassertions,wecannotclaimthatatestisactuallymade.Inordertodesignassertion,itisworthtorecallwhichisitsgenericstructure.Inshort,anassertionconsistsinthecomparisonofsomeexpectedvalue(testdata)andtheactualoutcomeobtainedfromtheSUT.Ifanyoftheassertionsisnegative,thetestwillbedeclaredasfailed(testverdict):
Testcasesandassertionsgeneralschema
Testdataplaysacrucialroleinthetestingprocess.Thesourceoftestdatais
oftencalledtestoracles,andtypicallycanbeextractedfromtherequirements.Nevertheless,therearesomeotherscommonlyusedsourcesfortestsoracles,forexample:
Adifferentprogram,whichproducestheexpectedoutput(inverserelationship).Aheuristicorstatisticaloraclethatprovidesapproximateresults.Valuesbasedontheexperienceofhumanexperts.
Moreover,testdatacanbederived,dependingontheunderlyingtestingtechnique.Whenusingblack-boxtesting,thatis,exercisesomespecificrequirementbasedusingsomeinputandexpectingsomeoutput,differenttechniquescanbeemployed,suchasequivalencepartitioningorboundaryanalysis.Ontheotherside,ifweareusingwhite-boxtesting,thestructureisthebasisforourtestandthereforethetestcoveragewillbekeytoselectthetestinputwhichmaximizesthesecoveragerates.Inthefollowingsections,thesetechniquesarereviewed.
EquivalencepartitioningEquivalencepartitioning(alsoknownasequivalenceclasspartitioning)isablack-boxtechnique(thatis,itreliesintherequirementsofthesystem)aimedtoreducethenumberofteststhatshouldbeexecutedagainstaSUT.ThistechniquewasfirstdefinedbyGlenfordMyersin1978as:
“Atechniquethatpartitionstheinputdomainofaprogramintoafinitenumberofclasses[sets],itthenidentifiesaminimalsetofwell-selectedtestcasestorepresenttheseclasses.”
Inotherwords,equivalencepartitioningprovidesacriteriatoanswerthequestionHowmanytestsdoweneed?Theideaistodivideallpossibleinputtestdata(whichoftenisaenormousnumberofcombinations)inasetofvaluesforwhichweassumetobeprocessedinthesamewaybytheSUT.Wecallequivalenceclassestothesesetsofvalues.TheideaisthattestingonerepresentativevaluewithintheequivalenceclassisconsidersufficientbecauseitisassumedthatallthevaluesareprocessedinthesamewaybytheSUT.
Typically,theequivalenceclassesforagivenSUTcanbegroupedintwotypes:validandinvalidinputs.Theequivalencepartitioningtestingtheoryensuresthatonlyonetestcaseofeachpartitionisneededtoevaluatethebehavioroftheprogramfortherelatedpartition(boththevalidandtheinvalidclasses).ThefollowingprocessdescribeshowtosystematicallycarryouttheequivalencepartitioningforagivenSUT:
1. First,wedeterminethedomainofallpossiblevalidinputsforaSUT.Tofindoutthesevalues,werelyonthespecification(featuresorfunctionalrequirements).OurSUTissupposedtoprocessthesevalues(validequivalenceclass)correctly.
2. Ifourspecificationestablishesthatsomeelementsoftheequivalenceclassareprocesseddifferently,theyshouldassignetoanotherequivalenceclass.
3. Thevaluesoutsidethisdomaincanbeseenasanotherequivalenceclass,thistimeforinvalidinputs.
4. Foreverysingleequivalenceclass,arepresentativevalueischosen.Thisdecisionisanheuristicprocesstypicallybasedonthetesterexperience.
5. Foreverytestinput,thepropertestoutputisalsoselected,andwiththese
BoundaryanalysisAsanyprogrammerknows,faultsoftenappearattheboundaryofaequivalenceclass(forexample,theinitialvalueofanarray,themaximumvalueforagivenrange,andsoon).Boundaryvalueanalysisisamethod,whichcomplementsequivalencepartitioningbylookingattheboundariesofthetestinput.ItwasdefinedbytheNationalInstituteofStandardsandTechnology(NIST)in1981as:
“Aselectiontechniqueinwhichtestdataarechosentoliealong‘boundaries’oftheinputdomain[oroutputrange]classes,datastructures,andprocedureparameters.”
Allinall,toapplyboundaryvalueanalysisinourtests,weneedtoevaluateourSUTexactlyinthebordersofourequivalenceclass.Therefore,typicallytwotestscasesarederivedusingthisapproach:theupperandthelowerboundaryoftheequivalenceclass.
TestcoverageTestcoverageistherateofcodeinSUTthatisexercisedforanyoftheirtests.TestcoverageisveryusefultofindinguntestedpartsofourSUT.Therefore,itcanbetheperfectwhiteboxtechnique(structural)tocomplementtheblackbox(functional).Asageneralrule,atestcoveragerateof80%oraboveisconsideredreasonable.
TherearedifferentJavalibraries,whichallowstomaketestcoverageinasimplemanner,forinstance:
Cobertura(http://cobertura.github.io/cobertura/):Itisanopensourcereportingtool,whichcanbeexecutedusingAnt,Maven,ordirectlyusingthecommandline.EclEmma(http://www.eclemma.org/):ItisanopensourcecodecoveragetoolforEclipse.AsofEclipse4.7(Oxygen),EclEmmaisintegratedoutoftheboxintheIDE.ThefollowingscreenshotshowsanexampleonhowEclEmmahighlightsthecodecoverageonaJavaclassinEclipse:
TestcoveragewithEclEmmainEclipse4.7(Oxygen)
JaCoCo(http://www.jacoco.org/jacoco/):ItisanopensourcecodecoveragelibrarycreatedbytheEclEmmateambasedonotheroldcoveragelibrarycalledEMMA(http://emma.sourceforge.net/).JaCoCoisavailableasaMavendependency.Codecov(https://codecov.io/):Itisacloudsolutionofferingafriendlycodecoveragewebdashboard.Itisfreeforopensourceprojects.
SoftwaretestingprinciplesExhaustivetestingisthenamegiventoatestapproach,whichusesallpossiblecombinationsoftestinputstoverifyasoftwaresystem.Thisapproachisonlyapplicabletotinysoftwaresystemsorcomponentswithaclosefinitenumberofpossibleofoperationsandalloweddata.Inthemajorityofsoftwaresystems,itisnotfeasibletoverifyeverypossiblepermutationandinputcombination,andthereforeexhaustivetestingisjustatheoreticalapproach.
Forthatreason,itissaidthattheabsenceofdefectsinasoftwaresystemcannotbeproved.ThiswasstatedbythecomputersciencepioneerEdsgerW.Dijkstra(seequoteatbeginningofthischapter).Thus,testingis,atbest,sampling,anditmustbecarriedoutinanysoftwareprojecttoreducetheriskofsystemfailures(seechapter1,RetrospectiveOnSoftwareQualityAndJavaTesting,torecallthesoftwaredefecttaxonomy).Sincewecannottesteverything,weneedtotestproperly.Inthissection,wereviewasetofbestpracticestowriteeffectiveandefficienttestcases,namely:
Testsshouldbesimple:Thesoftwareengineerwritingthetest(callhimorhertester,programmer,developer,orwhatever)shouldavoidattemptingtotesthisorherprogram.Inregardstotesting,therightanswertothequestionWhowatchesthewatchmen?Shouldbenobody.Ourtestlogicshouldbesimpleenoughtoavoidanykindofmeta-testing,sincethiswouldleadtoarecursiveproblemoutofanylogic.Indirectly,ifwekeeptestssimple,wealsoobtainanotherdesirablefeature:testswillbeeasytomaintain.Donotimplementsimpletests:Onethingismakesimpletests,andanotherverydifferentstuffistoimplementdummycode,suchasgetterorsetters.Asintroducedbefore,testisatbestsampling,andwecannotwasteprecioustimeinassessingsuchkindofpartofourcodebase.Easytoread:Thefirststepistoprovideameaningfulnameforourtestmethod.Inaddition,thankstotheJUnit5@DisplayNameannotation,wecanprovidearichtextualdescription,whichdefineswithoutJavanamingconstraintsthegoalofthetest.Singleresponsibilityprinciple:Thisisageneralprincipleofcomputerprogrammingthatstatesthateveryclassshouldhaveresponsibilityofasinglefunctionality.Itiscloselyrelatedtothemetricofcohesion.This
principleisveryimportanttobeaccomplishedwhencodingtests:asingletestshouldbeonlyreferredtoagivensystemrequirement.Testdataiskey:Asdescribedinthesectionbefore,theexpectedoutcomefromtheSUTisacentralpartofthetests.Thecorrectmanagementofthesedataiscriticaltocreateeffectivetests.Fortunately,JUnit5providesarichtoolboxtohandletestdata(seesectionParameterizedtestsinchapter4,SimplifyingTestingWithAdvancedJUnitFeatures).Unittestshouldbeexecutedveryfast:Acommonlyacceptedruleofthumbforthedurationofunittestisthataunittestshouldlastasecondatthemost.Toaccomplishthatgoal,itisalsorequiredthatunittestisolatesproperlytheSUT,doublingproperlyitsDOCs.Testmustberepeatable:Defectsshouldbereproducedasmanytimesasrequiredfordeveloperstofindthecauseofthebug.Thisisthetheory,butunfortunatelythisisnotalwaysapplicable.Forexample,inmulti-threadedSUT(areal-timeorserver-sidesoftwaresystems),raceconditionsarelikelytooccur.Inthosesituations,non-deterministicdefects(oftencalledheisenbugs)mightbeexperienced.Weshouldtestpositiveandthenegativescenarios:Thismeanthatweneedtowritetestswithforinputconditionthatassesstheexpectedoutcome,butwealsoneedtoverifywhattheprogramisnotsupposedtodo.Inadditiontomeetitsrequirements,programsmustbetestedtoavoidunwantedsideeffects.Testingcannotbedoneonlyforthesakeofcoverage:Justbecauseallpartsofthecodehavebeentouchedbysometests,wecannotassurethatthosepartshavebeenthoroughlytested.Forthattobetrue,testshavetoanalyzedintermsofreductionofrisks.
ThepsychologyoftestingFromapsychologicalpointofview,theobjectiveoftestingshouldbeexecutingasoftwaresystemwiththeintentoffindingdefects.Understandingthemotivationofthatclaimcanmakethedifferenceinthesuccessofourtests.
Humanbeingstendtobegoaloriented.Ifwecarryoutteststodemonstratethataprogramhasnoerrors,wewilltendtoimplementtestsselectingtestdatawithalowprobabilityofcausingprogramfailures.Ontheotherhand,iftheobjectiveistodemonstratethataprogramhaserrors,wewillincreasetheprobabilityoffindingthem,addingmorevaluetotheprogramthantheformerapproach.Forthatreason,testingisoftenconsideredasadestructiveprocess,sincetestersaresupposedtoprovethattheSUThaserrors.
Moreover,tryingtodemonstratethaterrorsarepresentinthesoftwareisagoalfeasible,whiletryingtodemonstratetheirabsence,asexplainedbefore,itisimpossible.Again,psychologystudiestellusthatpeopleperformpoorlywhentheyknowthatataskisinfeasible.
Testanti-patternsInsoftwaredesign,apatternisareusablesolutiontosolverecurringproblems.Thereareabunchofthem,includingforexamplesingleton,factory,builder,facade,proxy,decorator,oradapter,tonameafew.Anti-patternsarealsopatterns,butundesirableones.Concerningtotesting,itisworthtoknowsomeoftheseanti-patternstoavoidtheminourtests:
Secondclasscitizens:Testcodecontainingalotofduplicatedcode,makingithardtomaintain.Thefreeride(alsoknownasPiggyback):Insteadofwritinganewmethodtoverifyanotherfeature/requirement,anewassertionisaddedtoanexistingtest.Happypath:Itonlyverifiesexpectedresultswithouttestingforboundariesandexceptions.Thelocalhero:Atestdependenttosomespecificlocalenvironment.Thisanti-patterncanbesummarizedinthephraseItworksinmymachine.Thehiddendependency:Atestthatrequiressomeexistingdatapopulatedsomewherebeforethetestruns.Chaingang:Teststhatmustberuninacertainorder,forexample,changingtheSUTtoastateexpectedbythenextone.Themockery:AunittestthatcontainstoomuchtestdoublesthattheSUTisnoteventestedatall,insteadofreturningdatafromtestdoubles.Thesilentcatcher:Atestthatpassesevenifanunintendedexceptionactuallyoccurs.Theinspector:AtestthatviolatesencapsulationthatanyrefactorintheSUTrequiresreflectingthosechangesinthetest.Excessivesetup:Atestthatrequiresahugesetupinordertostarttheexercisestage.Analprobe:Atestwhichhastouseunhealthywaystoperformitstask,suchasreadingprivatefieldsusingreflection.Thetestwithnoname:Testmethodsnamewithnoclearindicatoraboutwhatitisbeingtested(forexample,identifierinabugtrackingtool).Theslowpoke:Aunittestwhichlastsoverfewseconds.Theflickeringtest:Atestwhichcontainsraceconditionswithinthepropertest,makingittofailfromtimetotime.
Waitandsee:Atestthatneedstowaitaspecificamountoftime(forexample,Thread.sleep())beforeitcanverifysomeexpectedbehavior.Inappropriatelysharedfixture:Teststhatuseatestfixturewithoutevenneedthesetup/teardown.Thegiant:Atestclassthatcontainsahugenumberoftestsmethods(GodObject).Wetfloor:Atestthatcreatespersisteddatabutitisnotcleanupatwhenfinished.Thecuckoo:Aunittestwhichestablishessomekindoffixturebeforetheactualtest,butthenthetestdiscardssomehowthefixture.Thesecretcatcher:Atestthatisnotmakinganyassertion,relyingonanexceptiontobethrownandreportingbythetestingframeworkasafailure.Theenvironmentalvandal:Atestwhichrequirestheuseofgivenenvironmentvariables(forinstance,afreeportnumbertoallowssimultaneousexecutions).Doppelganger:Copyingpartsofthecodeundertestintoanewclasstomakevisibleforthetest.Themotherhen:Afixturewhichdoesmorethanthetestneeds.Thetestitall:TeststhatshouldnotbreaktheSingleResponsibilityPrinciple.Linehitter:AtestwithoutanykindofrealverificationoftheSUT.Theconjoinedtwins:TeststhatarecalledunittestsbutarereallyintegrationtestssincethereisnoisolationbetweentheSUTandtheDOC(s).Theliar:Atestthatdoesnottestwhatwassupposedtotest.
CodesmellsCodesmells(alsoknownasbadsmellwhenreferredtosoftware)areundesirablesymptomswithinthesourcecode.Codesmellsarenotproblematicperse,buttheycanevidencesomekindofissuenearby.
Asdescribedinprevioussections,testsshouldbesimpleandeasytoread.Withthatpromises,codesmellsshouldbepresentinourtestsundernocircumstances.Allinall,genericcodesmellsmightbeavoidedinourtests.Someofthemostcommoncodesmellsarethefollowing:
Duplicatedcode:Clonedcodeisalwaysabadideainsoftware,sinceitbreakstheprincipleDon’tRepeatYourself(DRY).Thisproblemisevenworstintests,sincetestlogicmustbecrystalclear.Highcomplexity:Toomanybranchesorloopsmaybepotentiallysimplifiedintosmallerpieces.Longmethod:Amethodthathasgrowntoolargeisalwaysproblematic,anditisaverybadsymptomwhenthismethodisatest.Unappropriatednamingconvention:Variables,class,andmethodnamesshouldbeconcise.Itisconsideredabadsmelltouseverylongidentifiers,butalsouseexcessiveshort(ormeaningless)ones.
SummaryThestartingpointforthetestdesignshouldbethelistofrequirements.Iftheserequirementshavenotbeenformallyelicited,atleastweneedtoknowtheSUTfeatures,whichreflectsthesoftwareneeds.Fromthispoint,severalstrategiescanbecarriedout.Asusual,thereisnouniquepathtoreachourgoal,whichintheendshouldbereducingtherisksoftheproject.
Thischapterreviewedaprocessaimedtocreateeffectiveandefficienttestscases.Thisprocessinvolvestheanalysisofrequirements,definitionofatestplan,designoftestcases,andfinallywritingthetestcases.Weshouldbeawarethat,eventhoughsoftwaretestingistechnicaltask,itinvolvessomeimportantconsiderationsofhumanpsychology.Thesefactorsshouldbeknownbysoftwareengineersandtestersinordertofollowknowbestpracticesandalsoavoidingcommonmistakes.
Inchapter7,Testingmanagement,wearegoingtounderstandhowsoftwaretestingactivitiesaremanagedinalivingsoftwareproject.Tothat,firstwereviewwhenandhowtocarryouttestinginthecommonsoftwaredevelopmentprocesses,suchaswaterfall,spiral,iterative,spiral,agile,ortest-drivendevelopment.Then,theserver-sideinfrastructure(suchasJenkinsorTravis)aimedtoautomatethesoftwaredevelopmentprocessinthecontextofJUnit5isreviewed.Finally,welearnhowtokeeptrackofthedefectsfoundwiththeJupitertestsusingtheso-calledissuetrackingsystemsandtestreportinglibraries.
TestingManagementTheimportantthingisnottostopquestioning.
-AlbertEinstein
Thisisthefinalchapterofthebook,anditsobjectiveistoguidehowtounderstandwhenandhowsoftwaretestingactivitiesaremanagedinalivingsoftwareproject.Tothataim,thischapterisstructuredintothefollowingsections:
Softwaredevelopmentprocesses:Inthissectionwestudywhentestsareexecutedindifferentmethodologies:Behavior-DrivenDevelopment(BDD),Test-DrivenDevelopment(TDD),Test-FirstDevelopment(TFD)andTest-LastDevelopment(TLD).ContinuousIntegration(CI):Inthissection,wewilldiscoverCI,thesoftwaredevelopmentpractice,inwhichtheprocessofbuild,test,andintegrationiscarriedoutcontinuously.Thecommontriggerofthisprocessisusuallythecommitofnewchanges(patches)toasourcecoderepository(forexample,GitHub).Inaddition,inthissection,wewilllearnhowtoextendCI,reviewingtheconceptofContinuousDeliveryandContinuousDeployment.Finally,wepresenttwoofthemostimportantbuildservernowadays:JenkinsandTravisCI.Testreporting:Inthissection,wewillfirstdiscovertheXMLformatinwhichthexUnitframeworkusuallyreportstheexecutionoftests.Theproblemwiththisformatisthatitisnothumanreadable.Forthisreason,therearetoolswhichcovertthisXMLintoafriendlierformat,typicallyHTML.Wereviewtwoalternatives:MavenSurefireReportandAllure.Defecttrackingsystems:Inthissection,wereviewseveralissuetrackes:JIRA,Bugzilla,Redmine,MantisBT,andGitHubissues.Staticanalysis:Inthissection,ontheonehandwereviewseveralautomatedanalysistools(linters)suchasCheckstyle,FindBugs,PMD,andSonarQube.Ontheotherside,wedescribeseveralpeerreviewtools,suchasCollaborator,Crucible,Gerrit,andGitHubpullrequestsreviews.Puttingall,piecestogether:Toconcludethebook,inthefinalsectionwepresentacompleteexampleapplicationinwhichdifferenttypesoftests(unit,integration,andend-to-end)areperformedusingsomeofthemainconceptspresentedalongthisbook.
SoftwaredevelopmentprocessesInsoftwareengineering,thesoftwaredevelopmentprocess(alsoknownasthesoftwaredevelopmentlifecycle)isthenamegiventotheworkflowfortheactivities,actions,andtasksrequiredtocreatesoftwaresystems.AsintroducedinChapter6,FromRequirementstoTestCases,theusualphasesinanysoftwaredevelopmentprocessare:
Definitionofwhat:Requirementselicitation,analysisandusecasemodeling.Definitionofhow:Thesystemarchitectureandmodelingofstructuralandbehavioraldiagrams.Theactualsoftwaredevelopment(coding).Thesetofactivitiesthatmakesthesoftwareavailableforuse(release,installation,activation,andsoon).
Thetiminginwhichtestsaredesignedandimplementedintheoverallsoftwaredevelopmentprocessresultsindifferenttestmethodologies,namely(seediagramafterthelist):
Behavior-DrivenDevelopment(BDD):Atthebeginningoftheanalysisphase,conversationsbetweenthesoftwareconsumer(finaluserorcostumer)andsomeofthedevelopmentteam(typically,projectleader,manager,oranalysts)tookplace.Theseconversationsareusedtoconcretizescenarios(thatis,concreteexamplestobuildupacommonunderstandingofthesystemfeatures).TheseexamplesformthebasistodevelopacceptancetestsusingtoolssuchasCucumber(formoredetailsaboutit,takealooktoChapter5,IntegrationofJUnit5withexternalframeworks.)ThedescriptionofacceptancetestsinBDD(forexample,usingGherkininCucumber)producesbothautomatedtestsanddocumentationthataccuratelydescribetheapplicationfeatures.TheBDDapproachisnaturallyalignedwithiterativeorAgilemethodologies,sinceitisverydifficulttodefinerequirementsupfront,andtheseevolveastheteamlearnsmoreabouttheproject.
ThetermagilewaspopularizedwiththeinceptionoftheAgilemanifestoin2001(http://agilemanifesto.org/).Itwaswrittenby17softwarepractitioners(KentBeck,JamesGrenning,RobertC.
Martin,MikeBeedle,JimHighsmith,SteveMellor,ArievanBennekum,AndrewHunt,KenSchwaber,AlistairCockburn,RonJeffries,JeffSutherland,WardCunningham,JonKern,DaveThomas,MartinFowler,andBrianMarick),andincludesalistof12principlestoguideaniterativeandpeople-centricsoftwaredevelopmentprocess.Basedontheseprinciples,severalsoftwaredevelopmentframeworksemerged,suchasSCRUM,Kanban,orextremeprogramming(XP).
Test-DrivenDevelopment(TDD):TDDisamethodologyinwhichtestsaredesignedandimplementedbeforetheactualsoftwaredesign.Theideaistoconverttherequirementsobtainedintheanalysisstagetospecifictestcases.Then,thesoftwareisdesignedandimplementedtopassthesetests.TDDispartoftheXPmethodology.Test-FirstDevelopment(TFD):Inthismethodology,testsareimplementedafterthedesignstage,butbeforetheactualimplementationoftheSUT.Thisallowstoassurethatthesoftwareunitshavebeenunderstoodcorrectlybeforeitsactualimplementation.ThismethodologyisfollowedintheUnifiedProcess,whichisapopulariterativeandincrementalsoftwaredevelopmentprocess.TheRationalUnifiedProcess(RUP)isawell-knownframeworkimplementationoftheUnifiedProcess.InadditiontoTFD,RUPalsosupportsothermethodologiessuchasTDDandTLD.
Test-LastDevelopment(TLD):Inthismethodology,theimplementationofthetestiscarriedoutaftertheimplementationoftheactualsoftware(SUT).Thistestmethodologyisfollowedbyclassicsoftwaredevelopmentprocesses,suchaswaterfall(sequential),incremental(multi-waterfall)orspiral(risk-orientedmulti-waterfall).
Testmethodologiesduringthesoftwaredevelopmentprocesses
Thereisnouniversalaccepteddefinitionsofthetermspresentedsofar.Theseconceptsaresubjecttocontinuousevolutionanddebate,justlikethesoftwareengineeringitself.Considerthistobeaproposal,whichfitsintoalargenumberofsoftware
projects.
Regardingwhoisresponsibleforcodingthetests,thereisauniversallyacceptedconsensus.ItisbroadlyrecommendedthatunittestsshouldbewrittenbySUTdevelopers.Insomecases,especiallyinsmallteams,thesedevelopersarealsoresponsibleforotherkindsoftests.
Inaddition,theroleofanindependenttestgroup(oftencalledtestersoraQAteam)isalsoacommonpractice,especiallyinlargeteams.Oneoftheobjectiveofthisroleseparationistoremovetheconflictofintereststhatmaybepresentotherwise.Wecannotforgetthattestingisunderstoodasadestructiveactivityfromaphysiologicalpointofview(theobjectiveisfindingdefects).Thisindependenttestgroupisusuallyinchargeontheintegration,system,andnon-functionaltests.Inthiscase,bothgroupsofengineersshouldworkclosely;whiletestsareconducted,developersshouldbeavailabletocorrectfaultsandminimizefutureerrors.
Finally,high-levelacceptancetestsareusuallyconductedinheterogeneousgroupsinvolvingnon-programmers(customers,businessanalysis,managers,andsoon)togetherwithsoftwareengineersortesters(forexample,forimplementthestepdefinitioninCucumber).
ContinuousIntegrationTheconceptofCIwasfirstcoinedon1991byGradyBooch(Americansoftwareengineer,bestknownforthedevelopmentofUMLtogetherwithIvarJacobsonandJamesRumbaugh).TheExtremeProgramming(XP)methodologyadoptedthisterm,makingitverypopular.AccordingtoMartinFowler,CIisdefinedasfollows:
ContinuousIntegrationisasoftwaredevelopmentpracticewheremembersofateamintegratetheirworkfrequently,usuallyeachpersonintegratesatleastdaily-leadingtomultipleintegrationsperday.Eachintegrationisverifiedbyanautomatedbuild(includingtest)todetectintegrationerrorsasquicklyaspossible.
InCIsystems,wecanidentifydifferentparts.First,weneedasourcecoderepository,whichisafilearchivetohostthesourcecodeofoursoftwareproject,typicallyusingaversioncontrolsystem.Nowadays,thepreferredversioncontrolsystemisGit(originallydevelopedbyLinusTorvalds)overoldersolutions,suchasCVSorSVN.Atthemomentofthiswriting,theleadingversioncontrolrepositoryisGitHub(https://github.com/),whichasitsnameindicatesitisbasedonGit.Besides,thereareotheralternatives,suchasGitLab(https://gitlab.com),BitBucket(https://bitbucket.org/),orSourceForge(https://sourceforge.net/).Thelatterwastheleadingforgeinthepast,butisnowadayslessused.
Acopyofthesourcecoderepositoryissynchronizedinthelocalenvironmentofdevelopers.Thecodingworkisdoneagainstthislocalcopy.Developersaresupposedtocommitnewchanges(knownaspatches)totheremoterepositoryinadailybasis.Frequentcommitsallowtoavoidconflicterrorsduetothemutualmodificationofthesamepartsofagivenfile.
ThebasicideaofCIisthateverycommitshouldexecutethebuildandtestthesoftwarewiththenewchanges.Forthatreason,weneedaserver-sideinfrastructurewhichautomatesthisprocess.Thisinfrastructureisknownasbuildserver(ordirectlyCIserver).TwoofthemostimportantbuildserversnowadaysareJenkinsandTravisCI.Detailsofbothofthemareprovidedinnextsubsections.Asaresultofthebuildprocess,thebuildservershouldnotifytheresultoftheprocesstotheorigindeveloper.Iftestsweresuccessful,thepatchismergedinthecodebase:
ContinuousIntegrationprocess
ClosetoCI,thetermDevOpshasgainedmomentum.DevOpscomesfromdevelopmentandoperations,anditisthenamegiventoasoftwaredevelopmentprocessthatemphasizesthecommunicationandcollaborationdifferentteamsinaprojectsoftware:development(softwareengineering),QA(qualityassurance),andoperations(infrastructure).ThetermDevOpsisalsoreferredtoajobposition,typicallyinchargeofthesetup,monitoringanoperationofthebuildservers:
DevOpsareinbetweendevelopment,operationsandQA
Asshowninthenextfigure,theconceptofCIcanbeextendedto:
ContinuousDelivery:WhentheCIpipelinefinishcorrectly,atleastareleaseofsoftwarewillbedeployedtoatestenvironment(forinstance,deployinganSNAPSHOTartifacttoaMavenarchiver).Inthisphase,acceptancetestscanalsobeexecuted.ContinuousDeployment:Asthefinalstepintheautomationtoolchain,thereleaseofthesoftwarecanbereleasedtoaproductionenvironment(forexample,deployingawebapplicationtotheproductionserverforeachcommit,whichachievestopassthecompletepipeline).
JenkinsJenkins(https://jenkins.io/)isanopensourcebuildserverwhichsupportsbuilding,deploying,andautomatinganyproject.JenkinshasbeendevelopedinJava,anditcanbemanagedeasilyusingitswebinterface.TheglobalconfigurationofaJenkinsinstanceincludesinformationaboutJDK,Git,Maven,Gradle,Ant,andDocker.
JenkinswasoriginallydevelopedastheHudsonprojectbySunMicrosystemsin2004.AftertheacquisitionofSunbyOracle,theHudsonprojectwasforkedtoanopensourceproject,renamedtoJenkins.Bothnames(HudsonandJenkins)weremeanttosoundlikestereotypicalEnglishbutlernames.Theideaistheyhelpdeveloperscarryouttedioustasks,justlikeahelpfulbutler.
InJenkins,buildsaretypicallytriggeredbynewcommitsinversioncontrolsystems.Inaddition,buildscanbestartedbyothermechanisms,suchasscheduledcrontaskorevenmanuallyusingtheJenkinsinterface.
Jenkinsishighlyextensiblethankstoitspluginarchitecture.Thankstothose,Jenkinshasbeenextendedtoarichpluginecosystemmadebyvastnumberofthird-partyframeworks,libraries,systems,andsoon.Thisismaintainedbytheopensourcecommunity.TheJenkinspluginportfolioisavailableonhttps://plugins.jenkins.io/.
AttheheartofJenkins,wefindtheconceptofjob.AjobisarunnableentitymonitoredbyJenkins.Asshowninthescreenshothere,aJenkinsjobiscomposedoffourgroups:
Sourcecodemanagement:ThisistheURLofthesourcecoderepository(Git,SVN,andsoon)Buildtrigger:Thisisthemechanismstartingthebuildprocess,suchasnewchangesinthesourcecoderepository,externalscripts,periodically,andsoon.Buildenvironment:Optionalsetup,forexample,deleteworkspacebeforebuildstart,abortthebuildwhenstuck,andsoon.Collectionofstepsofthejobs:ThesestepscanbedonewithMaven,Gradle,Ant,orshellcommands.Afterthose,post-buildactionscanbeconfigured,forexample,toarchiveanartifact,topublishJUnittest
report(wewilldescribethisfeaturelaterinthischapter),emailnotifications,andsoon.
Jenkinsjobconfiguration
AnotherinterestingwayofconfiguringajobisusingaJenkinspipeline,whichisthedescriptionofthebuildworkflowusingthePipelineDSL(adomain-specificlanguagebasedonGroovy).AJenkinspipelinedescriptionistypicallystoredinafilecalledJenkinsfile,whichcanbeunderthecontrolofthesourcecoderepository.Inshort,aJenkinspipelineisdeclarativechainofstagescomposedofsteps.Forexample:
pipeline{
agentany
stages{
stage('Build'){
steps{
sh'make'
}
}
stage('Test'){
steps{
TravisCITravisCI(https://travis-ci.org/)isadistributedbuildserverusedtobuildandtestsoftwareprojectshostedonGitHub.Travissupportsopensourceprojectswithnocharge.
TheconfigurationofTravisCIisdoneusingafilenamed.travis.yaml.Thecontentofthisfileisstructuredusingdifferentkeywords,including:
language:Projectlanguage,thatis,java,node_js,ruby,python,orphpamongothers(thecompletelistisavailableonhttps://docs.travis-ci.com/user/languages/).sudo:Flagvaluetosetifsuperuserprivilegesareneeded(forexampletoinstallUbuntupackages).dist:BuildscanbeexecutedonLinuxenvironments(UbuntuPrecise12.04orUbuntuTrusty14.04).addons:Declarativeshortcutstobasicoperationsoftheapt-getcommands.install:FirstpartoftheTravisbuildlifecycle,inwhichtheinstallationoftherequireddependenciesisdone.Thispartcanbeoptionallyinitiatedusingbefore_install.script:Actualexecutionofthebuild.Thisphasecanbeoptionallysurroundedbybefore_scriptandafter_script.deploy:Finally,thedeploymentofthebuildcanbeoptionallymadeinthisphase.Thisstagehasitsownlifecyclecontrolledwithbefore_deployandafter_deploy.
YAMLislightweightmarkuplanguageusedbroadlyforconfigurationfilesduetoitsminimalistsyntax.ItwasoriginallydefinedasYetAnotherMarkupLanguage,butthenitwasrepurposedtoYAMLAin’tMarkupLanguagetodistinguishitspurposeasdataoriented.
Thefollowingsnippetshowsanexampleof.travis.yaml:language:java
sudo:false
dist:trusty
addons:
firefox:latest
apt:
packages:
-google-chrome-stable
sonarcloud:
organization:"bonigarcia-github"
token:
secure:"encripted-token"
before_script:
-exportDISPLAY=:99.0
-sh-e/etc/init.d/xvfbstart&
-sleep3
script:
-mvntestsonar:sonar
-bash<(curl-shttps://codecov.io/bash)
TravisCIprovidesawebdashboardinwhichwecancheckthestatusofthecurrentandpastbuildgeneratedintheprojectsusingTravisCIofourGitHubaccount:
TravisCIdashboard
TestreportingFromitsinitialversions,theJUnittestingframeworkintroducedanXMLfileformattoreporttheexecutionoftestsuites.Overtheyears,thisXMLformathasbecomeadefactostandardforreportingtestresults,broadlyadoptedinthexUnitfamily.
TheseXMLcanbeprocessedbydifferentprogramstodisplaytheresultsinahuman-friendlyformat.Thisisforexamplewhatbuildserversdo.Forexample,JenkinsimplementsatoolcalledJUnitResultArchiver,whichparsestoHTMLtheXMLfilesresultingfromthetestexecutionofajob.
DespitethefactthatthisXMLformathasbecomepervasive,thereisnouniversalformaldefinitionforit.JUnittestexecutors(forexample,Maven,Gradle,andsoon)usuallyuseitsownXSD(XMLSchemaDefinition).Forinstance,thestructureofthisXMLreportinMaven(http://maven.apache.org/surefire/maven-surefire-plugin/)isasdepictedinthefollowingdiagram.Notethatatestsuiteiscomposedbyasetofpropertiesandasetoftestcases.Eachtestcasecanbedeclaredasafailure(testwithsomeassertionfailed),skipped(testignored),andanerror(testwithanunexpectedexception).Ifnoneofthesestatesappearinthebodyofthetestsuite,thenthetestisinterpretedassuccessful.Finally,foreachtestcasetheXMLalsostoresthestandardoutput(system-out)andthestandarderroroutput(system-err):
SchemarepresentationforMavenSurefireXMLreports
ThererunFailureisacustomstateimplementedbyMavenSurefireforretryingflaky(intermittent)tests(http://maven.apache.org/surefire/maven-surefire-plugin/examples/rerun-failing-tests.html).
WithregardstoJUnit5,theMavenandGradlepluginsusedtorunJupitertests(maven-surefire-pluginandjunit-platform-gradle-pluginrespectively)writetheresultsofthetestexecutionfollowingthisXMLformat.Inthefollowingsections,wearegoingtoseehowtotransformthisXMLoutputtoahumanreadableHTMLreport.
MavenSurefireReportBydefault,maven-surefire-plugingeneratestheXMLresultingfromatestsuiteexecutionas${basedir}/target/surefire-reports/TEST-*.xml.ThisXMLoutputcanbeeasilyparsedtoHTMLusingthepluginmaven-surefire-report-plugin.Tothat,wesimplyneedtodeclarethisplugininthereportingclauseofourpom.xml,asfollows:
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>${maven-surefire-report-plugin.version}</version>
</plugin>
</plugins>
</reporting>
Thisway,whenweinvoquetheMavenlifecyclefordocumentation(mvnsite),anHTMLpagewiththetestresultwillbeincludedinthegeneralreport.
Seeanexampleofthereport,madeusingtheprojectjunit5-reportingwithintheGitHubrepositoryexamples(https://github.com/bonigarcia/mastering-junit5):
AllureAllure(http://allure.qatools.ru/)isalight-weightopensourceframeworkforgeneratingtestreportsfordifferentprogramminglanguages,includingJava,Python,JavaScript,Ruby,Groovy,PHP,.NET,andScala.Generaliyspeaking,AllureusestheXMLtestoutputandtransformsitinanHTML5-richreport.
AllureprovidessupportforJUnit5projects.ThiscanbedoneusingbothMavenandGradle.RegardingMaven,weneedtodoregisteralistenerinmaven-surefire-plugin.ThislistenerwillbetheclassAllureJunit5(locatedinthelibraryio.qameta.allure:allure-junit5),whichisbasicallyaimplementationoftheJUnit5’sTestExecutionListener.Asdescribedinchapter2,What’sNewInJUnit5,TestExecutionListenerispartoftheLauncherAPI,anditisusedtoreceiveeventsaboutthetestexecution.Allinall,thislistenerallowstoAlluretocompilethetestinformation,whileitisgeneratedintheJUnitplatform.ThisinformationisstoredasJSONfilesbyAllure.Afterthat,wecanusethepluginio.qameta.allure:allure-maventogeneratetheHTML5fromtheseJSONfiles.Thecommandsare:
mvntest
mvnallure:serve
Thecontentofourpom.xmlshouldcontainthefollowing:<dependencies>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<version>${allure-junit5.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<properties>
<property>
<name>listener</name>
<value>io.qameta.allure.junit5.AllureJunit5</value>
</property>
</properties>
<systemProperties>
<property>
<name>allure.results.directory</name>
<value>${project.build.directory}/allure-results</value>
</property>
</systemProperties>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>${allure-maven.version}</version>
</plugin>
</plugins>
</build>
ThesameprocesscanbedoneusingGradle,thistimeusingtheequivalentplugin,io.qameta.allure:allure-gradle.Allinall,thecontentofourbuild.gradlefileshouldcontain:
buildscript{
repositories{
jcenter()
mavenCentral()
}
dependencies{
classpath("org.junit.platform:junit-platform-gradle-
plugin:${junitPlatformVersion}")
classpath("io.qameta.allure:allure-gradle:${allureGradleVersion}")
}
}
applyplugin:'io.qameta.allure'
dependencies{
testCompile("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testCompile("io.qameta.allure:allure-junit5:${allureJUnit5Version}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
ThefollowingpictureshowsseveralscreenshotsoftheAllurereportgeneratedusingtheabove-mentionedsteps(thefinalresultisthesameusingMavenorGradle).Theprojectofthisexampleiscalledjunit5-allure,asusualhostedinGitHub.
Defect-trackingsystemsAdefect-trackingsystem(alsoknownasbugtrackingsystem,bugtracker,orissuetracker)isasoftwaresystemthatkeepstrackofreportedsoftwaredefectsinsoftwareprojects.Themainbenefitsofthiskindofsystemsistoprovideacentralizedoverviewofdevelopmentmanagement,bugreporting,andevenfeaturerequest.Itisalsocommontomaintainalistofpendingitems,oftencalledbacklog.
Thereareabunchofdefect-trackingsystemsavailable,bothproprietaryandopensource.Inthissection,wemakeabriefofseveralofthemostwell-known:
JIRA(https://www.atlassian.com/software/jira):Itisaproprietarydefect-trackingsystemcreatedbyAtlasian.Inadditiontobugandissuetracking,itprovidesmanagementscapabilitiessuchasSCRUMandKanbanboards,alanguagetoqueryissues(JIRAQueryLanguage),integrationwithexternalsystems(forexample,GitHub,Bitbucket),andanadd-onsmechanismtoextendJIRAwithpluginsfromtheAtlasianMarketplace(https://marketplace.atlassian.com/).Bugzilla(https://www.bugzilla.org/):Itisanopensourceweb-based,defect-trackingsystemdevelopedbytheMozillaFoundation.Amongitsfeatures,wecanfindadatabasedesignedtoimproveperformanceandscalability,querymechanismforsearchingdefects,integratede-mailcapabilities,anduserrolesmanagement.Redmine(http://www.redmine.org/):Itisanopensource,web-baseddefect-trackingsystem.Itprovideswikis,forums,timetracking,role-basedaccesscontrol,orGanttchartsforprojectmanagement.MantisBT(https://www.mantisbt.org/):Itisanotheropensource,web-baseddefecttrackingsystemdesignedtobesimplebuteffective.Amongitsfeatures,wecanhighlightitsevent-drivenpluginsystemtoallowsextensionsbothofficialthatthird-party,multi-channelnotificationsystem(e-mail,RSSfeed,Twitterplugin,andsoon),orrole-basedaccesscontrol.GitHubissues(https://guides.github.com/features/issues/):ItisthetrackingsystemintegratedineachGitHubrepository.TheapproachofGitHubissuesistoprovideagenerictrackingsystemfordefects,taskscheduling,discussions,andevenfeaturerequestusingGitHubissues.
StaticanalysisThisbook,whichisfinishingsoon,hasbeenfocusedonsoftwaretesting.Nosurprises,JUnitisabouttesting.ButasweseeninChapter1,RetrospectiveonsoftwarequalityandJavatesting,althoughsoftwaretestingisthemostcommonlyperformedactivitieswithinVerification&Validation(V&V),itisnottheonlytype.Theotherimportantgroupofactivitiesisstaticanalysis,inwhichthereisnoexecutionofthesoftwaretesting.
Therearedifferentactivitiesthatcanbecategorizedasstaticanalysis.Amongthem,theautomatedsoftwareanalysisisanalternativequiteinexpensiveintermsofrequiredeffort,anditcanhelptoincreasetheinternalcodequalitysignificantly.Inthischapter,wearegoingtoreviewseveralautomatedsoftwareanalysistools,knownaslinters,namely:
Checkstyle(http://checkstyle.sourceforge.net/):ItanalyzesJavacodefollowingdifferentrules,suchasmissingJavadoccomments,theuseofmagicnumbers,namingconventionsofvariablesandmethods,method’sargumentlengthandlinelengths,theuseofimports,thespacesbetweensomecharacters,thegoodpracticesofclassconstruction,orduplicatedcode.ItcanbeusedasEclipseorIntelliJplugin,amongothers.FindBugs(http://findbugs.sourceforge.net/):ItlooksforthreetypesoferrorswithinJavacode:
Correctnessbug:Apparentcodingmistake(forexample,classdefinesequal(Object)insteadofequals(Object).Badpractice:Violationsofrecommendedbestpractices(droppedexceptions,misuseoffinalize,andsoon).Dodgyerrors:Confusingcodeorwritteninawaythatleadstoerror(forexample,classliteralneverused,switchfallthrough,unconfirmedtypecasts,andredundantnullcheck.
PMD(https://pmd.github.io/):Itisacross-languagestaticcodeanalyzer,includingJava,JavaScript,C++,C#,Go,Groovy,Perl,PHP,amongothers.Ithasalotofplugins,includingMaven,Gradle,Eclipse,IntelliJ,andJenkins.SonarQube(https://www.sonarqube.org/):It(formerlyjustSonar)isaweb-based,opensourcecontinuousqualityassessmentdashboard.Itsupportsawidevarietyoflanguages,includingJava,C/C++,Objective-C,C#,andmanyothers.Offersreportsonduplicatedcode,codesmells,code
coverage,complexityandsecurityvulnerabilities.SonarQubehasadistributedflavorcalledSonarCloud(https://sonarcloud.io/).Itcanbeusedforfreeinopensourceprojects,providingaseamlessintegrationwithTravisCIthroughafewlinesofconfigurationin.travis.yml(seethefollowingsnippet),includingtheSonarCloudorganizationidentifierandsecuretoken.TheseparameterscanbeobtainedintheSonarCloudwebadministrationpanel,afterassociatingoutSonarCloudaccountwithGitHub.
addons:
sonarcloud:
organization:"bonigarcia-github"
token:
secure:"encrypted-token"
Afterthat,wesimplyneedtocallSonarCloud,usingMavenorusingGradle:script:
-mvntestsonar:sonar
script:
-gradletestsonarQube
ThefollowingpictureshowstheSonarClouddashboardfortheexampleapplicationRatemycat!,describedinthelastsectionofthischapter:
SonarCloudreportfortheapplicationRatemycat!
Anotheranalysisstatictechniquehighlyadoptedinmanysoftwareprojectsispeerreview.Thismethodisquiteexpensiveintermsoftimeandeffortrequired,butwhencorrectlyapplied,itallowstomaintainverygoodlevelsofinternalcodequality.Nowadaysthereisawiderangeoftoolsaimedtoeasethepeerreviewprocessofsoftwarecodebase.Amongothers,wefindthefollowing:
Collaborator(https://smartbear.com/product/collaborator/):Peercode(anddocumentation)reviewproprietytoolcreatedbythecompanySmartBear.Crucible(https://www.atlassian.com/software/crucible):On-premisescodereviewproprietytoolforenterpriseproducts,createdbyAtlassian.Gerrit(https://www.gerritcodereview.com/):Web-basedcodecollaborationopensourcetool.ItcanbeusedwithGitHubrepositorythroughGerritHub(http://gerrithub.io/).GitHubpullrequestreviews(https://help.github.com/articles/about-pull-request-reviews/):InGitHub,apullrequestisamethodforsubmittingcontributionsinthird-partyrepositories.AspartofthecollaborativetoolsprovidedbyGitHub,pullrequestsallowsreviewsandcommentsinaeasyandintegratedfashion.
PuttingallpiecestogetherInthislastsectionofthebook,wearegoingtoreviewsomeofthemajoraspectscoveredinthisbookwithapracticalexample.Tothataim,acompleteapplicationisdevelopedtogetherwithdifferenttypesoftestsimplementedwithJUnit5.
FeaturesandrequirementsThehistoryofourapplicationbeginswithahypotheticalperson,whichlovescats.Thispersonownsaclowder,andhe/shewouldliketogetfeedbackaboutthemfromtheexternalworld.Forthatreason,thisperson(wecanhim/herourclientfromnowon)contactswithustoimplementawebapplicationwhichsatisfieshis/herneeds.Thenameforthatapplicationwillbe“Ratemycat!”.Inaconversationwiththeclient,weelicitafollowinglistoffeaturesfortheapplicationtobedeveloped:
F1:Eachusershallratealistofcatsbywatchingitsnameandpicture.F2:Therateshallbedoneonceperuserusingastarmechanism(from0.5to5starspercat)andoptionallycommentscouldbeincludedpercat.
Aspartoftheanalysisphaseinourdevelopmentprocess,thosefeaturesarerefinedasalistoffunctionalrequirements(FR)asfollows:
FR1:Theapplicationpresentsalistofcats(composedbynameandpicture)totheenduser.FR2:Eachcatcanberatedindividually.FR3:Therangeforratingcatsisanintervalfrom0.5to5(stars).FR4:Optionallytothenumericratepercat,usersshallincludesomecomments.FR5:Eachenduseronlyshallrateeachcat(commentsand/orstars)once.
DesignSinceourapplicationisquitesimple,wedecidetostoptheanalysisphasehere,withoutmodelingourrequirementsasusecases.Instead,wemoveonmakingahigh-levelarchitecturaldesignofthewebapplicationusingtheclassicalthree-tiermodel:presentation,application(orbusiness)logic,anddatatier.Regardingtheapplicationlogic,asthefollowingpicturedepicts,twocomponentsareneeded.Firstone,calledCatServiceischargeofalltheratingactionsasdescribedintherequirementslist.Secondone,calledCookiesServicesisneededtohandleHTTPCookies,neededtoimplementFR5:
High-levelarchitecturaldesignfortheapplicationRatemycat!
Atthisstage,inthedevelopment,weareabletodecidethemajortechnologiesimpliedintheimplementationourapplication:
Spring5:Thiswillbethefoundationframeworkforourapplication.Concretely,weuseSpringMVCthroughSpringBoottosimplifythecreationofourwebapplication.Moreover,weuseSpringDataJPAusingasimpleH2databasetopersisttheapplicationdata,andThymeleaf(http://www.thymeleaf.org/)astemplateengine(forviewsinMVC).Finally,wealsousetheSpringTestmoduletomakein-containerintegrationtestsinaneasyway.JUnit5:Ofcourse,wecannotuseadifferenttestingframeworkthanJUnit5forourtestscases.Moreover,toimprovethereadabilityofourassertionsweuseHamcrest.Mockito:Inordertoimplementunittestcases,wewillusetheMockitoframework,isolatingtheSUTfromitsDOCsinseveralout-of-containerunittests.SeleniumWebDriver:Wewillalsoimplementdifferentend-to-endtestsusingSeleniumWebDrivertoexerciseourwebapplicationfromJUnit5
tests.GitHub:OursourcecoderepositorywillbehostedinapublicGitHubrepository.TravisCI:OurtestsuitewillbeexecutedeachtimeanewpatchiscommittedtoourGitHubrepository.Codecov:TotrackthecodecoverageofourtestsuitewewilluseCodecov.SonarCloud:Toprovideacompleteassessmentoftheinternalqualityofoursourcecode,wecomplementourtestprocesswithsomeautomaticstaticanalysisusingSonarCloud.
ThescreenshothereshowstheapplicationGUIinaction.Itisnotthemainobjectiveofthissectiontodigdeeperintheimplementationspecificsoftheapplication.VisittheGitHubrepositoryoftheapplicationonhttps://github.com/bonigarcia/rate-my-catfordetailsaboutit.
ScreenshotoftheapplicationRatemycat!
Thepicturesusedtoimplementthisexamplehavebeendownloadedfromthefreeimagesgalleryavailableonhttps://pixabay.com/.
TestsLet’sfocusnowontheJUnit5testsofthisapplication.Weimplementthreetypesoftests:unit,integration,andendtoend.Asintroducedbefore,fortheunittest,weuseMockitotoexercisetheSUTinisolation.Wedecidetounittestthetwomajorcomponentsofourapplication(CatServiceandCookiesServices)usingJavaclassescontainingdifferentJUnit5tests.
Considerthefirsttest(calledRateCatsTest).Ascanbeseenthecode,inthisclasswearedefiningtheclassCatServiceastheSUT(usingtheannotation@InjectMocks)andtheclassCatRepository(whichisusedbyCatServicewithdependencyinjection)astheDOC(usingtheannotation@Mock).Thefirsttestofthisclass(testCorrectRangeOfStars)isanexampleofparameterizedJUnit5tests.TheobjectiveofthistestiftoassesstheratemethodinsideCatService(methodrateCate).Inordertoselectthetestdata(input)forthistest,wefollowablack-boxstrategyandthereforeweusetheinformationoftherequirementsdefinition.Concretely,FR3statestherangeofstarstobeusedintheratingmechanismforcats.Followingaboundaryanalysisapproach,weselecttheedgesoftheinputrange,thatis,0.5and5.Thesecondtestcase(testCorrectRangeOfStars)alsoteststhesamemethod(rateCat),butthistimethetestevaluatestheSUTresponsewhenout-of-rangeinputsexercisetheSUT(negativetestscenario).Then,twomoretestsareimplementedinthisclass,thistimeaimedtoassessFR4(thatis,usingalsocommentstoratecats).NoticethatweareusingtheJUnit5@Tagannotationtoidentifyeachtestwithitscorrespondingrequirement:
packageio.github.bonigarcia.test.unit;
importstaticorg.hamcrest.CoreMatchers.equalTo;
importstaticorg.hamcrest.MatcherAssert.assertThat;
importstaticorg.hamcrest.text.IsEmptyString.isEmptyString;
importstaticorg.junit.jupiter.api.Assertions.assertThrows;
importstaticorg.mockito.ArgumentMatchers.any;
importstaticorg.mockito.Mockito.when;
importjava.util.Optional;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.junit.jupiter.params.ParameterizedTest;
importorg.junit.jupiter.params.provider.ValueSource;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.Cat;
importio.github.bonigarcia.CatException;
importio.github.bonigarcia.CatRepository;
importio.github.bonigarcia.CatService;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
@DisplayName("Unittests(black-box):ratingcats")
@Tag("unit")
classRateCatsTest{
@InjectMocks
CatServicecatService;
@Mock
CatRepositorycatRepository;
//Testdata
Catdummy=newCat("dummy","dummy.png");
intstars=5;
Stringcomment="foo";
@ParameterizedTest(name="Ratingcatwith{0}stars")
@ValueSource(doubles={0.5,5})
@DisplayName("Correctrangeofstarstest")
@Tag("functional-requirement-3")
voidtestCorrectRangeOfStars(doublestars){
when(catRepository.save(dummy)).thenReturn(dummy);
CatdummyCat=catService.rateCat(stars,dummy);
assertThat(dummyCat.getAverageRate(),equalTo(stars));
}
@ParameterizedTest(name="Ratingcatwith{0}stars")
@ValueSource(ints={0,6})
@DisplayName("Incorrectrangeofstarstest")
@Tag("functional-requirement-3")
voidtestIncorrectRangeOfStars(intstars){
assertThrows(CatException.class,()->{
catService.rateCat(stars,dummy);
});
}
@Test
@DisplayName("Ratingcatswithacomment")
@Tag("functional-requirement-4")
voidtestRatingWithComments(){
when(catRepository.findById(any(Long.class)))
.thenReturn(Optional.of(dummy));
CatdummyCat=catService.rateCat(stars,comment,0);
assertThat(catService.getOpinions(dummyCat).iterator().next()
.getComment(),equalTo(comment));
}
@Test
@DisplayName("Ratingcatswithemptycomment")
@Tag("functional-requirement-4")
voidtestRatingWithEmptyComments(){
when(catRepository.findById(any(Long.class)))
.thenReturn(Optional.of(dummy));
CatdummyCat=catService.rateCat(stars,dummy);
assertThat(catService.getOpinions(dummyCat).iterator().next()
.getComment(),isEmptyString());
}
}
Next,unittestevaluatesthecookiesservice(FR5).Tothataim,thefollowingtestusetheclassCookiesServiceasSUT,andthistimewearegoingtomockthestandardJavaobject,whichmanipulatestheHTTPCookies,thatis,javax.servlet.http.HttpServletResponse.Inspectingthesourcecodeofthistest
class,wecanseethatthefirsttestmethod(calledtestUpdateCookies)exercisetheservicemethodupdateCookies,verifyingwhetherornottheformatofthecookiesisasexpected.Nexttwotests(testCheckCatInCookiesandtestCheckCatInEmptyCookies)evaluatesthemethodisCatInCookiesoftheserviceusingapositivestrategy(thatistheinputcatcorrespondswiththeformatofthecookie)andanegativeone(theoppositecase).Finally,thelasttwotests(testUpdateOpinionsWithCookiesandtestUpdateOpinionsWithEmptyCookies)exercisethemethodupdateOpinionsWithCookiesValueoftheSUTfollowingthesameapproach,thatis,checkingtheresponseoftheSUTusingavalidandemptycookie.Allthesetestshavebeenimplementedfollowingawhite-boxstrategy,sinceitstestdataandlogicreliescompletelyinthespecificinternallogicoftheSUT(inthiscasehowthecookiesareformattedandmanaged).
Thistestdoesnotfollowpurewhite-boxapproachinthesenseofitsobjectiveistoexerciseallthepossiblepathswithintheSUT.Itcanbeseenaswhite-boxinthesenseofithasbeendesigneddirectlylinkedtotheimplementationratherthantherequirements.
packageio.github.bonigarcia.test.unit;
importstaticorg.hamcrest.CoreMatchers.containsString;
importstaticorg.hamcrest.CoreMatchers.equalTo;
importstaticorg.hamcrest.CoreMatchers.not;
importstaticorg.hamcrest.MatcherAssert.assertThat;
importstaticorg.hamcrest.collection.IsEmptyCollection.empty;
importstaticorg.mockito.ArgumentMatchers.any;
importstaticorg.mockito.Mockito.doNothing;
importjava.util.List;
importjavax.servlet.http.Cookie;
importjavax.servlet.http.HttpServletResponse;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.mockito.InjectMocks;
importorg.mockito.Mock;
importio.github.bonigarcia.Cat;
importio.github.bonigarcia.CookiesService;
importio.github.bonigarcia.Opinion;
importio.github.bonigarcia.mockito.MockitoExtension;
@ExtendWith(MockitoExtension.class)
@DisplayName("Unittests(white-box):handlingcookies")
@Tag("unit")
@Tag("functional-requirement-5")
classCookiesTest{
@InjectMocks
CookiesServicecookiesService;
@Mock
HttpServletResponseresponse;
//Testdata
Catdummy=newCat("dummy","dummy.png");
StringdummyCookie="0#0.0#_";
@Test
@DisplayName("Updatecookiestest")
voidtestUpdateCookies(){
doNothing().when(response).addCookie(any(Cookie.class));
Stringcookies=cookiesService.updateCookies("",0L,0D,"",
response);
assertThat(cookies,
containsString(CookiesService.VALUE_SEPARATOR));
assertThat(cookies,
containsString(Cookies.CAT_SEPARATOR));
}
@Test
@DisplayName("Checkcatincookies")
voidtestCheckCatInCookies(){
booleancatInCookies=cookiesService.isCatInCookies(dummy,
dummyCookie);
assertThat(catInCookies,equalTo(true));
}
@DisplayName("Checkcatinemptycookies")
@Test
voidtestCheckCatInEmptyCookies(){
booleancatInCookies=cookiesService.isCatInCookies(dummy,"");
assertThat(catInCookies,equalTo(false));
}
@DisplayName("Updateopinionswithcookies")
@Test
voidtestUpdateOpinionsWithCookies(){
List<Opinion>opinions=cookiesService
.updateOpinionsWithCookiesValue(dummy,dummyCookie);
assertThat(opinions,not(empty()));
}
@DisplayName("Updateopinionswithemptycookies")
@Test
voidtestUpdateOpinionsWithEmptyCookies(){
List<Opinion>opinions=cookiesService
.updateOpinionsWithCookiesValue(dummy,"");
assertThat(opinions,empty());
}
}
Let’smoveontothenexttypeoftests:integration.Forthistypeoftest,wearegoingtousethein-containertestcapabilitiesprovidedbySpring.Concretely,weusetheSpringtestobjectMockMvctoevaluatetheHTTPresponsesofourapplicationfromtheclient-side.Ineachtest,differentrequestsareexercisedverifyingiftheresponses(statuscodeandcontenttype)areasexpected:
packageio.github.bonigarcia.test.integration;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
importstatic
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
importstatic
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
importorg.springframework.test.web.servlet.MockMvc;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@DisplayName("Integrationtests:HTTPreponses")
@Tag("integration")
@Tag("functional-requirement-1")
@Tag("functional-requirement-2")
classWebContextTest{
@Autowired
MockMvcmockMvc;
@Test
@DisplayName("Checkhomepage(GET/)")
voidtestHomePage()throwsException{
mockMvc.perform(get("/")).andExpect(status().isOk())
.andExpect(content().contentType("text/html;charset=UTF-8"));
}
@Test
@DisplayName("Checkratecat(POST/)")
voidtestRatePage()throwsException{
mockMvc.perform(post("/").param("catId","1").param("stars","1")
.param("comment","")).andExpect(status().isOk())
.andExpect(content().contentType("text/html;charset=UTF-8"));
}
@Test
@DisplayName("Checkratecat(POST/)ofannon-existingcat")
voidtestRatePageCatNotAvailable()throwsException{
mockMvc.perform(post("/").param("catId","0").param("stars","1")
.param("comment","")).andExpect(status().isOk())
.andExpect(content().contentType("text/html;charset=UTF-8"));
}
@Test
@DisplayName("Checkratecat(POST/)withbadparameters")
voidtestRatePageNoParameters()throwsException{
mockMvc.perform(post("/")).andExpect(status().isBadRequest());
}
}
Finally,wealsoimplementseveralend-to-endtestsusingSeleniumWebDriver.Inspectingtheimplementationofthistest,wecanseethatthistestisusingtwoJUnit5extensionsatthesametime:SpringExtension(tostart/stoptheSpringcontextwithintheJUnit5tests’lifecycle)andSeleniumExtension(toinjectWebDriverobjectsaimedtocontrolwebbrowsersinthetestmethods).Inparticular,weusethreedifferentbrowsersinoneofthetests:
PhantomJS(headlessbrowser),toassessisthelistofcatsisproperlyrenderedinthewebGUI(FR1).Chrome,toratecatsusingthroughtheapplicationGUI(FR2).Firefox,toratecatsusingtheGUIbutgettinganerrorasaresult(FR2).
packageio.github.bonigarcia.test.e2e;
importstaticorg.hamcrest.CoreMatchers.containsString;
importstaticorg.hamcrest.CoreMatchers.equalTo;
importstaticorg.hamcrest.MatcherAssert.assertThat;
importstatic
org.openqa.selenium.support.ui.ExpectedConditions.elementToBeClickable;
importstatic
org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
importjava.util.List;
importorg.junit.jupiter.api.DisplayName;
importorg.junit.jupiter.api.Tag;
importorg.junit.jupiter.api.Test;
importorg.junit.jupiter.api.extension.ExtendWith;
importorg.openqa.selenium.By;
importorg.openqa.selenium.WebElement;
importorg.openqa.selenium.chrome.ChromeDriver;
importorg.openqa.selenium.firefox.FirefoxDriver;
importorg.openqa.selenium.phantomjs.PhantomJSDriver;
importorg.openqa.selenium.support.ui.WebDriverWait;
importorg.springframework.boot.test.context.SpringBootTest;
importorg.springframework.boot.web.server.LocalServerPort;
importorg.springframework.test.context.junit.jupiter.SpringExtension;
importio.github.bonigarcia.SeleniumExtension;
@ExtendWith({SpringExtension.class,SeleniumExtension.class})
@SpringBootTest(webEnvironment=RANDOM_PORT)
@DisplayName("E2Etests:userinterface")
@Tag("e2e")
publicclassUserInferfaceTest{
@LocalServerPort
intserverPort;
@Test
@DisplayName("ListcatsintheGUI")
@Tag("functional-requirement-1")
publicvoidtestListCats(PhantomJSDriverdriver){
driver.get("http://localhost:"+serverPort);
List<WebElement>catLinks=driver
.findElements(By.className("lightbox"));
assertThat(catLinks.size(),equalTo(9));
}
@Test
@DisplayName("RateacatusingtheGUI")
@Tag("functional-requirement-2")
publicvoidtestRateCat(ChromeDriverdriver){
driver.get("http://localhost:"+serverPort);
driver.findElement(By.id("Baby")).click();
StringfourStarsSelector="#form1span:nth-child(4)";
newWebDriverWait(driver,10)
.until(elementToBeClickable
(By.cssSelector(fourStarsSelector)));
driver.findElement(By.cssSelector(fourStarsSelector)).click();
driver.findElement(By.xpath("//*[@id=\"comment\"]"))
.sendKeys("Verynicecat");
driver.findElement(By.cssSelector("#form1>button")).click();
WebElementsucessDiv=driver
.findElement(By.cssSelector("#success>div"));
assertThat(sucessDiv.getText(),containsString("Yourvotefor
Baby"));
}
@Test
@DisplayName("RateacatusingtheGUIwitherror")
@Tag("functional-requirement-2")
publicvoidtestRateCatWithError(FirefoxDriverdriver){
driver.get("http://localhost:"+serverPort);
driver.findElement(By.id("Baby")).click();
StringsendButtonSelector="#form1>button";
newWebDriverWait(driver,10).until(
elementToBeClickable(By.cssSelector(sendButtonSelector)));
driver.findElement(By.cssSelector(sendButtonSelector)).click();
WebElementsucessDiv=driver
.findElement(By.cssSelector("#error>div"));
assertThat(sucessDiv.getText(),containsString(
"Youneedtoselectsomestarsforratingeachcat"));
}
}
Inordertomakeeasierthetraceabilityofthetestexecutions,inalltheimplementedtest,[email protected],forparameterizedtests,weusetheelementnametorefinethetestnameofeachexecutionofthetest,dependingonthetestinput.ThefollowingscreenshotoftheexecutionofthetestsuiteinEclipse4.7(Oxygen):
ExecutionofthetestsuitefortheapplicationRatemycat!inEclipse4.7
Asintroducedbefore,weuseTravisCIasbuildservertoexecuteourtestsduringthedevelopmentprocess.IntheconfigurationofTravisCI(file.travis.yml),wesetuptwoadditionaltoolstoenhancethedevelopmentandtestprocessofourapplication.Ontheonehand,Codecovprovidesacomprehensivetestcoveragereport.Ontheotherhand,SonarCloudprovidesacompletestaticanalysis.BothtoolsaretriggeredbyTravisCIaspartofthecontinuousintegrationbuildprocess.Asaresult,wecanevaluateboththecoveragetestandtheinternalcodequalityofourapplication(suchascode
smells,duplicatedblocks,ortechnicaldebt)alongwithourdevelopmentprocess.
ThefollowingpictureshowsascreenshotoftheonlinereportprovidedbyCodecov(thereportprovidedbySonarCloudwaspresentedintheprevioussectionofthischapter):
\
CodecovreportfortheapplicationRatemycat!
Lastbutnotleast,weareusingseveralbadgesintheREADMEofourGitHubrepository.Concretely,weaddbadgesforTravisCI(statusofthelastbuildprocess),SonarCloud(statusofthelastanalysis),andCodecov(percentageofthelastcodecoverageanalysis):
SummaryInthischapter,wereviewedseveralconcernsaboutthemanagementsideofthetestingactivities.First,welearnedthattestingcanbemadeindifferentpartsofthesoftwaredevelopmentprocess(softwarelifecycle)dependingonthetestmethodology:BDD(acceptancetestsaredefinedbeforetherequirementanalysis),TDD(testsaredefinedbeforethedesignofthesystem),TFD(testsareimplementedafterthesystemdesign),andTLD(testsareimplementedafterthesystemimplementation).
CIisaprocessmoreandmoreusedinsoftwaredevelopment.Itconsistsontheautomatedbuildandtestofacodebase.Thisprocessistypicallytriggeredwithanewcommitinasourcecoderepository,suchasGitHub,GitLab,orBitbucket.CIisextendedtoContinuousDelivery(whenreleasesaremadetodevelopmentenvironment)andtoContinuousDeployment(whendeploymenttoproductionenvironmentismadecontinuously).Wereviewedtwoofthemostusedbuildserversnowadays:Jenkins(CIasaService)andTravis(in-premises).
Theresomeothertoolsthatcanbeusedtoimprovethemanagementoftests,forexample,reportingtools(suchasMavenSurefireReportorAllure)ordefecttrackingsystems(suchasJIRA,Bugzilla,Redmine,MantisBT,andGitHubissues).Automatedstaticanalysisisagreatcomplementtotesting,forexample,usinglinterssuchasCheckstyle,FindBugs,PMD,orSonarQube,andalsopeerreviewtoolssuchasCollaborator,Crucible,Gerrit,andGitHubpullrequestsreviews.
Toclosethisbook,thefinalsectionofthischapterpresentsacompletewebapplication(namedRatemycat!)anditscorrespondingJUnit5tests(unit,integration,andend-to-end).Itconsistsonawebapplicationsdevelopedandassessedusingdifferenttechnologiespresentedthroughoutthebook,namely,Spring,Mockito,Selenium,Hamcrest,TravisCI,Codecov,andSonarCloud.