Automating Specification with Cucumber & Grails

Specification by Example is an interesting approach to build a specification in collaboration with a customer. One part of it is to automate the specification without changing it and to build a living documentation system that is always in sync with the software.

One possibility to automate the specification is to describe it using Cucumbers Given, When & Then style and writing some glue code to connect the steps with the system we build.

I will introduce you to a new cucumber grails plugin (grails.org/plugins, documentation) that make it a lot easier to automate cucumber specifications of a Grails application.

The plugin is a functional test plugin for grails and it is only available in grails functional test phase. It is interesting because it runs cucumber inside a running grails application which allows us to use the grails api in the cucumber step implementations. We can call GORM stuff like dynamic finders, services and controllers.

To make the magic work, the plugin is based on Cucumber-JVM the native JVM implementation of cucumber. Cucumber-JVM does support many JVM languages. Groovy is one of them which makes it a perfect fit for grails.

As we will see in the following introduction, the plugin integrates into grails standard test infrastructure which makes it quite easy to use. The full source code of the example and the plugin itself is online in my github repository (example source code, plugin source code).

We will walk through a very simple example that will use cucumber to test two scenarios against a grails web application. We will implement the steps under the skin of the application, i.e. below the ui.
I have written another article (here) that runs the same example against the ui using Geb in the steps to remote control a web browser (Note: because of my latest changes in the plugin the article is no longer 100% up to date).

Let us start with the setup…

setup

First, create a grails app with: grails create-app Books (I am using grails 2.0.3). Next, add the plugin dependency to grails-app/conf/BuildConfig.groovy in the plugins section:

plugins {
   test ":cucumber:0.4.0"
}

Done, setup complete.

.feature files

By convention, the plugin expects all cucumber files (features and steps) in test/functional (you can change it in the configuration, see the documentation).

Create the following two .feature files in test/functional which describe the functionality of the example application:

NewBook.feature:

Feature: new book entry
    As a book owner
    I want to add books I own to the book tracker
    so that I do not have to remember them by myself

Scenario: new book
   Given I open the book tracker
    When I add "Specification by Example"
    Then I see "Specification by Example"s details

ListBooks.feature:

Feature: list owned books
    As a book owner
    I want to list my books
    so that I can look up which books I own

@ignore
Scenario: list existing books
   Given I have already added "Specification by Example"
    When I view the book list
    Then my book list contains "Specification by Example"

Specification by Example: Website, amazon.com

running the features

I already mentioned that the plugin properly integrates into grails and that it is a functional test plugin. To run the cucumber features only we can use the usual grails test-app command using one of the following variations:

grails test-app functional:cucumber
grails test-app :cucumber
grails test-app functional:

Running it we will see the typical cucumber output for missing step implementations:

You can implement missing steps with the snippets below:

