• Save
Spock: A Highly Logical Way To Test
Upcoming SlideShare
Loading in...5
×
 

Spock: A Highly Logical Way To Test

on

  • 8,292 views

As presented at No Fluff Just Stuff San Antonio, April 14th 2012.

As presented at No Fluff Just Stuff San Antonio, April 14th 2012.

Statistics

Views

Total Views
8,292
Views on SlideShare
7,615
Embed Views
677

Actions

Likes
6
Downloads
0
Comments
0

4 Embeds 677

http://d.hatena.ne.jp 674
https://si0.twimg.com 1
https://twitter.com 1
https://www.google.co.jp 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-NoDerivs LicenseCC Attribution-NoDerivs License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Spock: A Highly Logical Way To Test Spock: A Highly Logical Way To Test Presentation Transcript

  • Spock: A Highly LogicalWay To TestHoward M. Lewis ShipTWD Consultinghlship@gmail.com@hlship © 2012 Howard M. Lewis Ship
  • Why Dont We Test? Hard To Get Started My Codes Perfect Code Too Monolithic Tests Broke, Nobody Fixed, Turned Off Uh, We Used To? More Code Is More Code Test Code Hard To Maintain
  • What if ... Test code was readable? Tests were concise? Test reports were useful? Failures were well described? Mocking was easy?… wouldnt that be most logical?
  • My Path Wrote own test framework – 1990s in PL/1 – 5000 - 7000 tests First JUnit tests (Tapestry 2001 template parser) 2003-ish Started using TestNG 2004-ish Started using EasyMock 2005-ish Started using Selenium 1 2006-ish Dabbled in Groovy 2010 Spock! ~ 0.4
  • Terminology FixtureSpecification Collaborator System Feature Feature Under Specification Collaborator
  • First Specification sp oSkySpecification.groovy gro ck 0 ov .package org.example y-1 6- .8import spock.lang.*class SkySpecification extends Specification { ...} All Specifications extend from this base class Sky.java package org.example.sus; public class Sky { public String getColor() { return "blue"; } }
  • Feature Methods def "sky is blue"() { setup: def sky = new Sky() expect: sky.color == "blue" }
  • Execution$ gradle testNote: the Gradle build daemon is an experimental feature.As such, you may experience unexpected build failures. You may need tooccasionally stop the daemon.:compileJava UP-TO-DATE:compileGroovy UP-TO-DATE:processResources UP-TO-DATE:classes UP-TO-DATE:compileTestJava UP-TO-DATE:compileTestGroovy $ tree build/reports/:processTestResources UP-TO-DATE build/reports/:testClasses └── tests:test ├── base-style.css ├── css3-pie-1.0beta3.htcBUILD SUCCESSFUL ├── index.html ├── org.example.SkySpecification.htmlTotal time: 3.831 secs ├── org.example.html~/workspaces/github/spock-examples ├── report.js$ └── style.css 1 directory, 7 files ~/workspaces/github/spock-examples $
  • sky.color == "green"
  • Feature Method Blocks def "sky is blue"() { setup: def sky = new Sky() expect: sky.color == "blue" } setup: or given: expect: when: then: where: cleanup: Feature methods must contain at least one block otherwise, not a feature method
  • setup: Initialization of the fixture Always at top of method Cant be repeated Can be given: instead Anything up to first label is implicit setup:
  • expect: Response expect: sky.color == "blue" Stimulus Combines stimulus and response Contains only conditions and variable definitions Conditions assert Groovy truth Best for purely functional (no-side effects) functions
  • when: / then: def "clouds are grey"() { def sky = new Sky() when: Stimulus sky.addStormSystem() then: sky.color == "grey" Response } Used as a pair Tests method with side effects then: may only contain conditions, exception conditions, interactions and variable definitions
  • when: / then: def "clouds are grey"() { def sky = new Sky() when: sky.addStormSystem() then: sky.color == "grey" }
  • Test drivenSky.javapackage org.example.sus;public class Sky { private String color = "blue"; public String getColor() { return color; } public void addStormSystem() { color = "grey"; }}
  • then: Old Values def "pushing an element on the stack increases its size by one"() {   def stack = new Stack()   when: stack.push("element")   then: stack.size() == old(stack.size()) + 1 } Expression value captured before where: block
  • Extended Assert def "use of extended assert"() { expect: assert 4 == 5, "Big Brother says there are four fingers" }
  • cleanup: setup: def file = new File("/some/path") file.createNewFile() // ... cleanup: file.delete() Cleanup external resources Always invoked, even if previous exceptions
  • where: def "length of crew member names"() { expect: name.length() == length where: name     | length "Spock"  | 5 "Kirk"   | 4 "Scotty" | 6 } Parameterizes feature method with data Must be last block Can use | or || as separator
  • where: One entry for three feature method executions
  • where: using lists def "length of crew member names (using lists)"() { expect: name.length() == length where: name << ["Spock", "Kirk", "Scotty"] length << [5, 4, 6] } Could come from external file or database where: [name, age, gender] = sql.execute("select name, age, sex from ⏎ customer")
  • where: derived values def "length of crew member names (with derived values)"() { expect: name.length() == length where: name << ["Spock", "Kirk", "Scotty"] length = name.length() }
  • Block labels def "clouds are grey"() { given: "A fresh Sky" def sky = new Sky() when: "A storm system rolls in" sky.addStormSystem() then: "It all goes grey" sky.color == "grey" } Allowed, but not (currently) used and: "block" allowed, does nothing
  • Beyond Feature Methods
  • Fields package org.example import org.example.sus.Sky import spock.lang.Specification class SkySpecification extends Specification { def sky = new Sky() New for each feature method def "sky is blue by default"() { expect: sky.color == "blue" } def "clouds are grey"() { given: "A fresh Sky" when: "A storm system rolls in" sky.addStormSystem() then: "It all goes grey" sky.color == "grey" } }
  • Shared Fields Created once, shared across all instances class MySpecification extends Specification { @Shared def resource = new AnExepensiveResource() static final PASSWORD = "sayfriendandenter" … } Statics should be final and immutable
  • Fixture Methods def setup() { … } def cleanup() { … } Create / initialize instance of Specification Invoke setup() Invoke feature method Invoke cleanup()
  • Fixture Methods def setupSpec() { … } def cleanupSpec() { … } Instance created for Specification setup / cleanup May only access @Shared and static fields
  • Exception Conditionsdef ins = new ClassInstantiatorImpl(ContextCatcher, ⏎ ContextCatcher.constructors[0], null)def "may not add a duplicate instance context value"() { given: def ins2 = ins.with(String, "initial value") when: ins2.with(String, "conflicting value") then: def e = thrown() Or: e = thrown(IllegalStateException) e.message == "An instance context value of type java.lang.String ⏎ has already been added."}
  • Typed Exceptionsdef ins = new ClassInstantiatorImpl(ContextCatcher, ⏎ ContextCatcher.constructors[0], null)def "may not add a duplicate instance context value"() { given: def ins2 = ins.with(String, "initial value") when: ins2.with(String, "conflicting value") then: IllegalStateException e = thrown() e.message == "An instance context value of type java.lang.String ⏎ has already been added."}
  • notThrown() def "HashMap accepts null key"() {   setup:   def map = new HashMap()     when:   map.put(null, "elem")     then:   notThrown(NullPointerException) } Documentation value onlyAlso: noExceptionThrown()
  • Mocks and Interactions
  • FixtureSpecification Collaborator Feature SystemUnder Feature Specification Collaborator
  • Configured System Instance UnderSpecification Mock Object
  • Payment CustomerDAO ProcessorPaymentProcessor.groovypackage org.example.susclass PaymentProcessor { CustomerDAO customerDAO def applyPayment(long customerId, BigDecimal amount) { Customer customer = customerDAO.getById(customerId) if (customer == null) throw new IllegalArgumentException("No customer #$customerId") customer.accountBalance += amount CustomerDAO.java package org.example.sus; customerDAO.update(customer) } public interface CustomerDAO {} Customer getById(long id); void update(Customer customer); }
  • class ApplyPaymentSpecification extends Specification { CustomerDAO dao = Mock() Factory method PaymentProcessor processor def setup() { processor = new PaymentProcessor(customerDAO: dao) } …}|
  • Defining Mock Behavior def "unknown customer id is an exception"() { when: processor.applyPayment(12345, 100) then: 1 * dao.getById(12345) >> null Define behavior for preceding when: block IllegalArgumentException e = thrown() e.message == "No customer #12345" }
  • def "valid customer id for update"() { when: processor.applyPayment(customer.id, 200) then: 1 * dao.getById(customer.id) >> customer 1 * dao.update(customer) customer.accountBalance == 500 where: customer = new Customer(id: 98765, accountBalance: 300)}
  • Target and Method Constraints Argument Constraints 1 * dao.getById(12345) >> null Number of invocations: Returns a value cardinality
  • Cardinality Omitted ➠ Interaction is optional, must have return value n * mock.method(…) ➠ exactly n times (n.._) * mock.method(…) ➠ at least n times (_..n) * mock.method(…) ➠ Up to n times
  • Argument Constraints _ ➠ Any argument *_ ➠ Any number of arguments !null ➠ Any non-null argument value ➠ Argument equals value !value ➠ Argument not equal to value _ as Type ➠ Non-null argument assignable to Type
  • Closures for Argument Contraintsclass InteractionsSpecification extends Specification { Operation mock = Mock() interface Operation { Object op(input) } def isOdd(input) { input % 2 != 0 } Helper method def "closure for parameter constraint"() { when: assert mock.op(3) == 6 assert mock.op(7) == 14 then: (2..7) * mock.op({ isOdd(it) }) >> { 2 * it } } Return value computed by closure
  • def "wrong number of invocations"() { when: assert mock.op(7) == 14 then: (2..7) * mock.op({ isOdd(it) }) >> { 2 * it } }Too few invocations for:(2..7) * mock.op({ result = 2 * it; return it % 2 != 0 }) >> { result } (1 invocation)& at org.spockframework.mock.InteractionScope.verifyInteractions(InteractionScope.java:66)& at org.spockframework.mock.MockController.leaveScope(MockController.java:35)& at org.example.InteractionsSpecification.wrong number ofinvocations(InteractionsSpecification.groovy:32)
  • Mocks are Lenient def "parameter does not match closure constraint"() { when: assert mock.op(3) == 6 assert mock.op(4) == null assert mock.op(7) == 14 then: _ * mock.op({ isOdd(it) }) >> { 2 * it } }
  • Less Lenient Mocks def "detecting parameter that doesnt match"() { when: assert mock.op(3) == 6 assert mock.op(4) == null assert mock.op(7) == 14 then: _ * mock.op({ isOdd(it) }) >> { 2 * it } 0 * _ aka "any interaction" }Too many invocations for:0 * _ (1 invocation)Last invocation: mock.op(4)& at org.spockframework.mock.MockInteraction.accept(MockInteraction.java:58)& at org.spockframework.mock.MockInteractionDecorator.accept(MockInteractionDecorator.java:41)& at org.spockframework.mock.InteractionScope$1.accept(InteractionScope.java:38)& at org.spockframework.mock.MockController.dispatch(MockController.java:42)& at org.spockframework.mock.DefaultMockFactory$1.invoke(DefaultMockFactory.java:70)& at org.example.InteractionsSpecification.detecting parameter that doesntmatch(InteractionsSpecification.groovy:58)
  • Target and Method Constraints Target Constraint 1 * dao.getById(12345) >> null Method Constraint
  • Target Constraints name ➠ Match the mock in the variable or field _ ➠ Match any mock
  • Method Constraints name ➠ Match method with given name /re/ ➠ Match methods matching regular expression ➠ e.g. bean./set.*/(_) _ ➠ Match any method
  • Return Values >> value ➠ Return the value >> { … } ➠ Evaluate the closure and return the result >>> [ a, b, c] ➠ Return a, then return b, then keep returning c ➠ Any kind of iterable list, any size
  • Chained Return Values then: service.getStatus() >>> ["ok", "ok", "fail"] >> { throw new RuntimeException("Status failure."); } The last value sticks
  • Ordered Interactions def "test three amigos"() { when: facade.doSomething() then: 1 * collab1.firstPart() No order checking 1 * collab2.secondPart() on firstPart(), secondPart() then: 1 * collab3.thirdPart() } thirdPart() only allowed after both firstPart(), secondPart()
  • Stubbing def "frobs the gnop"() { A "global interaction" checker.isValid(_) >> true valid to end of when: method … }
  • Extensions
  • @Unroll @Unroll def "Crew member #name length is #length"() { expect: name.length() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 }
  • @Unrollclass CanonicalWhereBlockExampleSpecification extends Specification { def "Crew member #name length is #length"() { … } def "length of crew member names (using lists)"() { … } def "length of crew member names (with derived values)"() { … }}
  • @Timeout @Timeout(5) def "can access data in under five seconds"() { … } @Timeout(value=100, unit=TimeUnit.MILLISECONDS) def "can update data in under 100 ms"() { … }Feature and fixture methods run on main threadA second thread may interrupt the main threadCan be placed on class to affect all feature methods
  • @Stepwise @Stepwise class BuildingBlocksSpecification extends Specification { def "first step()" { … } def "second step"() { … } def "third step"() { … }Feature methods run in declaration orderFailed methods cause remainder to be skipped
  • @AutoCleanup def DatabaseSpecification … { @AutoCleanup @Shared Connection connection … }Will invoke close() on fields valueExceptions are reported but are not failuresquiet=true➠ Dont report exceptionsvalue attribute is name of method to invoke
  • @Ignore / @IgnoreRest Used to temporarily control which feature methods execute @Ignore ➠ Ignore this method, run others @IgnoreRest ➠ Run this method, ignore others
  • More Info
  • https://github.com/spockframework/spock
  • http://code.google.com/p/spock/
  • http://docs.spockframework.org
  • https://github.com/hlship/spock-examples
  • http://howardlewisship.com
  • Q&A