48 Hours With Spock

Spock on Trial

Spock operating the Spock web console
Here is Spock operating the Spock web console

Ok, only had 2 days to learn as much as possible about a testing tool. Chose Spock as it is groovy-based and i am into the groovy eco-system now with groovy, gradle, gaelyf, grails, caelyf and the new kid on the block called ‘grain’. Here’s a list of references i found to be most useful in getting up to speed with spock.

trishaA gal that writes clearly on the plus points and perils of Spock, and testing in general, is Trisha Gee. Here are several of her spock-related posts :

If you prefer a set of slides to walk you thru the Spock framework, try this. It’s a bit deep for beginners but you’ll need it after you get your feet wet.


  • see: http://www.slideshare.net/hlship/spock-a-highly-logical-way-to-test and a mocking framework is discussed at slide 39+
  • along with cardinality like
    
            then:
                1 * dao.getById(123) >> null
                illegalArgumentException e = thrown()
                e.message == "No customer 123"
    
                1 * dao.getById(123) >> null
                ^ cardinality/invocations
                     ^ target method
                                  ^ argument
                                         ^ return value            
    

Yes and no I don’t understand cardinality yet myself, so we’ll leave it till later.

MyFirstSpecification.groovy

Picture 2There is always a first try at anything. Here’s a script written in groovy as we took Spock out for a test drive. There are plenty of features that work as well as some that don’t. Copy this into your groovyConsole (or try the Spock web console, but leave of the ‘Grapes(…) bits).

The ‘Grapes’ will grab the spock jar from a maven repo. This will mean you only need an installed groovy engine.



@Grapes(
    @Grab(group='org.spockframework', module='spock-core', version='0.7-groovy-2.0')
)

import spock.lang.*
class MyFirstSpecification extends Specification {
  // fields
  def static num= 0
  def fh = null;
  def balance = 1.25

  // fixture methods
  /*
    Fixture methods are responsible for setting up and cleaning up the environment 
    in which feature methods are run. Usually it's a good idea to use a fresh fixture 
    for every feature method, which is what the setup() and cleanup() methods are for. 

    Occasionally it makes sense for feature methods to share a fixture, which is achieved 
    by using shared fields together (see below) with the setupSpec() and cleanupSpec() methods. 
    All fixture methods are optional.  

    The setupSpec() and cleanupSpec() methods may not reference instance fields, only @Shared and statics.

    One fixture methos will typically:
    1) create instance of Specification
    2) invoke setup()
    3) invoke feature method
    4) invoke cleanup()    
  */
  def setup() { println "setup() num="+num; }          // run before every feature method

  def cleanup() {println "cleanup num="+num; ++num;  }        // run after every feature method

  def setupSpec() { println "// run before the first feature method" }     // run before the first feature method
  def cleanupSpec() { println "// run after the last feature method" }   // run after the last feature method

  // feature methods
/*
Feature methods are the heart of a specification.Conceptually, a feature method consists of four phases:

Set up the feature's fixture
Provide a stimulus to the system under specification
Describe the response expected from the system
Clean up the feature's fixture

Whereas the first and last phases are optional, the stimulus and response phases are always present 
(except in interacting feature methods), and may occur more than once.
*/

  def "increment the counter"() 
  {
     setup: "open a file connection"
     // code goes here
     fh = new File("/Volumes/FHD-XS/spock");

     and: "seed the customer table"
     // code goes here

     when:
     num += 1;

     then:
         assert num == 1, "increment counter failed"    // power assert with "added notes" after condition

     //where:
      // blocks go here
  } // end of increment

/*
    Specifications as Documentation
    Well-written specifications are a valuable source of information. 
    Especially for higher-level specifications targeting a wider audience 
    than just developers (architects, domain experts, customers, etc.), 
    it makes sense to provide more information in natural language than 
    just the names of specifications and features. Therefore, Spock provides 
    a way to attach textual descriptions to blocks:
*/
  //@Ignore
  def "In Behavior Driven Development, customer-facing features (called stories) are described in a given-when-then format"()
  {
    given: "an empty bank account"  // alias for setup:
    // ...
    balance = 0.00

    when: "the account is credited \$10"
    // ...
    balance += 10.01

    then: "the account's balance is \$10"
    // ...
    balance == 10.01

  } // end of def

  /* the 'expect:' choice combines stimulus & response - best for no side-effect functions
   expect: sky.color == "blue"
           stimulus     response

   ---------
   when: then: combo used as a pair and work if methods have side effects; 
         then: only for conditions,exceptions, interactions and var defs

         def "clouds are grey"(){
            given: "a fresh object"
                    def sky = new Sky()

            when: sky.addStormSystem()  // since this method does not exist, test will fail

            then: sky.color == "grey"         
         } // end of def

   ---------
   // example of the 'old' keyword
         def "push ele onto stack"(){
            def stack = new Stack()
            when: stack.push("ele")
            then: stack.size() == old(stack.size()) + 1         
         } // end of def

   --------
   // extended asserts
       def "try asserts with comments"(){
        expect: assert 4==5, "naw, this won't work !"       
       } // end of def

  */

  // where: block using lists
  def "can you understand this ?"()
  {
      expect:
      name.size() == length

      where:
      name << ["Kirk","Spock","Scotty"]
      length  true
        when:
            checker.isValid()
        then :
            assert checker.isValid() == true, "my dog has flees"
    } // end of def

  // helper methods
}


Sysout Terminal Session


// test results from syslog:

// run before the first feature method
setup() num=0
cleanup num=1
setup() num=2
cleanup num=2
setup() num=3
cleanup num=3
setup() num=4
cleanup num=4
setup() num=5
cleanup num=5
setup() num=6
cleanup num=6
setup() num=7
cleanup num=7
setup() num=8
cleanup num=8
setup() num=9
cleanup num=9
setup() num=10
cleanup num=10
setup() num=11
cleanup num=11
// run after the last feature method
JUnit 4 Runner, Tests: 7, Failures: 4, Time: 457
Test Failure: increment the counter(MyFirstSpecification)
Condition not satisfied:

num == 0

increment counter failed

    at MyFirstSpecification.increment the counter(SpockSpecifications.groovy:86)

Test Failure: length of crew member names(MyFirstSpecification)
Condition not satisfied:

name.size() == length
|    |      |  |

Kirk 4      |  5
            false

    at MyFirstSpecification.length of crew member names(SpockSpecifications.groovy:181)

Test Failure: might fail(MyFirstSpecification)
Expected exception java.lang.IllegalStateException, but no exception was thrown
    at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:79)
    at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:60)
    at MyFirstSpecification.might fail(SpockSpecifications.groovy:207)

Test Failure: stubbing a global good to end of method(MyFirstSpecification)
groovy.lang.MissingPropertyException: No such property: Person for class: MyFirstSpecification
    at MyFirstSpecification.stubbing a global good to end of method(SpockSpecifications.groovy:220)

Result: org.junit.runner.Result@a064fb

Live long and prosper …

Stay tuned for part two when we will decode the vulcan terminology and explore some of the mysteries that is Spock.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s