Given(~"^I have already added \"([^\"]*)\"$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
When(~"^I view the book list$") { ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
Then(~"^my book list contains \"([^\"]*)\"$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
Given(~"^I open the book tracker$") { ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
When(~"^I add \"([^\"]*)\"$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
Then(~"^I see \"([^\"]*)\"s details$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

tagging features

To concentrate on implementing the steps for the NewBook feature we will disable the second one by adding an @ignore tag to it and by telling the plugin to ignore the scenarios tagged with @ignore:

ListBooks.feature:

Feature: list owned books
    As a book owner
    I want to list my books
    so that I can look up which books I own

@ignore
Scenario: list existing books
   Given I have already added "Specification by Example"
    When I view the book list
    Then my book list contains "Specification by Example"

The plugin will pick up a couple of configuration options from grails-app/conf/CucumberConfig.groovy, so we create it and add a tags configuration like this:

cucumber {
    tags = ["~@ignore"]
}

tags is list of strings and each item corresponds to a standard cucumber --tags option.

Running grails test-app :cucumber again, we will only get the missing steps message for the NewBook feature:

You can implement missing steps with the snippets below:

Given(~"^I open the book tracker$") { ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
When(~"^I add \"([^\"]*)\"$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
Then(~"^I see \"([^\"]*)\"s details$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

implementing steps, part one

Create a new file test/functional/steps/Book_steps.groovy and

  • copy the step templates into it
  • add an import for PendingException
  • mixin the EN language of Gherkin
  • escape the $ at the end of the regular expression because in groovy it is a special character in GStrings. Alternativly you can replace the double quotes with single quotes.

Finally it should look like this:

import cucumber.runtime.PendingException

this.metaClass.mixin (cucumber.runtime.groovy.EN)


Given (~"^I open the book tracker\$") { ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

When (~"^I add \"([^\"]*)\"\$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

Then (~"^I see \"([^\"]*)\"s details\$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

Running again will throw a PendingException. To implement the steps we will need a domain class, a service and a controller. That is what we will do next, step by step…

Given (~‘^I open the book tracker$’)

This step is easy. We want to test under the skin ignoring the ui, so we simply do nothing in this step. In a real project we would probably check that the controller has an action that returns the entry page of our application.

Given (~"^I open the book tracker\$") { ->
    // nop
}

When (~‘^I add “([^"]*)”$’)

In this step we want to add a book. We need a controller with an add action that we will call in the step implementation.

Create a BookController with an add action method. It will get the book details we enter in the ui form and it will return the new Book as JSON that will be shown in the ui. We don’t care what the ui does with it, we will only check that we get the correct JSON.

As is usual in grails the controller will just delegate the work to a service and prepare the result for the ui. In this case rendering the Book as JSON. Here is the controller code:

package books

import grails.converters.JSON

class BookController {
    def bookService

    def add () {
        render bookService.add (params) as JSON
    }
}

The service:

package books

class BookService {
    Book add (Map params) {
        def newBook = new Book (params)
        newBook.save ()
        newBook
    }
}

And finally the Book domain class:

package books

class Book {
    String author
    String title
}

After creating the grails artifacts we can finally implement the step like this:

import books.BookController
import data.Data

// Scenario State
BookController bookController

When (~"^I add \"([^\"]*)\"\$") { String bookTitle ->
    bookController = new BookController ()
    bookController.params << Data.findByTitle (bookTitle)
    bookController.add ()
}

Note the additional imports and the bookController variable we will need to call the controller we create in the Then step.

If you are familiar with Grails you will probably understand most of it. We are simply using grails standard integration test logic for controllers.

Data is a small helper class that will lookup all the properties of a book identified by the bookTitle. This has the advantage that we do not have to list all properties of a book in the .feature file and the feature is more robust against changes like adding an additional property. That the additional property gets handled properly would be tested on the integration or unit test level.

Here is the source for test/functional/data/Data.groovy:

package data

import books.Book


class Data {
    static def books = [
        [title: "Specification by Example", author: "Gojko Adzic"]
    ]

    static public def findByTitle (String title) {
        books.find { book ->
            book.title == title
        }
    }

    static void clearBooks () {
        Book.findAll()*.delete (flush: true)
    }
}

Running the features now will still fail with a rather obscure java.lang.IllegalStateException exception (I guess at least for most of us, count me in). We are calling the controller outside of a real http request and to fix this we have to add a few simple lines of setup and tear down code.

Before & After

If we are building normal integration tests for a controller, grails will take care of setting up the test environment in a way that we will not get the IllegalStateException, that we have a mock request and response etc. and that we can set the request parameters with bookController.params = ... .

When we run cucumber we have to take care of this ourself. Let’s create a new file test/functional/hooks/env.grooy with the following content:

import org.codehaus.groovy.grails.test.support.GrailsTestRequestEnvironmentInterceptor

this.metaClass.mixin (cucumber.runtime.groovy.Hooks)


GrailsTestRequestEnvironmentInterceptor scenarioInterceptor

Before () {
    scenarioInterceptor = new GrailsTestRequestEnvironmentInterceptor (appCtx)
    scenarioInterceptor.init ()
}

After () {
    scenarioInterceptor.destroy ()
}

Running cucumber now will fail with a PendingException from our last step in this scenario. We still have to check that the result is correct to finish our first scenario.

Then (~‘^I see “([^"]*)”s details$’)

We need to assert that the returned JSON has an id (so we know it was save()d) and author and title with the same values we passed to the BookController:

Then (~"^I see \"([^\"]*)\"s details\$") { String bookTitle ->
    def expected = Data.findByTitle (bookTitle)
    def actual = bookController.response.json

    assert actual.id
    assert actual.title  == expected.title
    assert actual.author == expected.author
}

Next run and we see:

> grails test-app :cucumber --stacktrace
| Server running. Browse to http://localhost:8080/Books
| Running 1 cucumber test...
| Completed 1 cucumber test, 0 failed in 1623ms
| Server stopped
| Tests PASSED - view reports in target/test-reports

Great, the scenario passed! Now let’s finish our example application by implementing the steps for the ListBooks feature. There is a little bit more :-)

implementing steps, part two

First remove the @ignore tag from the list existing books scenario and run it again to get the templates for the missing steps and copy them to the BookSteps.groovy file (don’t forget to escape the ‘$’):

Given(~"^I have already added \"([^\"]*)\"$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
When(~"^I view the book list$") { ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}
Then(~"^my book list contains \"([^\"]*)\"$") { String arg1 ->
    // Express the Regexp above with the code you wish you had
    throw new PendingException()
}

Given (~‘^I have already added “([^"]*)”$’)

We want to pre-populate our database. We will reuse our BookService to create a new Book in the database. Reusing our business code to setup test data looks like a good idea to make sure that the we get proper data into the database without duplicating the code for our tests.

Here is the code:

Given (~"^I have already added \"([^\"]*)\"\$") { String bookTitle ->
    def bookService = appCtx.getBean ("bookService")
    bookService.add (Data.findByTitle (bookTitle))
}

Now that we have two features running and interacting with the database we should also take care of cleaning up the database after each scenario.

more Before & After

Here is the additional code for test/functional/hooks/env.groovy:

import data.Data

Before () {
    ....
    Data.clearBooks ()
}

After () {
    ....
}

You can also add the the cleanup to the After hook. I have choosen to add it to the setup because if a scenario fails we can still look at its database state. Maybe it will help to diagnose the problem.

When (~‘^I view the book list$’)

Not much new here, we have to deliver the full list of books. We get it by adding a new all() action method to the controller that will return the list as JSON.

Here is the step code:

When (~"^I view the book list\$") { ->
    bookController = new BookController ()
    bookController.all ()
}

Here the new methods for the controller:

....    
class BookController {
    ....    
    def all () {
        def books = bookService.all ()
        render books as JSON
    }
}

.. and the service:

class BookService {
    ....
    List<Book> all () {
        Book.findAll ()
    }
}

Running again fails with the now common PendingException in the last step.

Then (~‘^my book list contains “([^"]*)”$’)

We check that the returned list contains the book we have pre-populated to the database in the Given step.

Here is the code:

Then (~"^my book list contains \"([^\"]*)\"\$") { String bookTitle ->
    def expected = Data.findByTitle (bookTitle)
    def all = bookController.response.json
    actual = all.getJSONObject (0)

    assert actual.id
    assert actual.title  == expected.title
    assert actual.author == expected.author
}

It is nearly the same as the previous Then check in the first feature, we just have to extract the object from the list first. We should probably refactor the assertions to a method and use that in both Then steps. We will keep that as an exercise for you ;-)

Running the test will report two passed cucumber features:

> grails test-app :cucumber --stacktrace
| Server running. Browse to http://localhost:8080/Books
| Running 2 cucumber tests...
| Completed 2 cucumber tests, 0 failed in 1612ms
| Server stopped
| Tests PASSED - view reports in target/test-reports

In case you have not noticed, we are done! ;-)

Summary

Congratulations, you have succesfully implemented two features for our example application! :-)

We have implemented a couple of cucumber steps using a grails domain class, a service, a controller and a few lines of code for the before and after hooks. We have seen were to put the feature files and the step implementations and how we can configure @tags.

This should cover the basics to get you started with cucumber and grails. :-)

Happy Cuking!

Advertisements

The FizzBuzz Kata in Clojure

My first Clojure code was the Bowling Kata I wrote about in my last post. Today I did the FizzBuzz Kata and the stage 2 requirements. This was a lot simpler than the Bowling Kata.

There is not much to say about it, here is my test code:

(ns fizzbuzztest
 (:use fizzbuzz clojure.test))

(deftest nonefizzbuzz-numbers-return-itself
 (is (= (fizzbuzz '(1 2 4 7)) '(1 2 4 7))))

(deftest multiple-of-three-and-not-five-return-fizz
 (is (= (fizzbuzz '(3 6 9 12 18 21 24 27)) (take 8 (repeat "fizz")) )))

(deftest multiple-of-five-and-not-three-return-buzz
 (is (= (fizzbuzz '(5 10 20 25 40 50 55 65)) (take 8 (repeat "buzz")) )))

(deftest multiple-of-three-and-five-return-fizzbuzz
 (is (= (fizzbuzz '(15 30 45 60 75 90 105 120)) (take 8 (repeat "fizzbuzz")) )))

(deftest three-in-number-returns-fizz
 (is (= (fizzbuzz '(3 13 23 31 103)) (repeat 5 "fizz"))))

(deftest five-in-number-returns-buzz
 (is (= (fizzbuzz '(5 52 58 151 502)) (repeat 5 "buzz"))))

(run-tests)

and here is the implementation:

(ns fizzbuzz)

(defn- numeric-fizz? [n]
 (= (rem n 3) 0))

(defn- string-fizz? [n]
 (some #(= % \3) (str n)))

(defn- fizz? [n]
 (or (numeric-fizz? n) (string-fizz? n)))

(defn- numeric-buzz? [n]
 (= (rem n 5) 0))

(defn- string-buzz? [n]
 (some #(= % \5) (str n)))

(defn- buzz? [n]
 (or (numeric-buzz? n) (string-buzz? n)))

(defn- fizzbuzz? [n]
 (and (fizz? n) (buzz? n)))

(defn- replace-fizz-buzz [n]
 (cond
 (fizzbuzz? n) "fizzbuzz"
 (fizz? n) "fizz"
 (buzz? n) "buzz"
 (number? n) n))

(defn fizzbuzz [numbers]

 (map replace-fizz-buzz numbers))

I like the function that checks if a number contains a ‘3’ character. Although it looks weird it is not so hard to read after  getting a little used to Clojure code.

(defn- string-fizz? [n]
 (some #(= % \3) (str n)))

This functions gets a number n as parameter, which is converted to a string with (str n). The some calls the anonymous function #(= % \3) on each element (i.e. character) of n as string. If the anonymous function returns true, some return true as well. The anonymous function simply checks if its parameter (the %) is the character 3.

There is one issue with the cond in replace-fizz-buzz:the last condition (number? n) is just a dummy condition. Actually I don’t want it. If none of the other conditions match, it should just return the n. But that is not possible with cond. I’m looking for a better solution…

I also created my first Leiningen build script to run the tests. Leiningen is a build tool for Clojure. Here is the build script (named project.clj and placed in the project root folder):


(defproject fizzbuzz "1.0.0"
 :description "A cloure implmementation of the FizzBuzz kata."
 :dependencies [[org.clojure/clojure "1.1.0"]])

Quite simple ;-)

Running the following command:

$ lein test fizzbuzztest

prints:

 [null] Testing fizzbuzztest
 [null] Ran 6 tests containing 6 assertions.
 [null] 0 failures, 0 errors.
 [null] --------------------
 [null] Total:
 [null] Ran 6 tests containing 6 assertions.
 [null] 0 failures, 0 errors.

Works, but I have no idea, why it prints the [null] stuff.

The Bowling Kata in Clojure

I have bee reading Programming Clojure lately to learn about functional programming. Interest in functional programming is increasing so it might be a good idea to get familiar with it. :-)

Clojure is a Lisp like programming language that runs on the Java VM and also on the .NET CLR.

I didn’t have much knowledge about functional programming before and the book still left me with a lot of questions. After practicing OOP for most of my programming life, Clojure is very strange at first.

To get a little bit practice at it I spend a couple of hours (reading documentation, playing with the REPL) tdd’ing the Bowling Kata in Clojure.

There are already a few versions of the Clojure Bowling Kata on the net: Uncle Bob, Micah, Halloway. There is a lot of info in the comments of the first article and the third link points to a version from the author of the Clojure book mentioned above.

Here is mine :-)

I didn’t read the links in detail before I started because the idea was to do this on my own. So I guess there is still some potential for improvements.

If you are looking for an environment, you may try Intellij IDEA’s Community Edition and their Clojure plugin La-Clojure. That’s what I have used.

Unfortunately WordPress’s [sourcode] tag doesn’t seem to know Clojure yet. So no highlighting.

The tests:

(ns test
  (:use
    bowling
    clojure.contrib.test-is))

(defn- zero
  ([] (repeat 2 0))
  ([n] (repeat (* n 2) 0)))

(defn- spare
  ([] (repeat 2 5))
  ([n] (repeat (* n 2) 5)))

(deftest the-score-of-a-gutter-game-is-zero
  (is (= 0 (score (zero 10)))))

(deftest the-score-of-one-rolls-only-is-twenty
  (is (= 20 (score (repeat 20 1)))))

(deftest the-score-of-a-spare-includes-the-next-roll
  (is (= 16 (score (concat (spare) '(3 0)  (zero 8)))))
  (is (= 16 (score (concat (zero)   (spare) '(3 0) (zero 7)))))
  (is (= 16 (score (concat (zero 2) (spare) '(3 0) (zero 6)))))
  )

(deftest two-spares-in-a-row
  (is (= 31 (score (concat (spare 2) '(3 0) (zero 7))))))

(deftest three-spares-in-a-row
  (is (= 46 (score (concat (spare 3) '(3 0) (zero 6))))))

(deftest the-score-of-a-strike-includes-the-next-two-rolls
  (is (= 24 (score (concat '(10) '(3 4) (zero 8))))))

(deftest spare-in-last-frame
  (is (= 16 (score (concat (zero 9) (spare) '(3))))))

(deftest strike-in-last-frame
  (is (= 17 (score (concat (zero 9) '(10) '(3 4))))))

(deftest the-score-of-a-perfect-game-is-300
  (is (= 300 (score (repeat 12 10)))))

(run-tests)

and the code:

(ns bowling)

(defn- sum [rolls]
  (reduce + rolls))

(defn- spare? [rolls]
  (= 10 (sum (take 2 rolls))))

(defn- strike? [rolls]
  (= 10 (first rolls)))

(defn- more? [rolls]
  (seq rolls))

(defn- score-strike [rolls]
  (sum (take 3 rolls)))

(defn- score-spare [rolls]
  (sum (take 3 rolls)))

(defn- score-frame [rolls]
  (sum (take 2 rolls)))

(def score)

(defn- score-after-strike [rolls]
  (score
    (if (= 3 (count rolls))
      (drop 3 rolls)
      (drop 1 rolls)
    )))

(defn- score-after-frame [rolls]
  (score (drop 2 rolls)))

(defn score
  "calculate the bowling score of a [rolls] sequence"
  [rolls]
  (cond
    (empty? rolls)     0
    (strike? rolls)    (+ (score-strike rolls) (score-after-strike rolls))
    (spare?  rolls)    (+ (score-spare rolls)  (score-after-frame rolls))
    (more?   rolls)    (+ (score-frame rolls)  (score-after-frame rolls))
    )
  )

A few notes:

The last test I added (the-score-of-a-perfect-game-is-300) made me insert that strange if to (score-after-strike). This didn’t happen on the Java version. Because there is no loop that counts the frames in the Clojure version, one has to detect the last frame some other way. Without it, the last two strikes would not only be counted as the bonus for the 10th strike but also as an 11th and 12th frame.

I think my solution is not too bad for my first piece of Clojure code.

What I dislike is that every call in the (score) function has the rolls sequence as parameter. That is a lot of duplication noise. I see an object here ;-)

In the test I’m not sure if the (spare) and (zero) helper methods improve the readability. Maybe simply writing out every single roll would be easier to understand.

What do you think of my Clojure Bowling Kata? Is there anything that’s totally not the Clojure way? ;)

Update (10.2.2010)

After some feedback on the clojure group, I fixed the forward declaration of the (score) function. It should use (declare) and instead of (def) and I modified the score function to use (condp) with (apply). That is a small trick to avoid the rolls parameter on the predicate functions (empty? etc.). From a readability point, I think I still prefer the simple (cond) from my first version.

(ns bowling)

(defn- sum [rolls]
  (reduce + rolls))

(defn- spare? [rolls]
  (= 10 (sum (take 2 rolls))))

(defn- strike? [rolls]
  (= 10 (first rolls)))

(defn- more? [rolls]
  (seq rolls))

(defn- score-strike [rolls]
  (sum (take 3 rolls)))

(defn- score-spare [rolls]
  (sum (take 3 rolls)))

(defn- score-frame [rolls]
  (sum (take 2 rolls)))

(declare score)

(defn- score-after-strike [rolls]
  (score
    (if (= 3 (count rolls))
      (drop 3 rolls)
      (drop 1 rolls)
    )))

(defn- score-after-frame [rolls]
  (score (drop 2 rolls)))

(defn score
  "calculate the bowling score of a [rolls] sequence"
  [rolls]
  (condp apply [rolls]
    empty?   0
    strike?  (+ (score-strike rolls) (score-after-strike rolls))
    spare?   (+ (score-spare rolls)  (score-after-frame rolls))
    more?    (+ (score-frame rolls)  (score-after-frame rolls))
    )
  )