Thursday, November 19, 2009

Running easyb stories in a Gradle build

Recently, I've felt like I've been fighting Maven for all but the simplest builds, so I've been exploring alternative build systems, notably Gradle. One thing, too, that's become really important to my build processes lately is running easyb tests, so I wanted to figure out how to use easyb in my Gradle builds.

One of the nice things about Gradle is it makes good use of existing Ant tasks (why reinvent the wheel?), so integrating easyb is not difficult at all. The first step is adding the easyb artifact to the project dependencies. Unless your project is actually making use of easyb internally, you'll likely want to include it as a "testRuntime" dependency. The easyb artifact actually requires commons-cli, too, so you'll have to do one of two things:

  • If you have your Gradle build set up to resolve transitive dependences with a Maven pom or Ivy file, you can set up your dependency definition to resolve transitive dependencies.

  • If you're including the easyb jar directly via some other means, you can add a second "testRuntime" dependency for commons-cli.


The dependencies section might end up looking something like this:

// Access via Maven repo, 0.9.6 is currently the latest available
testRuntime("org.easyb:easyb:0.9.6@jar") { transitive = true }

// Access via local artifacts
testRuntime "org.easyb:easyb:0.9.7@jar"
testRuntime "commons-cli:commons-cli:1.2@jar"


With the dependencies in place, it's next a matter of configuring easyb to run in a Gradle task. Again, there are two options here:

  • You can set up a separate task to run your easyb stories if you want to keep them separate.

  • You can add the easyb run to an existing task like "check" if you know you always want to run your easyb stories whenever other tests are run.


In either case, you first need to add the easyb task via Ant's "taskdef":

ant.taskdef(name: "easyb", classname:"org.easyb.ant.BehaviorRunnerTask",
classpath: sourceSets.test.runtimeClasspath.asPath)


Next, you set up the easyb Ant task, telling it the classpath to use, where to put the reports and how to format them, along with where to find the behaviors you want to run:

ant.easyb( classpath: sourceSets.test.runtimeClasspath.asPath ) {
// assume testResultsDir has already been created
report( location:"${project.testResultsDir}/story.txt", format:"txtstory" )
behaviors( dir: "src/test/stories" ) {
include( name:"**/*.story" )
}
}


The "format" attribute can have a value of "html", "xml", "txtstory", or "txtspecification". For more information about those output formats, see the easyb documentation. Note, at this time, it doesn't appear possible to output multiple formats at once; if you require multiple formats, one workaround, depending on how extensive your tests are, might be to run easyb multiple times, once for each output format you want.

Finally, the easyb Ant task provides a "failureProperty" attribute naming a build property that will be set if any easyb story results in a failure. Since Gradle sees running easyb as a successful outcome and will happily keep chugging along as long as easyb ran without error, you can use the "failureProperty" in conjunction with Ant's "fail" task to fail the build if a story does not pass:

// easyb task configured with failureProperty
ant.easyb( classpath: sourceSets.test.runtimeClasspath.asPath, failureProperty:'easyb_failed' ) {
report( location:"${project.testResultsDir}/story.txt", format:"txtstory" )
behaviors( dir: "src/test/stories" ) {
include( name:"**/*.story" )
}
}

// checking the failureProperty
ant.fail( if:'easyb_failed', message: 'Failures in easyb stories' )


The final code in the build.gradle file, with dependencies defined and easyb added to Gradle's "check" task, might end up looking something like this:

dependencies {
testRuntime("org.easyb:easyb:0.9.5.2@jar") { transitive = true }
}

check << {

ant.taskdef(name: "easyb", classname:"org.easyb.ant.BehaviorRunnerTask", classpath: sourceSets.test.runtimeClasspath.asPath)

ant.easyb( classpath: sourceSets.test.runtimeClasspath.asPath, failureProperty:'easyb_failed' ) {
report( location:"${project.testResultsDir}/story.txt", format:"txtstory" )
behaviors( dir: "src/test/stories" ) {
include( name:"**/*.story" )
}
}

ant.fail( if:'easyb_failed', message: 'Failures in easyb stories' )
}


When the build is run (in this case, on a Groovy project), the output might look like this:

user$ gradle clean check
:clean
:compileJava
:compileGroovy
:processResources
:classes
:compileTestJava
:compileTestGroovy
:processTestResources
:testClasses
:test
:check

BUILD SUCCESSFUL

Total time: 7.267 secs



If there are problems, or if any of your stories fail and you're failing the build based on the "failureProperty", then you might see output like this:


user$ gradle clean check
:clean
:compileJava
:compileGroovy
:processResources
:classes
:compileTestJava
:compileTestGroovy
:processTestResources
:testClasses
:test
:check

FAILURE: Build failed with an exception.

* Where:
Build file '/path/to/project/build.gradle' line: 39

* What went wrong:
Execution failed for task ':check'.
Cause: Failures in easyb stories

* Try:
Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace.

BUILD FAILED

Total time: 7.443 secs


Note, if the build fails, and you haven't yet successfully run easyb in Gradle yet, you might not have it configured correctly. However, the "normal" output isn't particularly helpful in telling you what's wrong, since Gradle seems to supress most of the error output. One of the more helpful things at that point is to run your build using the "-d" option to get the full debugging output:

gradle -d clean check


With the extensive debugging output, you should be able to trace through the build and see what's going on; often, the problem is a "ClassDefNotFound" error as a result of an artifact missing from the dependencies or an error in parsing your easyb stories or something along those lines.

4 comments:

Tomek said...

Hi Jeffrey,

I would be grateful if you could post a short information on running easyb with Gradle to Gradle Cookbook.
I created an empty slot there:
http://docs.codehaus.org/display/GRADLE/Cookbook#Cookbook-easyb

--
Regards
Tomek Kaczanowski

Jeffrey Erikson said...

Done . . . let me know if you'd like to see more detail or make any changes.

Tomek said...

Thank you !

I would appreciate if you could update the Cookbook in case if next versions of Gradle will allow for easier integration with easyb.
As for now it is perfect.

--
Tomek

David Smart said...

Thanks for your post. Any idea how to pass a JVM system property to the :check task?

The syntax for adding system properties to the :test task doesn't work for :check.