Upload
others
View
5
Download
0
Embed Size (px)
Citation preview
IMPROVE UNIT TESTING FOR JAVA WITH AUTOMATION
BEST PRACTICES FOR JAVA DEVELOPERS
03 Introduction:ImproveUnitTesting 03 WhatIsUnitTesting?
03 ImproveUnitTestingWithAutomatedTestingTools
04 BestPracticesforDevelopers 04 UnitTestingBestPractices:HowtoGettheMost outofYourTestAutomation
10 JUnitTutorial:SettingUp,Writing,andRunningJavaUnitTests
17 MockinginJava:HowtoAutomateaJavaUnitTest, IncludingMockingandAssertions
24 HowtoCreateJUnitParameterizedTests
34 GetMoreOutofUnitTestingandReduceMaintenance EffortsWithRuntimeAnalysis
Table of Contents
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
2
Introduction: Improve Unit TestingWHAT IS UNIT TESTING?Unittestingisthepracticeoftestingindividualunitsorcomponentsofanapplicationinordertovalidatethateachofthoseunitsisworkingproperly.Generally,aunitshouldbeasmallpartoftheapplication—inJavait'softenasingleclass.Notethatthereisnostrictdefinitionof"unit"here,anditisuptothedevelopertodecidethescopeoftestedcodeforeachtest.
Peoplesometimescontrasttheterm"unittesting"with"integrationtesting"or"end-to-endtesting".Thedifferenceisthat,generally,unittestingisdonetovalidatethebehaviorofanindividualtestableunit,whereasintegrationtestsarevalidatingthebehaviorofmultiplecomponentstogether,ortheapplicationasawhole.Asmentionedabove,thedefinitionforwhatconstitutesa"unit"isnotstrictlydefined,andit'suptoyoutodecidethescopeforeachtest.Ifthescopeistoobroad,itmaynotbepossibletodeterminewhyatestfailureoccurred.
Withthesechallenges,unittestingjustisn'teasy.Itrequiresalotofdevelopmentskillandeffort,andittakescommitmentandtimetomaintaintestsuites.ThisebookprovideshelpfultipsandtechniquesaswellasbestpracticestohelpyouimproveunittestingwithJUnitandParasoftJtest.
IMPROVE UNIT TESTING WITH AUTOMATED TESTING TOOLS Withautomatedtestingtools,developersareabletoreducelate-cycledefectswithbetterunittestsandautomatedstaticcodeanalysis.Theycanfocusmoretimeonnewfeaturedevelopmentforthebusiness.Developersalsobenefitfromimmediatefeedback.They'reabletorapidlyidentifywhethertheircodechangesarebreakingfunctionalityintheapplicationandaddressingitquickly.
Parasofthasfocusedonimprovingautomatedtestingforover30years.ParasoftJtestisakeyenablerofdeliveringqualityatspeedforunittesting.ThisintegratedJavasolutionenablesdevelopmentteamstobeagileanddeliverfasterwithoutsacrificingquality,makingthebusinesssuccessful.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
3
Best Practices for DevelopersUNIT TESTING BEST PRACTICES: HOW TO GET THE MOST OUT OF YOUR TEST AUTOMATION Unittestingisawell-knownpractice,butthere'slotsofroomforimprovement!Thissectionwillcoverthemosteffectiveunittestingbestpractices,includingapproachesformaximizingyourautomationtoolsalongtheway.Itwillalsodiscusscodecoverage,mockingdependencies,andoveralltestingstrategies.
WHYUNITTEST? Unittestingisaproventechniqueforensuringsoftwarequality,withplentyofbenefits.Hereare(morethan)afewgreatreasonstounittest:
» Unittestingvalidatesthateachpieceofyoursoftwarenotonlyworksproperlytoday,butcontinuestoworkinthefuture,providingasolidfoundationforfuturedevelopment.
» Unittestingidentifiesdefectsatearlystagesoftheproductionprocess,whichreducesthecostsoffixingtheminlaterstagesofthedevelopmentcycle.
» Unit-testedcodeisgenerallysafertorefactor,sincetestscanbere-runquicklytovalidatethatbehaviorhasnotchanged.
» Writingunittestsforcesdeveloperstoconsiderhowwelltheproductioncodeisdesignedinordertomakeitsuitableforunittesting,andmakesdeveloperslookattheircodefromadifferentperspective,encouragingthemtoconsidercornercasesanderrorconditionsintheirimplementation.
» Includingunittestsinthecodereviewprocesscanrevealhowthemodifiedornewcodeissupposedtowork.Plus,reviewerscanconfirmwhetherthetestsaregoodonesornot.
It'sunfortunatethatalltoooften,developerseitherdon'twriteunittestsatall,don'twriteenoughtests,ortheydon'tmaintainthem.Unittestscansometimesbetrickytowrite,ortime-consumingtomaintain.Sometimesthere'sadeadlinetomeet,anditfeelslikewritingtestswillmakeusmissthatdeadline.Butnotwritingenoughunittestsornotwritinggoodunittestsisariskytraptofallinto.
Sopleaseconsiderthefollowingbest-practicerecommendationsonhowtowriteclean,maintainable,automatedteststhatgiveyouallthebenefitsofunittesting,withaminimumamountoftimeandeffort.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
4
UNITTESTINGBESTPRACTICES Let’slookatsomebestpracticesforbuilding,running,andmaintainingunittests,toachievethebestresults.
UnitTestsShouldBeTrustworthy Thetestmustfailifthecodeisbrokenandonlyifthecodeisbroken.Ifitdoesn't,wecannottrustwhatthetestresultsaretellingus.
UnitTestsShouldBeMaintainableandReadable Whenproductioncodechanges,testsoftenneedtobeupdated,andpossiblydebuggedaswell.So,itmustbeeasytoreadandunderstandthetest,notonlyforwhoeverwroteit,butforotherdevelopersaswell.Alwaysorganizeandnameyourtestsforclarityandreadability.
UnitTestsShouldVerifyaSingleUseCase Goodtestsvalidateonethingandonethingonly,whichmeansthattypically,theyvalidateasingleuse-case.Teststhatfollowthisbestpracticearesimplerandmoreunderstandable,andthatisgoodformaintainabilityanddebugging.Teststhatvalidatemorethanonethingcaneasilybecomecomplexandtime-consumingtomaintain.Don'tletthishappen.
Anotherbestpracticeistouseaminimalnumberofassertions.Somepeoplerecommendjustoneassertionpertest(thismaybealittletoorestrictive);theideaistofocusonvalidatingonlywhatisneededfortheuse-caseyouaretesting.
UnitTestsShouldBeIsolated Testsshouldberunnableonanymachine,inanyorder,withoutaffectingeachother.Ifpossible,testsshouldhavenodependenciesonenvironmentalfactorsorglobal/externalstate.Teststhathavethesedependenciesarehardertorunandusuallyunstable,makingthemhardertodebugandfix,andendupcostingmoretimethantheysave(seetrustworthy,above).
MartinFowler,afewyearsago,wroteabout"solitary"vs"sociable"code,todescribedependencyusageinapplicationcode,andhowtestsneedtobedesignedaccordingly.Inhisarticle,"solitary"codedoesn'tdependonotherunits(it'smoreself-contained),whereas"sociable"codedoesinteractwithothercomponents.Iftheapplicationcodeissolitary,thenthetestissimple,butforsociablecodeundertest,youcaneitherbuilda"solitary"or"sociable"test.A"sociabletest"wouldrelyonrealdependenciesinordertovalidatebehavior,whereasa"Solitarytest"isolatesthecodeundertestfromdependencies.ThisisvisuallyshowninFigure1.Youcanusemockstoisolatethecodeundertestandbuilda"solitary"testfor"sociable"code.We'lllookathowtodothatbelow.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
5
Ingeneral,usingmocksfordependenciesmakesourlifeeasierastesters,becausewecangenerate"solitarytests"forsociablecode.Asociabletestforcomplexcodemayrequirealotofsetupandmayviolatetheprinciplesofbeingisolatedandrepeatable.Butsincethemockiscreatedandconfiguredinthetest,itisself-contained,andwehavemorecontroloverthebehaviorofdependencies.Plus,wecantestmorecodepaths.Forinstance,youcanreturncustomvaluesorthrowexceptionsfromthemock,inordertocoverboundaryorerrorconditions.
UnitTestsShouldBeAutomated Makesuretestsarebeingruninanautomatedprocess.Thiscanbedaily,oreveryhour,orinaContinuousIntegrationorDeliveryprocess.Thereportsneedtobeaccessibletoandreviewedbyeveryoneontheteam.Asateam,talkaboutwhichmetricsyoucareabout:codecoverage,modifiedcodecoverage,numberoftestsbeingrun,performance,etc.Alotcanbelearnedbylookingatthesenumbers,andabigshiftinthosenumbersoftenindicatesregressionsthatcanbeaddressedimmediately.
UseaGoodMixtureofUnitandIntegrationTests MichaelCohn'sbook,SucceedingwithAgile:SoftwareDevelopmentUsingScrum,addressesthisusingatestingpyramidmodel(seeillustrationinFigure2).Thisisacommonlyusedmodeltodescribetheidealdistributionoftestingresources.Theideaisthatasyougoupthepyramid,testsareusuallymorecomplextobuild,morefragile,slowertorun,andslowertodebug.Lowerlevelsaremoreisolatedandmoreintegrated,faster,andsimplertobuildanddebug.Therefore,automatedunittestsshouldmakeupthebulkofyourtests.
Figure 1: Comparison of Sociable and Solitary Tests. Source: Martin Fowler, 2014, "UnitTest"
Figure 2: Testing Pyramid Model
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
6
Unittestsshouldvalidateallofthedetails,thecornercasesandboundaryconditions,etc.Component,integration,UI,andfunctionaltestsshouldbeusedmoresparingly,tovalidatethebehavioroftheAPIsorapplicationasawhole.Manualtestsshouldbeaminimalpercentageoftheoverallpyramidstructurebutarestillusefulforreleaseacceptanceandexploratorytesting.Thismodelprovidesorganizationswithahighlevelofautomationandtestcoverage,sothattheycanscaleuptheirtestingeffortsandkeepthecostsassociatedwithbuilding,running,andmaintainingtestsataminimum.
UnitTestsShouldBeExecutedWithinanOrganizedTestPractice Inordertodrivethesuccessofyourtestingatalllevels,andmaketheunit testingprocessscalableandsustainable,youwillneedsomeadditionalpractices inplace.Firstofall,thismeanswritingunittestsasyouwriteyourapplicationcode.Someorganizationswritethetestsbeforetheapplicationcode(test-driven or behavior-drivenprogramming).Theimportantthingisthattestsgohand-in-handwiththeapplicationcode.Thetestsandapplicationcodeshouldevenbereviewedtogetherinthecodereviewprocess.Reviewshelpyouunderstandthecodebeingwritten(becausetheycanseetheexpectedbehavior)andimproveteststoo!
Writingtestsalongwithcodeisn'tjustfornewbehaviororplannedchanges,it’scriticalforbugfixestoo.Everybugyoufixshouldhaveatestthatverifiesthebug isfixed.Thisensuresthatthebugstaysfixedinthefuture.
Adoptazero-tolerancepolicyforfailingtests.Ifyourteamisignoringtestresults, thenwhyhavetestsatall?Testfailuresshouldindicaterealissuessoaddress thoseissuesrightaway—beforetheywasteQA'stime,orworse,theygetintothereleasedproduct.
Thelongerittakestoaddressfailures,themoretimeandmoneythosefailureswillultimatelycostyourorganization.So,youshouldruntestsduringrefactoring,runtestsrightbeforeyoucommitcode,anddon'tletataskbeconsidered"done"untilthetestsarepassingtoo.
Finally,maintainthosetests.Asmentionedpreviously,ifyou'renotkeepingthosetestsuptodatewhentheapplicationchanges,theylosetheirvalue.Especiallyiftheyarefailing,failingtestsarecostingtimeandmoneytoinvestigateeachtimetheyfail.Refactorthetestsasneeded,whenthecodechanges.
Asyoucansee,maximizingyourreturnsonmoneyandtimeinvestedinyourunittestsrequiressomeinvestmentinapplyingbestpractices.Butintheend,therewardsareworththeinitialinvestment.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
7
WHATABOUTCODECOVERAGE? Ingeneral,codecoverageisameasurementofhowmuchoftheproductioncodeisexecutedwhileyourautomatedtestsarerunning.Byrunningasuiteoftestsandlookingatcodecoveragedata,youcangetageneralsenseofhowmuchofyourapplicationisbeingtested.
Therearemanykindsofcodecoverage—themostcommononesarelinecoverageandbranchcoverage.Mosttoolsfocusonlinecoverage,whichjusttellsyouifaspecificlinewascovered.Branchismoregranular,asittellsyouifeachpaththroughthecodeiscovered.
Codecoverageisanimportantmetricbutrememberthatincreasingitisameanstoanend.It’sgreatforfindinggapsintesting,butit'snottheonlythingtofocuson.Becarefulnottospendtoomuchefforttryingtoachieve100%coverage–itmaynotevenbepossibleorfeasible,andreallythequalityofyourtestsistheimportantthing.Thatbeingsaid,achievingatleast60%coverageforyourprojectsisagoodstartingpoint,and80%ormoreisagoodgoaltoset.Obviously,it'suptoyoutodecidewhatthatgoalshouldbe.
It'salsovaluableifyouhaveautomatedtoolsthatnotonlymeasurecodecoveragebutalsokeeptrackhowmuchmodifiedcodeisbeingcoveredbytests,becausethiscanprovidevisibilityintowhetherenoughtestsarebeingwrittenalongwithchangesinproductioncode.
Figure3showsanexamplecodecoveragereportfromParasoftDTP,thereporting andanalyticshub,thatyoucannavigatethroughifyouareusingParasoftJtestforyourunittesting:
Figure 3: Example Code Coverage Report
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
8
Anotherthingtokeepinmindisthat,whenwritingnewtests,becarefuloffocusingonlinecoveragealone,assinglelinesofcodecanresultinmultiplecodepaths,somakesureyourtestsvalidatethesecodepaths.Linecoverageisausefulquickindicator,butitisn'ttheonlythingtolookfor.
Themostobviouswaytoincreasecoverageissimplytoaddmoretestsformore codepaths,andmoreusecasesofthemethodundertest.Apowerfulwaytoincreasecoverageistouseparameterizedtests.ForJUnit4,therewasthebuiltinJUnit4Parameterizedfunctionalityandthird-partylibrarieslikeJunitParams.JUnit5has built-inparameterization.
Finally,ifyouaren'talreadytrackingtestcoverage,wehighlyrecommendyoustart.Thereareplentyoftoolsthatcanhelp,likeParasoftJtest.Startbymeasuringyourcurrentcoveragenumbers,thensetgoalsforwhereitshouldbe,addressimportantgapsfirst,andthenworkfromthere.
SUMMARYOFUNITTESTINGBESTPRACTICES Althoughunittestingisaproventechniqueforensuringsoftwarequality,it’sstillconsideredaburdentodevelopersandmanyteamsarestillstrugglingwithit.Inordertogetthemostoutoftestingandautomatedtestingtools,testsmustbetrustworthy,maintainable,readable,self-contained,andbeusedtoverifyasingleusecase.Automationiskeytomakingunittestingworkableandscalable.
Inaddition,softwareteamsneedtopracticegoodtestingtechniques,suchaswritingandreviewingtestsalongsideapplicationcode,maintainingtests,andensuringthatfailedtestsaretrackedandremediatedimmediately.Adoptingtheseunittestingbestpracticescanquicklyimproveyourunittestingoutcomes.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
9
JUNIT TUTORIAL: SETTING UP, WRITING, AND RUNNING JAVA UNIT TESTSThistutorialwillhelpyouunderstandthebasicsandscaleyourunittestingpracticelikeapro.
BeforewegointoJUnits,let'stalkalittlebitaboutunittestingandregressiontestingandwhytheymatteringeneral.We'llgetintosomegoodexamplesaswegoon.
UNITTESTING Unittestingisaformofwhiteboxtesting,inwhichtestcasesarebasedoninternalstructure.Thetesterchoosesinputstoexploreparticularpathsanddeterminestheappropriateoutput.Thepurposeofunittestingistoexaminetheindividualcomponentsorpieceofmethods/classestoverifyfunctionality,ensuringthebehaviorisasexpected.
Theexactscopeofa“unit”isoftenlefttointerpretation,butaniceruleofthumbisforaunittocontaintheleastamountofcodethatperformsastandalonetask(e.g.asinglemethodorclass).Thereisagoodreasonthatwelimitscopewhenunittesting--ifweconstructatestthatincorporatesmultipleaspectsofaproject,wehaveshiftedfocus,fromfunctionalityofasinglemethod,tointeractionbetweendifferentportionsofthecode.Ifthetestfails,wedon'tknowwhyitfailed,andweareleftwonderingwhetherthepointoffailurewaswithinthemethodwewereinterestedin,orinthedependenciesassociatedwiththatmethod.
REGRESSIONTESTING Complementingunittesting,regressiontestingmakescertainthatthelatestfix,enhancement,orpatchdidnotbreakexistingfunctionality,bytestingthechangesyou'vemadetoyourcode.Changestocodeareinevitable,whethertheyaremodificationsofexistingcodeoraddingpackagesfornewfunctionality--yourcodewillcertainlychange.Itisinthischangethatthemostdangerlies,sowiththatinmind,regressiontestingisamust.
WHATISJUNIT? JUnitisaunittestingframeworkfortheJavaprogramminglanguagethatplaysabigroleinregressiontesting.Anopen-sourceframework,itisusedtowriteandrunrepeatableautomatedtests.
Aswithanythingelse,theJUnitframeworkhasevolvedovertime.ThemajorchangetomakenoteofistheintroductionofannotationsthatcamealongwiththereleaseofJUnit4,whichprovidedanincreaseinorganizationandreadabilityofJUnits.TherestofthisblogpostwillbewrittenfromusagesofJUnit4and5.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
10
HOWTOSETUPJUNIT ThemorecommonIDEs,suchasEclipseandIntelliJ,willalreadyhaveJUnitfunctionalityinstalledbydefault.IfoneisnotusinganIDEandperhapsrelyingsolelyonabuildsystemsuchasMavenorGradle,theinstallationofJUnit4/5ishandledviathepom.xmlorbuild.gradle,respectively.ItisimportanttonotethatJUnit5wassplitintothreemodules,oneofthosebeingavintagemodulethatsupportsannotation/syntaxofJUnit4.
JUNIT4 ToaddJUnit4toyourMaven,buildthefollowingtothepom.xml. Bemindfulofversion:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
ForGradle,addthefollowingtothebuild.gradle:
apply plugin: 'java'
dependencies { testCompile 'junit:junit:4.12' }
JUNIT5 AddingJUnit5isabitdifferent.BecauseofthemodularfashionofJUnit5,aBOMisusedtoimportallaspects.Ifonlyparticularclassesareneeded,individualgroupsorartifactscanbespecified.
ToaddJUnit5toMaven,addthefollowingtopom.xml:
<dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency>
ForGradle,addthefollowingtothebuild.gradle:
apply plugin: 'java'
dependencies { implementation 'org.junit:junit-bom:5.2.0' }
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
11
Althoughnottypicallyneeded,theraw.jarfile,whichallowsonetousetheJUnitframework,isalsoaccessibletomanuallyputontheclasspath.GithousesthecodeforJUnit.JUnit4hasthe.jaravailabletodownloaddirectly.ItismostcommontoincludetheJUnit5.jarfilesusingabuildmanagementsystem,likeMavenorGradle.SeetheJUnit5docsonlineforspecifics.
WRITINGUNITTESTS:THEANATOMYOFAJUNIT Nowthatwetalkedalittleaboutunittestingandsetup,let'smoveontoactualconstructionandexecutionofthesetests.TobestillustratethecreationofJUnits, wewanttostartwithsomethingbasic.Intheexampleimagebelow,wehavea simplemethod(left)thatconvertsFahrenheittoCelsius,andtheJUnit(right)associatedwithourmethod.TheJUnitsarenumberedinsectionsbelowandwe'lldiscusseachindetail.
Sections1and2 TheseareimportsfortheJUnitlibrariesneededtoleveragethetestingframework.TheimportedlibrariescanbespecifieddowntoaparticularfunctionalityofJUnitbutarecommonlyimportedwithasteriskstohaveaccesstoallfunctionality.
Section3 Thishasthestartofourtestclass,andtheimportantthingtotakenoteofhereisthenamingconventionfortheclass,whichfollowsClassNameTest.
Section4 Here,weseeourfirstJUnit-specificsyntax,anannotation.AnnotationsareextremelyimportantwhencreatingJUnits.ThisishowJUnitknowswhattodowiththeprocessingsectionofcode.Inourexamplecase,wehavean@Testannotation,whichtellsJUnitthatthepublicvoidmethodtowhichitisattachedcanberunasatestcase.
Figure 4: Example Code With Example Unit Test
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
12
Therearemanyotherannotations,butthemorecommonare@Before(whichrunssomestatement/preconditionbefore@Test,publicvoid),@After(whichrunssomestatementafter@Test,publicvoide.g.resettingvariables,deletingtemporaryfiles,variables,etc.),and@Ignore(whichignoressomestatementduringtestexecution--notethat@BeforeClassand@AfterClassareusedforrunningstatementsbeforeandafteralltestcases,publicstaticvoid,respectively).
Section5 Thetakeawayhereisagainnamingconvention.Notethestructure,testMethodName.
Section6 Hereweconstructanewinstanceofourclassobject.Thisisnecessarysowecancallthemethodwearetestingonsomething.Withoutthisobjectinstance,wecannottestthemethod.
Section7 Variablesassociatedwiththemethodneedtobeestablished,soherewedeclarevariablescorrespondingtoourmethod.Theseshouldbegivenmeaningfulvalues(note:ifaparameterisanobject,onecaninstantiateit,ormockit),sothatourtest hasmeaning.
Section8 Thisvariabledeclarationcouldbearguedasoptional,butit'sworthwhileforthesakeoforganizationandreadability.Weassigntheresultsofourmethodbeingtestedtothisvariable,usingitasneededforassertionsandsuch.
Section9 Theassertmethods(whicharepartoftheorg.junit.Assertclass)areusedindeterminingpass/failstatusoftestcases.Onlyfailedassertionsarerecorded.Likewithannotations,therearemanyassertoptions.InourexampleJUnitabove,weuseassertEquals(expected,actual,delta).Thistakesintheexpectedoutcome,whichtheuserdefines,theactual,whichistheresultofthemethodbeingcalled,andthedelta,whichallowsforimplementinganalloweddeviationbetweenexpectedandactualvalues.Thepurposeofanassertionisvalidation.AlthoughnotrequiredtorunyourJUnit,failingtoaddassertionsarguablydefeatsthepurposeofyourtest.Withoutassertions,youhavenoverificationandatmostasmoketest,whichgivesfeedbackonlywhenatesterrorsout.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
13
HOWTORUNAJUNIT Chooseyourownadventure!Here,wewilllookatthreewaystorunJUnits:straightfromthecommandline,fromtheIDE(EclipseandIntelliJ),andusingbuildsystems(MavenandGradle).
How to Run a JUnit From the Command Line TorunaJUnitdirectlyfromthecommandline,youneedafewthings:JDKonyourpath,rawJunitjarfile,andthetestcases.Thecommandisasfollows(thisexampleisforJUnit4):
java -cp /path/to/junit.jar org.junit.runner.JUnitCore <test class name>
NOTE: it is unlikely in a professional setting that one would be running a test manually from the command line, without some build system, but the ability is there.
How to Run a JUnit From the IDE Eclipse TorunfromEclipse,fromyourPackageExplorerlocateyourJUnittest,inwhicheverfolderyouhavedesignateditto.Right-click,andmovedowntoRunAsJUnitTest.ThiswillexecuteyourtestandopenanewJUnitwindowifnotalreadyopen.
Figure 5: Eclipse Menu to Run as JUnit Test
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
14
IntelliJ RunningatestinIntelliJisverysimilartoEclipse.FromtheProjectwindow,locatetest,right-click,andselectRun‘testName’.LikeEclipse,aJUnitwindowwillopenwiththeresultsofthetest.
Figure 6: IntelliJ Menu to Run a Unit Test
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
15
How to Run a JUnit Using Build Systems
Maven Mavenmaderunningtestssimple.Ensureyouareintheproperlocationfromyourcommandline,andtheprojectpom.xmlisproperlyconfigured.ThenyoucanrunthefollowingtoexecuteyourJUnits:
Torunentiretestsuite:
mvn test
Torunsingle/specifictest(s):
mvn -Dtest=TestName test
Gradle Gradle,likeMaven,maderunningtestssimple.
Torunentiretestsuite:
gradlew test
Torunsingle/specifictest(s):
gradlew -Dtest.single=testName test
NOTE: Maven and Gradle are their own monster -- what is shown here is minimal to cover the basics.
CONTINUINGWITHUNITTESTING Ourexampleranthroughaverysimplesnippetofcode,andofcourse,thisisjustthestartofunittesting.Morecomplexmethodscalldatabasesorothermethods,buttoreassurefunctionalityweneedisolation,whichweachievethroughmocking.Mockinghelpsusisolateunitsofcodetofocusourvalidation.FrameworkscommonlyusedformockingareMockitoandPowerMock.
Thebenefitsofunittestingareclear:
» Identifydefectswithisolationandfocusedtesting.
» Assurebehaviorofindividualmethodsorpiecesofmethods.
» Helpstoensureadditionormodificationofcodedoesnotbreaktheapplication.
» Boundaryanalysismakesiteasiertocheckforinvalid/badinput.
» Testeveryaspectofthemethodtoincreasecodecoverage.
It'shelpfultodeploypowerfulunittestingtoolslikeParasoftJtestthatcanremedymuchofthepainassociatedwithJUnitsandsavedevelopersvaluabletime.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
16
MOCKING IN JAVA: HOW TO AUTOMATE A JAVA UNIT TEST, INCLUDING MOCKING AND ASSERTIONSWhatismockinginJava?Youcanauto-generateaunittestwithasinglebuttonclick,includingallofthemockingandvalidations.
Goodunittestsareagreatwaytomakesurethatyourcodeworkstodayandcontinuestoworkinthefuture.Acomprehensivesuiteoftests,withgoodcode-basedandbehavior-basedcoverage,cansaveanorganizationalotoftimeandheadaches.Andyet,itisnotuncommontoseeprojectswherenotenoughtestsarewritten.Infact,somedevelopershaveevenbeenarguingagainsttheirusecompletely.
WHATMAKESAGOODUNITTEST? Therearemanyreasonswhydevelopersdon’twriteenoughunittests.Oneofthebiggestreasonsistheamountoftimetheytaketobuildandmaintain,especiallyinlarge,complexprojects.Incomplexprojects,oftenaunittestneedstoinstantiateandconfigurealotofobjects.Thistakesalotoftimetosetupandcanmakethetestascomplex(ormorecomplex)thanthecodeitistesting,itself.
Let’slookatanexampleinJava:
public LoanResponse requestLoan(LoanRequest loanRequest, LoanStrategy strategy) { LoanResponse response = new LoanResponse(); response.setApproved(true); if (loanRequest.getDownPayment().compareTo(loanRequest.getAvailableFunds()) > 0) { response.setApproved(false); response.setMessage("error.insufficient.funds.for.down.payment"); return response; } if (strategy.getQualifier(loanRequest) < strategy.getThreshold(adminManager)) { response.setApproved(false); response.setMessage(getErrorMessage()); } return response; }
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
17
HerewehaveamethodthatprocessesaLoanRequest,generatingaLoanResponse.NotetheLoanStrategyargument,whichisusedtoprocesstheLoanRequest.Thestrategyobjectmaybecomplex–itmayaccessadatabase,anexternalsystem,orthrowaRuntimeException.TowriteatestforrequestLoan(),weneedtobeawareofwhichtypeofLoanStrategywe'retestingwithandweprobablyneedtotestthemethodwithavarietyofLoanStrategyimplementationsandLoanRequestconfigurations.
AunittestforrequestLoan()maylooklikethis:
Asyoucansee,there’sawholesectionofourtestwhichjustcreatesobjectsandconfiguresparameters.Itwasn’tobviouslookingattherequestLoan()methodwhatobjectsandparametersneedtobesetup.Tocreatethisexample,wehadtorunthetest,addsomeconfiguration,thenre-runagainandrepeattheprocessoverandover.WespenttoomuchtimefiguringouthowtoconfiguretheAdminManagerandtheLoanStrategyinsteadoffocusingonourmethodandwhatneededtobetestedthere.AndIstillneedtoexpandourtesttocovermoreLoanRequestcases,morestrategies,andmoreparametersforAdminDao.
Additionally,byusingrealobjectstotestwith,thistestisactuallyvalidatingmorethanjustthebehaviorofrequestLoan()—we'redependingonthebehaviorofAvailableFundsLoanStrategy,AdminManagerImpl,andAdminDaoinorderforourtesttorun.Effectively,we'retestingthoseclassestoo.Insomecases,thisisdesirable,butinothercasesitisnot.Plus,ifoneofthoseotherclasseschanges,thetestmaystartfailingeventhoughthebehaviorofrequestLoan()didn’tchange.Forthistest,wewouldratherisolatetheclassundertestfromitsdependencies.
@Test public void testRequestLoan() throws Throwable { // Set up objects DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor();
LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000); LoanStrategy strategy = new AvailableFundsLoanStrategy();
AdminManager adminManager = new AdminManagerImpl(); underTest.setAdminManager(adminManager); Map<String, String> parameters = new HashMap<>(); parameters.put("loanProcessorThreshold", "20");
AdminDao adminDao = new InMemoryAdminDao(parameters); adminManager.setAdminDao(adminDao);
// Call the method under test LoanResponse response = processor.requestLoan(loanRequest, strategy);
// Assertions and other validations }
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
18
MOCKINGINJAVA Onesolutionforthecomplexityproblemistomockthosecomplexobjects.Forthisexample,let'sstartbyusingamockfortheLoanStrategyparameter:
@Test public void testRequestLoan() throws Throwable { // Set up objects DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor(); LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000); LoanStrategy strategy = Mockito.mock(LoanStrategy.class);
Mockito.when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(20.0d);
Mockito.when(strategy.getThreshold(any(AdminManager.class))).thenReturn(20.0d);
// Call the method under test LoanResponse response = processor.requestLoan(loanRequest, strategy);
// Assertions and other validations }
Let’slookatwhat’shappeninghere.WecreateamockedinstanceofLoanStrategy usingMockito.mock().SinceweknowthatgetQualifier()andgetThreshold() willbecalledonthestrategy,wedefinethereturnvaluesforthosecallsusingMockito.when(…).thenReturn().Forthisthetest,wedon’tcarewhattheLoanRequestinstance’svaluesare,nordoweneedarealAdminManageranymorebecauseAdminManagerwasonlyusedbytherealLoanStrategy.
Additionally,sincewearen'tusingarealLoanStrategy,wedon’tcarewhattheconcreteimplementationsofLoanStrategymightdo.Wedon’tneedtosetuptestenvironments,dependencies,orcomplexobjects.WearefocusedontestingrequestLoan()–notLoanStrategyorAdminManager.Thecode-flowofthemethodundertestisdirectlycontrolledbythemock.
ThistestisaloteasiertowritewithMockitothanhavingtocreateacomplexLoanStrategyinstance.Buttherearestillsomechallenges.
» Forcomplexapplications,testsmayrequirelotsofmocks.
» IfyouarenewtoMockito,youneedtolearnitssyntaxandpatterns.
» Youmaynotknowwhichmethodsneedtobemocked.
» Whentheapplicationchanges,thetests(andmocks)needtobeupdatedtoo.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
19
SOLVINGMOCKINGCHALLENGESWITHAJAVAUNITTESTGENERATOR WedesignedParasoftJtesttohelpaddressthechallengesaboveandmanagetherisksofJavasoftwaredevelopment.
Ontheunittestingsideofthings,ParasoftJtesthelpsyouautomatesomeofthemostdifficultpartsofcreatingandmaintainingunittestswithmocks.Fortheaboveexample,itcanauto-generateatestforrequestLoan()withasinglebutton-click,includingallofthemockingandvalidationsyouseeintheexampletest.
Below,the“Regular”actionintheParasoftJtestUnit Test AssistantToolbargeneratesthefollowingtest:
@Test public void testRequestLoan() throws Throwable { // Given DownPaymentLoanProcessor underTest = new DownPaymentLoanProcessor(); // When double availableFunds = 0.0d; // UTA: default value double downPayment = 0.0d; // UTA: default value double loanAmount = 0.0d; // UTA: default value
LoanRequest loanRequest = LoanRequestFactory.create(availableFunds, downPayment, loanAmount); LoanStrategy strategy = mockLoanStrategy(); LoanResponse result = underTest.requestLoan(loanRequest, strategy); // Then // assertNotNull(result); }
Allthemockingforthistesthappensinahelpermethod:
private static LoanStrategy mockLoanStrategy() throws Throwable { LoanStrategy strategy = mock(LoanStrategy.class); double getQualifierResult = 0.0d; // UTA: default value when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(getQualifierResult);
double getThresholdResult = 0.0d; // UTA: default value
Figure 7: Test Generation With Parasoft Jtest
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
20
when(strategy.getThreshold(any(AdminManager.class))).thenReturn(getThresholdResult);
return strategy; }
Allthenecessarymockingissetupforus—ParasoftJtestdetectedthemethodcallstogetQualifier()andgetThreshold()andmockedthemethods.OnceweconfigurevaluesintheunittestforavailableFunds, downPayment,etc,thetestisreadytorun(wecouldalsogenerateaparameterizedtestforbettercoverage!).Notealsothattheassistantprovidessomeguidanceastowhichvaluestochangebyitscomments,“UTA:defaultvalue”,makingtestingeasier.
Thissavesalotoftimeingeneratingtests,especiallyifwedon’tknowwhatneedstobemockedorhowtousetheMockitoAPI.
HANDLINGCODECHANGES Whentheapplicationlogicchanges,thetestsoftenneedtochangealso.Ifthetestiswell-written,itshouldfailifyouupdatethecodewithoutupdatingthetest.Often,thebiggestchallengeinupdatingthetestisunderstandingwhatneedstobeupdated,andhowexactlytoperformthatupdate.Iftherearelotsofmocksandvalues,itcanbedifficulttotrackdownwhatthenecessarychangesare.
Toillustratethis,let’smakesomechangestothecodeundertest:
public LoanResponse requestLoan(LoanRequest loanRequest, LoanStrategy strategy) { ... String result = strategy.validate(loanRequest); if (result != null && !result.isEmpty()) { response.setApproved(false); response.setMessage(result); return response; } ... return response; }
WehaveaddedanewmethodtoLoanStrategy – validate(),andarenowcallingitfromrequestLoan().Thetestmayneedtobeupdatedtospecifywhatvalidate()shouldreturn.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
21
Withoutchangingthegeneratedtest,let’srunitwithintheParasoftJtestUnit TestAssistant:
ParasoftJtestdetectedthatvalidate()wascalledonthemockedLoanStrategy argumentduringourtestrun.Sincethemethodhasnotbeensetupforthemock,theassistantrecommendsthatwemockthevalidate()method.The“Mockit”quick-fixactionupdatesthetestautomatically.Thisisasimpleexample–butforcomplexcodewhereitisn’teasytofindthemissingmock,therecommendationandquick-fixcansaveusalotofdebuggingtime.
Afterupdatingthetestusingthequickfix,wecanseethenewmockandsetthedesiredvalueforvalidateResult:
private static LoanStrategy mockLoanStrategy() throws Throwable {
LoanStrategy strategy = mock(LoanStrategy.class); String validateResult = ""; // UTA: default value
when(strategy.validate(any(LoanRequest.class))).thenReturn(validateResult); double getQualifierResult = 20.0d;
when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(getQualifierResult); double getThresholdResult = 20.0d;
when(strategy.getThreshold(any(AdminManager.class))).thenReturn(getThresholdResult); return strategy; }
WecanconfigurevalidateResultwithanon-emptyvaluetotesttheusecasewherethemethodentersthenewblockofcode,orwecanuseanemptyvalue(ornull)tovalidatebehaviorwhenthenewblockisnotentered.
Figure 8: Example of Mocking in Parasoft Jtest
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
22
ANALYZINGTHETESTFLOW Theassistantalsoprovidessomeusefultoolsforanalyzingthetestflow.Forinstance,hereistheflowtreeforourtestrun:
SUMMARYOFMOCKINGINJAVA Youcanautomatemanyaspectsofunittesting.ParasoftJtesthelpsyougenerateunittestswithlesstimeandeffort,reducingthecomplexityassociatedwithmocking.Italsomakesmanyotherrecommendationstoimproveexistingtestsbasedonruntimedata,andhassupportforparameterizedtests,SpringApplicationtests,andPowerMock(formockingstaticmethodsandconstructors).
Figure 9: The Parasoft Jtest Unit Test Assistant’s Flow Tree, showing calls made during test execution.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
23
HOW TO CREATE JUNIT PARAMETERIZED TESTSParameterizedtestsareagoodwaytodefineandrunmultipletestcases,wheretheonlydifferencebetweenthemisthedata.Here,welookatthreedifferentframeworkscommonlyusedwithJUnittests.
Whenwritingunittests,itiscommontoinitializemethodinputparametersandexpectedresultsinthetestmethoditself.Insomecases,usingasmallsetofinputsisenough;however,therearecasesinwhichweneedtousealargesetofvaluestoverifyallofthefunctionalityinourcode.Parameterizedtestsareagoodwaytodefineandrunmultipletestcases,wheretheonlydifferencebetweenthemisthedata.Theycanvalidatecodebehaviorforavarietyofvalues,includingbordercases.Parameterizingtestscanincreasecodecoverageandprovideconfidencethatthecodeisworkingasexpected.
ThereareanumberofgoodparameterizationframeworksforJava.Inthisarticle,wewilllookatthreedifferentframeworkscommonlyusedwithJUnittests,withacomparisonbetweenthemandexamplesofhowthetestsarestructuredforeach.Finally,wewillexplorehowtosimplifyandexpeditethecreationofparameterizedtests.
JUNITPARAMETERIZEDTESTFRAMEWORKS Let’scomparethe3mostcommonframeworks:JUnit4,JunitParams,andJUnit5.EachJUnitparameterizationframeworkhasitsownstrengthsandweaknesses.
JUnit4 Pros » ThisistheparameterizationframeworkbuiltintoJUnit4,soitrequiresnoadditional externaldependencies. » ItsupportsolderversionsofJava(JDK7andolder).
Cons » Testclassesusefieldsandconstructorstodefineparameters,whichmaketests moreverbose. » Itrequiresaseparatetestclassforeachmethodbeingtested.
JunitParams Pros » Simplifiesparametersyntaxbyallowingparameterstobepasseddirectly toatestmethod. » Allowsmultipletestmethods(eachwiththeirowndata)pertestclass. » SupportsCSVdatasources,aswellasannotation-basedvalues (nomethodrequired).
Cons » RequirestheprojecttobeconfiguredwiththeJunitParamsdependency. » Whenrunninganddebuggingtests,alltestswithintheclassmustberun—it isnotpossibletorunasingletestmethodwithinatestclass.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
24
JUnit5 Pros » ThisparameterizationframeworkisbuiltintoJUnit5andimproveswhatwas includedwithJUnit4. » HasasimplifiedparametersyntaxlikeJunitParams. » Supportsmultipledata-setsourcetypes,includingCSVandannotation (nomethodrequired). » Eventhoughnoextradependenciesarerequired,morethanone.jarisneeded.
Consideration » RequiresnewerversionsofJavaandyourpreferredbuildsystem(e.g.Gradle orMavenSurefire).ChecktheJUnit5specificationsfordetails.
EXAMPLEOFAJUNITPARAMETERIZEDTEST Asanexample,supposethatwehaveamethodthatprocessesloanrequestsforabank.Wemightwriteaunittestthatspecifiestheloanrequestamount,downpaymentamount,andothervalues.Wewouldthencreateassertionsthatvalidatetheresponse—theloanmaybeapprovedorrejected,andtheresponsemayspecifythetermsoftheloan.
public LoanResponse requestLoan(float loanAmount, float downPayment, float availableFunds)
{ LoanResponse response = new LoanResponse(); response.setApproved(true);
if (availableFunds < downPayment) { response.setApproved(false); response.setMessage("error.insufficient.funds.for.down.payment");
return response; }
if (downPayment / loanAmount < 0.1) { response.setApproved(false); response.setMessage("error.insufficient.down.payment"); }
return response; }
Viewraw. Viewparameterizedtestexample.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
25
First,let’slookataregulartestfortheabovemethod:
@Test public void testRequestLoan() throws Throwable {
// Given| LoanProcessor underTest = new LoanProcessor();
// When LoanResponse result = underTest.requestLoan(1000f, 200f, 250f);
// Then assertNotNull(result); assertTrue(result.isApproved()); assertNull(result.getMessage()); }
Viewraw. Viewparameterizedtestexample2.
Inthisexample,wearetestingourmethodbyrequestinga$1000loan,witha $200downpaymentandindicatingthattherequestorhas$250inavailablefunds. Thetestthenvalidatesthattheloanwasapprovedanddidn’tprovideamessagein theresponse.
InordertomakesurethatourrequestLoan()methodistestedthoroughly,weneedtotestwithavarietyofdownpayments,requestedloanamounts,andavailablefunds.Forinstance,let’stesta$1millionloanrequestwithzerodownpayment,whichshouldberejected.Wecouldsimplyduplicatetheexistingtestwithdifferentvalues,butsincethetestlogicwouldbethesame,itismoreefficienttoparameterizethetestinstead.
Wewillparameterizetherequestedloanamount,downpayment,andavailablefunds,aswellastheexpectedresults:whethertheloanwasapproved,andthemessagereturnedaftervalidation.Eachsetofrequestdata,alongwithitsexpectedresults, willbecomeitsowntestcase.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
26
ANEXAMPLEOFAPARAMETERIZEDTESTUSINGJUNIT4PARAMETERIZED Let’sstartwithaJUnit4Parameterizedexample.Tocreateaparameterized test,wefirstneedtodefinethevariablesforthetest.Wealsoneedtoinclude aconstructortoinitializethem:
@RunWith(Parameterized.class) public class LoanProcessorParameterizedTest {
float loanAmount; float downPayment; float availableFunds; boolean expectApproved; String expectedMessage;
public LoanProcessorParameterizedTest(float loanAmount, float downPayment,
float availableFunds, boolean expectApproved, String expectedMessage)
{ this.loanAmount = loanAmount; this.downPayment = downPayment; this.availableFunds = availableFunds; this.expectApproved = expectApproved; this.expectedMessage = expectedMessage; }
// ...
}
Viewraw. Viewparameterizedtestexample3.
Here,weseethatthetestusesthe@RunWithannotationtospecifythatthetestwillrunwiththeJUnit4Parameterizedrunner.Thisrunnerknowstolookforamethodwhichwillprovidethevalue-setforthetest(annotatedwith@Parameters),initializethetestproperly,andrunthetestswithmultiplerows.
Notethateachparameterisdefinedasafieldinthetestclass,andtheconstructorinitializesthesevalues(youcanalsoinjectvaluesintofieldsusingthe@Parameter annotationifyoudon’twanttocreateaconstructor).Foreachrowinthevalue-set,theParameterizedrunnerwillinstantiatethetestclassandruneachtestintheclass.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
27
Let’saddamethodwhichprovidestheparameterstotheParameterizedrunner:
@Parameters(name = "Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
public static Iterable<Object[]> data() throws Throwable
{ return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null } }); }
Viewraw. Viewparameterizedtestexample4.
Thevalue-setsarebuiltasaList of Objectarraysbythedata()method,[email protected]@Parameterssetsthenameofthetestusingplaceholders,whichwillbereplacedwhenthetestruns.Thismakesiteasiertoseevaluesintestresults,aswewillseelater.Currently,thereisonlyonerowofdata,testingacasewheretheloanshouldbeapproved.Wecanaddmorerowstoincreasecoverageofthemethodundertest.
@Parameters(name = "Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
public static Iterable<Object[]> data() throws Throwable
{ return Arrays.asList(new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null },
{ 1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment" },
{ 1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment" } }); }
Viewraw. Viewparameterizedtestexample5.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
28
Here,wehaveonetestcasewheretheloanwouldbeapproved,andtwocasesinwhichitshouldnotbeapprovedfordifferentreasons.Wemaywanttoaddrowsinwhichzeroornegativevaluesareused,aswellastestboundaryconditions.
Wearenowreadytocreatethetestmethod:
@Test
public void testRequestLoan() throws Throwable
{ // Given LoanProcessor underTest = new LoanProcessor();
// When LoanResponse result = underTest.requestLoan(loanAmount, downPayment, availableFunds);
// Then assertNotNull(result); assertEquals(expectApproved, result.isApproved()); assertEquals(expectedMessage, result.getMessage()); }
Viewraw. Parameterizedtestexample6.
Here,wereferencethefieldswheninvokingtherequestLoan()methodandvalidatingtheresults.
JUNITPARAMSEXAMPLE TheJunitParamslibrarysimplifiesparameterizedtestsyntaxbyallowingparameterstobepasseddirectlytothetestmethod.Theparametervaluesareprovidedbyaseparatemethodwhosenameisreferencedinthe@Parametersannotation.
@RunWith(JUnitParamsRunner.class) public class LoanProcessorParameterizedTest2 {
@Test
@Parameters(method = "testRequestLoan _ Parameters") public void testRequestLoan(float loanAmount, float downPayment, float availableFunds, boolean expectApproved, String expectedMessage) throws Throwable { ... }
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
29
@SuppressWarnings("unused") private static Object[][] testRequestLoan _ Parameters() throws Throwable {
// Parameters: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}
return new Object[][] {
{ 1000.0f, 200.0f, 250.0f, true, null }, { 1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment"}, { 1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment" } }; } }
Viewraw. Viewparameterizedtestexample7.
JunitParamshastheadditionalbenefitthatitsupportsusingCSVfilestoprovidevaluesinadditiontoprovidingthevaluesincode.Thisallowsthetesttobedecoupledfromthedataanddatavaluestobeupdatedwithoutupdatingthecode.
JUNIT5EXAMPLE JUnit5addressessomeofthelimitationsandshortcomingsofJUnit4.LikeJunitParams,JUnit5alsosimplifiesthesyntaxofparameterizedtests.Themostimportantchangesinsyntaxare:
» Thetestmethodisannotatedwith@ParameterizedTestinsteadof@Test.
» Thetestmethodacceptsparametersdirectly,insteadofusingfields andaconstructor.
» The @RunWithannotationisnolongerneeded.
DefiningthesameexampleinJUnit5wouldlooklikethis:
public class LoanProcessorParameterizedTest {
@ParameterizedTest(name="Run {index}: loanAmount={0}, downPayment={1}, availableFunds={2}, expectApproved={3}, expectedMessage={4}")
@MethodSource("testRequestLoan _ Parameters") public void testRequestLoan(float loanAmount, float downPayment, float availableFunds,
boolean expectApproved, String expectedMessage) throws Throwable
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
30
{ ... }
static Stream<Arguments> testRequestLoan _ Parameters() throws Throwable {
return Stream.of(
Arguments.of(1000.0f, 200.0f, 250.0f, true, null),
Arguments.of(1000.0f, 50.0f, 250.0f, false, "error.insufficient.down.payment"),
Arguments.of(1000.0f, 200.0f, 150.0f, false, "error.insufficient.funds.for.down.payment") ); } }
Viewraw. Viewparameterizedtestexample8.
EFFICIENTLYCREATEPARAMETERIZEDTESTS Asonemightimagine,writingtheaboveparameterizedtestcanbeabitofwork.Foreachparameterizedtestframeworkthereissomeboilerplatecodethatneedstobewrittencorrectly.Itcanbehardtorememberthecorrectstructure,andittakestimetowriteout.Tomakethismucheasier,youcanuseParasoftJtesttogenerateparameterizedtests,automatically,liketheonesdescribedabove.Todothis,simplyselectthemethodyouwanttogenerateatestfor(inEclipseorIntelliJ):
Thetestisgenerated,usingdefaultvaluesandassertions.Youcanthenconfigure thetestwithrealinputvaluesandassertionsandaddmoredatarowstothe data()method.
Figure 10: Select Parameterized Test Generation in Parasoft Jtest
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
31
RUNNINGTHEPARAMETERIZEDTEST ParasoftJtestcanrunparameterizedtestsdirectlyinbothEclipseandIntelliJ.
Notethatthenameofeachtest,asshown,includesinputvaluesfromthedatasetandexpectedresultvalues.Thiscanmakedebuggingthetestmucheasierwhenitfails,sincetheinputparametersandexpectedoutputsareshownforeachcase.
YoucanalsousetheRunAllactionfromParasoftJtest:
Itanalyzesthetestflowandprovidesdetailedinformationabouttheprevioustestrun.Thisallowsyoutoseewhathappenedinthetestwithoutneedingtorerunthetestwithbreakpointsordebuggingstatements.Forinstance,youcanseeparameterizedvaluesintheVariablesview:
Figure 11: The JUnit View in Eclipse
Figure 12: The Flow Tree View in Parasoft Jtest
Figure 13: The Variables View in Parasoft Jtest
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
32
SUMMARYOFCREATINGJUNITPARAMETERIZEDTESTS Eachofthethreeframeworksthatwereviewedarefinechoicesandworkwell.IfusingJUnit4,JunitParamsispreferredoverthebuilt-inJUnit4Parameterizedframework,duetothecleanerdesignofthetestclassesandtheabilitytodefinemultipletestmethodsinthesameclass.However,ifusingJUnit5,werecommendthebuilt-inJUnit5frameworksinceitaddressestheshortcomingsinJUnit4andrequiresnoextralibraries.WealsolikeusingParasoftJtest’sunittestingcapabilitiestomakecreation,execution,anddebuggingofparameterizedtestsmoreefficient.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
33
GET MORE OUT OF UNIT TESTING AND REDUCE MAINTENANCE EFFORTS WITH RUNTIME ANALYSIS Torealizethebenefitsofunittesting,youcanobserveaunittestduringitsexecutionviaruntimeanalysis.Runtimeanalysisduringunittestexecutioniscriticaltoimprovingtestefficiencyandeffectiveness.
Unittestingisabestpracticetotestindividualunits/componentsofasoftware,butitcanbetediousandcostlyforJavadevelopers.It'spainstakingtotesteachunitforcorrectbehaviorwithmanualassertions,andisolateeachmethodwithmocking,andunittestingitselfisopentobugsandmisunderstoodbehavior.Toimprovethissituation,youcanusearuntimeanalysistooltodetectdataandcontrolflow,externaldependencies,andtocalculatetestcodecoverage.
Withthiscollecteddatafromtheruntimeanalysis,anenterprise-gradesolutionlikeParasoftJtestcanpromptthedeveloperabouthowtoimprovethetests,byautomaticallyrecommendingassertionsforcorrectbehavior,andmethodsformockingtoimprovetestisolation.ThisintegrationbetweenautomaticunittestgenerationandruntimeanalysisreducesthemanualinterventionrequiredforunittestingforJava.
BENEFITSOFUNITTESTING Unittestingisawell-knownpractice,butitsimplementationrequiresimprovementinmanyprojects.Unittesting,donewell,improvestheagilityofagileprocess,increasesqualityandsecurity,andbringslong-termcostsavings.
Unfortunately,regardlessofthesebenefits,developerscanstillstrugglewithunittesting,despitethedesiretoachievebetterresults.Theamountoftimeandeffortneededfortestcreationandmaintenancecanbetoomuchtojustifyincreasingtestingefforts.Often,testsuitesarefragilebecauseofpoorunit/objectisolationfromdependencies.Propermockingofdependenciesbecomesthebaneofsoftwaretesters,asdoescreatingtheassertionsneededtodeterminecorrectprogramlogic.Evenparameterizingtestsforscenarioscanbetediousandtimeconsuming.
Softwaredevelopmentteamsmustaddresstheseproblemswithtestcreation,isolation,andmaintenanceiftheywanttoachievethebenefitsofthoroughunittesting.Theanswerstartswithtestautomationtools,butsimplyautomatingtheexecutionoftestsandcollectingresultsisn’tenough.Runtimeanalysis,theprocess ofobservingarunningexecutableandrecordingkeymetrics,isaninnovativeway tohelpimproveunittestingcreation,mocking,andteststability.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
34
RUNTIMEANALYSISCANIMPROVEUNITTESTING Inmostcases,developersdon’tconsiderruntimeanalysisimportantinearlystagesofunittesting.Mosttoolsareusedforcatchingerrorsthatunittestingmissed,orsimplyincalculatingcodecoverage.Butwhilethesebenefitsareimportant,runtimeanalysiscanalsoobservetheexecutionofthefirstiterationofaunittesttomakerecommendationstoimprovethetestanddetectchangestothetestruntimeenvironmentthatinterferewithteststability.
TestframeworkssuchasJUnitcreatesparsecodethatrequiresfurtherdeveloperinput.Thisworkistedious,soitcanbeautomatedtofillinmoreofthedetailsbasedontheobservedprogramlogic.Forexample,thefollowingunittestcanbeautomaticallygeneratedbyParasoftJtest:
Figure 14: Unit Test Generation in Parasoft Jtest
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
35
Similarly,forunittestswithparameterizedinputs,shownbelow:
Sincethecreatedtestsareexecutablefromthestart,theycanbeobservedbyruntimeanalysisforbothresultsandexecutionflow.Forexample,atestmayfailduetoaraisedexception,shownbelow.
DETECTINGDEPENDENCIESANDMOCKINGWITHRUNTIMEANALYSIS Inaddition,runtimetoolsobservetheexecutionpathintodependenciesandrecommendpotentialmockstoincreasetheisolationofthetest.Althoughvisualinspectionofanobjectundertestwillrevealitsdependencies,automatingthedetectionandmockingofthesedependenciessaveslotsoftediousanderror-pronework.
Figure 15: P arameterized Test Generation in Parasoft Jtest
Figure 16: Exception Error Shown in Parasoft Jtest Unit Test Assistant
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
36
Intheexamplebelow,ParasoftJtestoffersthedeveloperachoiceofwhattomockbasedontheexecutiontraceoftheunittest:
Inthiscase,addingamockablemethodpatternaddsthemethodtoalistofmockstobehandledbyamockingframeworksuchasPowerMock.
Mockingstaticconstructorsarealsopossible,asshownbelow.
IMPROVINGTESTINGFIDELITYWITHRUNTIMEANALYSIS Withfullknowledgeoftheexecutionflow,plusparametersusedinmethodcalls,runtimeanalysiscanbeusedtoprovideusefulrecommendationstothedeveloper toimprovethetestcode.Althoughassertionsareprovided,statically,whena testiscreated,theymaynotbeenabledorcorrect.Attestexecution,failedandmissingassertionstriggerwarningswhichthenleadtorecommendationstoremedytheproblem.
Figure 17: Execution Trace of Unit Test
Figure 18: Example for Mocking Constructors
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
37
Forexample,aftercreatinganewtest,andnorecommendedassertionshavebeenuncommented,youwouldseethefollowing:
Whateverhappens,itistheconstantfeedbackaboutcorrectiveactionforassertionsthatclosestheloopontestcreationtocompleteunittesting.Additionally,astheunitundertestischanged,thesechangescanbedealtwithinthesamemanner,continuallyreducingthemanualtestmaintenancerequired.
Orifanassertionfails,forexample,thefollowingisdisplayed:
Figure 19: Example of Assertion Recommendations
Figure 20: Example of Assertion Failure
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
38
IMPROVINGTESTSTABILITYWITHRUNTIMEANALYSIS Runtimeanalysiscanalsodetectchangesinthetestenvironmentduringexecutionthatimpacttheabilitytorecreateanidenticaltestenvironmentforsubsequenttests.Teststhatpassatonetimeandfaillatercanbeacauseofgreatfrustrationandlosttimeandeffort.Someexamplesofinstabilitiesthatyoucandetectwithruntimeanalysisincludethefollowing:
» Achangedsystempropertyduringatestthathasn'tchangedbacktoitsoriginalstate.Asubsequenttestmayrelyonthisproperty.
» Additionalexecutionthreadsinthebackgroundthatmayinterferewithatestrun.
» Anewfilecreationduringtestexecutionthatmightimpactsubsequentrunsiftheyrelyonthefileanditscontents.
» Modifiedstaticfieldsthatmightimpactfutureteststhatusethesesamefields.
It’scriticalthateachtestexecutionhasanidenticalstartingpoint,toensurereliableresults.Preventingtestinstabilitywithruntimedetectionremovesguessworkfromthetestdebugphase.
CONCLUSION Youcanseethatruntimeanalysisisn'tjustforcomputingcodecoverage.Runtimeanalysisduringtestexecutioniscriticaltoimprovingtestefficiencyandeffectiveness.Monitoringexecutionpathsprovidesinformationaboutdependenciestoimprovethehandlingofdependenciesandmocking.Assertionscanbemonitored,andautomaticrecommendationsimprovetestfidelity.Detectingchangesintheruntimetestenvironmentthataffectteststabilityremovesfrustrationandreducesdebuggingcyclesfortestcode.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
39
TAKE THE NEXT STEPLearnhowParasoftJtestcanhelpyouimproveyourJava codequalityandteamproductivity.Contactustoday.
ABOUTPARASOFT Parasofthelpsorganizationscontinuouslydeliverqualitysoftwarewithitsmarket-proven,integratedsuiteofautomatedsoftwaretestingtools.Supportingtheembedded,enterprise,andIoTmarkets,Parasoft’stechnologiesreducethetime,effort,andcostofdeliveringsecure,reliable,andcompliantsoftwarebyintegratingeverythingfromdeepcodeanalysisandunittestingtowebUIandAPItesting,plusservicevirtualizationandcompletecodecoverage,intothedeliverypipeline.Bringingallthistogether,Parasoft’sawardwinningreportingandanalyticsdashboarddeliversacentralizedviewofqualityenablingorganizationstodeliverwithconfidenceandsucceedintoday’smoststrategicecosystemsanddevelopmentinitiatives—cybersecure,safety-critical,agile,DevOps,andcontinuoustesting.
Improve Unit Testing for Java With AutomationBest Practices for Java Developers
40