7 reasons why bother learning Spock

Post on 14-Jan-2017

124 views 3 download

Transcript of 7 reasons why bother learning Spock

7reasonswhybotherlearningSpock(forJavadevelopers)

Riga,6thSeptember2016

AboutmeAreasofexpertise

AutomaticTesting/TDD(withSpockofcourse:))

SoftwareCraftsmanship/CodeQuality

Concurrency/ParallelComputing/ReactiveSystems

DeploymentAutomation/ContinuousDelivery

FOSSprojectsauthorandcontributor,blogger,trainer

CTOofsmallsoftwarehouse-Codearte

targetedatclientswhocareaboutthequality

TrainerinBottegaITSolutions

WhybotherlearningSpock?

Reason1BDDspecificationbydefault

BDDspecificationbydefaultclassSimpleCalculatorSpecextendsSpecification{

def"shouldsumtwonumbers"(){given:Calculatorcalculator=newSimpleCalculator()when:intresult=calculator.sum(1,2)then:result==3}}

BDDspecificationbydefaultclassSimpleCalculatorSpecextendsSpecification{

def"shouldsumtwonumbers"(){given:Calculatorcalculator=newSimpleCalculator()when:intresult=calculator.sum(1,2)then:result==3}

[given]/when/then(orexpect)arerequiredtocompilecode

notjustcommentsincode

Reason2PowerAssertions

PowerAssertions-basiccasereusedJavaassertkeyword

assert(2+3)*4!=(2*4)+(3*4)

PowerAssertions-basiccasereusedJavaassertkeyword

assert(2+3)*4!=(2*4)+(3*4)

selfexplainingreasonoffailure

Assertionfailed:

assert(2+3)*4!=(2*4)+(3*4)||||||520false82012

PowerAssertions-morecomplexcasenotonlymathematicalexpressions

Stringword="Spock"intbegin=1intend=3assertword.substring(begin,end)==word[begin..end]

PowerAssertions-morecomplexcasenotonlymathematicalexpressions

Stringword="Spock"intbegin=1intend=3assertword.substring(begin,end)==word[begin..end]

alsoformethodreturntypesandarguments

Assertionfailed:

assertword.substring(begin,end)==word[begin..end]||||||||||po13|||13Spock||poc|Spockfalse

PowerAssertions-othercomplexcaseevenforcomplicatedstructuresandexpressions

assertann.name==bob.name&&ann.age==bob.age

PowerAssertions-othercomplexcaseevenforcomplicatedstructuresandexpressions

assertann.name==bob.name&&ann.age==bob.age

detailedevaluationofsubelements

Assertionfailed:

assertann.name==bob.name&&ann.age==bob.age|||||||Ann||Bobfalse||Person(name:Bob,age:7)|falsePerson(name:Ann,age:4)

PowerAssertions-othercomplexcaseevenforcomplicatedstructuresandexpressions

assertann.name==bob.name&&ann.age==bob.age

detailedevaluationofsubelements

Assertionfailed:

assertann.name==bob.name&&ann.age==bob.age|||||||Ann||Bobfalse||Person(name:Bob,age:7)|falsePerson(name:Ann,age:4)

secondpartignoredasmeaningless

PowerAssertionsself-explaining

optionalassertkeywordinthenandexpectcodeblock

unlessplacedinClosureorseparatemethod

backportedtoGroovy

Reason3Firstclasssupport

forparameterizedtests

Parameterizedtestsdef"shouldsumtwointegers"(){given:Calculatorcalculator=newSimpleCalculator()when:intresult=calculator.add(x,y)then:result==expectedResultwhere:x|y||expectedResult1|2||3-2|3||1-1|-2||-3}

Parameterizedtestsdef"shouldsumtwointegers"(){given:Calculatorcalculator=newSimpleCalculator()when:intresult=calculator.add(x,y)then:result==expectedResultwhere:x|y||expectedResult1|2||3-2|3||1-1|-2||-3}

build-insupportwithwherekeyword

doesnotstoponfailureforgiventestcase

syntacticsugarfortable-likedataformatting

Parameterizedtests-evenbetter@Unrolldef"shouldsumtwointegers(#x+#y=#expectedResult)"(){given:Calculatorcalculator=newSimpleCalculator()when:intresult=calculator.add(x,y)then:result==expectedResultwhere:x|y||expectedResult1|2||3-2|3||1-1|-2||-3}

separatetestforeveryinputparametersset-@UnrollvisiblealsoinreportsandIDEinputparameterspresentedintestname(with#)

datapipesanddataprovidersforadvancedusecases

Reason4Built-inmockingframework

SimpleStubbingclassDaoSpecextendsSpecification{

def"shouldstubmethodcall"(){given:Daodao=Stub()dao.getCount()>>1expect:dao.getCount()==1}}

interfaceDao{intgetCount()Itemsave(Itemitem)}

SimpleStubbing-customlogicclassDaoSpecextendsSpecification{

def"shouldthrowexceptionforspecificinputparameters"(){given:Dao<Item>dao=Stub()dao.save(_)>>{Itemitem->thrownewIllegalArgumentException(item.toString())}when:dao.save(newItem())then:thrown(IllegalArgumentException)}}

_foranyargumentvalue

argumentsavailableinsideClosure

MockinteractionsverificationclassDaoSpecextendsSpecification{

def"shouldstubandverify"(){given:ItembaseItem=newItem()and:Dao<Item>dao=Mock()when:dao.delete(baseItem)then:1*dao.delete(_)}}

1*-methodcalledonce

(_)-withanyvalueasthefirstparameter

StubbingandverifyingtogetherclassDaoSpecextendsSpecification{

def"shouldstubandverify"(){given:ItembaseItem=newItem()and:Dao<Item>dao=Mock()when:ItemreturnedItem=dao.save(baseItem)then:1*dao.save(_)>>{Itemitem->item}and:baseItem.is(returnedItem)}}

hastobedefinedinthesamestatementinthensection

StubbingandverifyingtogetherclassDaoSpecextendsSpecification{

def"shouldstubandverify"(){given:ItembaseItem=newItem()and:Dao<Item>dao=Mock()when:ItemreturnedItem=dao.save(baseItem)then:1*dao.save(_)>>{Itemitem->item}and:baseItem.is(returnedItem)}}

hastobedefinedinthesamestatementinthensectiondesignsuggestion:inmostcasesstubbingandinteractionverificationofthesamemockshouldn'tbeneeded

Reason5Exceptiontesting

Capturingthrownexceptiondef"shouldcaptureexception"(){when:throwNPE()then:NullPointerExceptione=thrown()e.message=="testNPE"}

Capturingthrownexceptiondef"shouldcaptureexception"(){when:throwNPE()then:NullPointerExceptione=thrown()e.message=="testNPE"}

thrownexceptioninterceptedandassignedtovariable

forfurtherasserting

testfailedifnotthrown

orhasunexpectedtype

Exceptionsutilityclasstoplaywithcausechain

Reason6Groovymagic

Groovy-whybother?smarter,shorten,morepowerfulJava

Closuretomakefunctionsfirst-classcitizen

Javacode(inmostcases)isalsovalidGroovycode

flatlearningcurve

seamlesslyintegrationwithJavacode

canuseJavalibraries

Groovy-listsandsetscompactsyntaxforlistandsetcreation

List<String>names=['Ann','Bob','Monica','Scholastica']

Set<Integer>luckyNumbers=[4,7,9,7]asSet//4,7,9

Groovy-listsandsetscompactsyntaxforlistandsetcreation

List<String>names=['Ann','Bob','Monica','Scholastica']

Set<Integer>luckyNumbers=[4,7,9,7]asSet//4,7,9

accessing

StringsecondName=names[1]//BobStringlastName=names[-1]//Scholastica

Groovy-listsandsetscompactsyntaxforlistandsetcreation

List<String>names=['Ann','Bob','Monica','Scholastica']

Set<Integer>luckyNumbers=[4,7,9,7]asSet//4,7,9

accessing

StringsecondName=names[1]//BobStringlastName=names[-1]//Scholastica

modification

names[1]='Alex'//Ann,Alex,Monica,Scholasticanames<<'John'//Ann,Alex,Monica,Scholastica,JohnSet<Integer>withoutSeven=luckyNumbers-7//4,9

Groovy-mapscompactsyntaxformapcreation

Map<String,Integer>childrenWithAge=[Ann:5,Bob:7,Monica:9,Scholastica:7]

Groovy-mapscompactsyntaxformapcreation

Map<String,Integer>childrenWithAge=[Ann:5,Bob:7,Monica:9,Scholastica:7]

accessing

childrenWithAge['Ann']//5

Groovy-mapscompactsyntaxformapcreation

Map<String,Integer>childrenWithAge=[Ann:5,Bob:7,Monica:9,Scholastica:7]

accessing

childrenWithAge['Ann']//5

modification

childrenWithAge['Bob']=8//Ann:5,Bob:8,Monica:9,Scholastica:7

Map<String,Integer>withAlice=childrenWithAge+[Alice:3]//Ann:5,Bob:8,Monica:9,Scholastica:7,Alice:3

FunctionalGroovy-Closuresoperationsoncollection

List<String>names=['Ann','Bob','Monica','Scholastica']

names.findAll{Stringname->name.length()>3}//Monica,Scholastica

FunctionalGroovy-Closuresoperationsoncollection

List<String>names=['Ann','Bob','Monica','Scholastica']

names.findAll{Stringname->name.length()>3}.collect{Stringname->//canbechainedname.toUpperCase()}//MONICA,SCHOLASTICA

FunctionalGroovy-Closuresoperationsoncollection

List<String>names=['Ann','Bob','Monica','Scholastica']

names.findAll{Stringname->name.length()>3}.collect{Stringname->//canbechainedname.toUpperCase()}//MONICA,SCHOLASTICA

ittorefersClosureexecutionargument-insimplecases

Set<Integer>luckyNumbers=[4,7,9,7]asSet

luckyNumbers.findAll{it%2==0}//4

FunctionalGroovy-Closuresinlinedfunctionalinterfaces

//productionJavamethodtocallvoidexecuteMultipleTimes(intnumber,RunnablecodeToExecute);

executeMultipleTimes(5,{println"Executed"})

//orsimplierexecuteMultipleTimes(5){println"Executed"}

(G)Stringsvariablereference

voidprintMagicNumber(intnumber){println"Todaymagicnumberis$number.Congrats!"}

(G)Stringsvariablereference

voidprintMagicNumber(intnumber){println"Todaymagicnumberis$number.Congrats!"}

methodexecution

println"Millisecondssincetheepoch:${System.currentTimeMillis()}."

(G)Stringsvariablereference

voidprintMagicNumber(intnumber){println"Todaymagicnumberis$number.Congrats!"}

methodexecution

println"Millisecondssincetheepoch:${System.currentTimeMillis()}."

multi-linestring

StringmailBody="""HelloUser,Welcometoournewsletter.Haveagoodday!"""

(G)Stringsvariablereference

voidprintMagicNumber(intnumber){println"Todaymagicnumberis$number.Congrats!"}

methodexecution

println"Millisecondssincetheepoch:${System.currentTimeMillis()}."

multi-linestring

StringmailBody="""HelloUser,Welcometoournewsletter.Haveagoodday!""".stripIndent()

Reason7Extensibility

Extensibilityverypowerfulextensionsmechanism

dozensofinternalfeaturesimplementedasextensions

providedout-of-box

@AutoCleanup,@IgnoreIf,@RestoreSystemProperties,...

manyextensionsasexternalprojects

abilitytoreuseJUnit's@Ruleand@ClassRule

Summary

WhySpock?consistandreadabletestcode

testsasspecificationbydefault

allGroovymagicavailabletohelp

chancetolearnnewlanguage

embeddedmockingframework

althoughMockitocanbeusedifpreferred(orneeded)

highlyextensible

compatiblewithtoolssupportingJUnit

What'snext?

What'snext?inmostcasesitisworthtogiveSpockatry

What'snext?inmostcasesitisworthtogiveSpockatry

andfallinlovewithitsreadabilityandsimplicity

What'snext?inmostcasesitisworthtogiveSpockatry

andfallinlovewithitsreadabilityandsimplicity

let'smakeasmallexperiment:)

writeallnewtestsinyourteam/projectentirelyinSpock

foraweek

decideifyoulikedit

tip:sampleSpockconfigurationforGradleandMavenavailableonmyblog

Questions?

MarcinZajączkowski

http://blog.solidsoft.info/@SolidSoftBlog

m.zajaczkowski@gmail.com

Thankyou!

MarcinZajączkowski

http://blog.solidsoft.info/@SolidSoftBlog

m.zajaczkowski@gmail.com