Chariot Training Classes

Training Courses

I run Chariot's training and mentoring services. We provide training in AngularJS, HTML5, Spring, Hibernate, Maven, Scala, and more.

Chariot Education Services

Technology

Chariot Emerging Tech

Learn about upcoming technologies and trends from my colleagues at Chariot Solutions.

Resources

Chariot Conferences

Podcasts

Thursday
Oct042012

More Spock love - how I tested complex install scenarios

I'm in love. Officially. With Spock.

Ok, we've only been hanging out for a little bit, here and there. But one of the things I'm doing is prepping for an updated talk on Spring Roo add-ons at SpringOne/2GX. Rather than repeat the same things again, I wanted to show some practical help for people writing add-ons, things like how to write good tests.

Also, I wanted to point out a few things that need more work, bring up some issues for the JIRA queue, so that we can improve Roo further after SpringOne. We'll get to that later in this article.

Testing add-on availability

Testing whether an add-on setup command is available to expose to the Roo shell can be a bit tricky. For example, in the Spock Roo add-on, I expect that the user's shell is:

  • running in a project context
  • the project has to contain two maven dependencies for spock-core and spock-spring
  • the project has to contain one dependency for the gmaven-plugin to run Spock tests

So, there are several scenarios to test. I started down the road to testing them all individually. Bad developer. First off, there is a sort of truth table here:

installedDependenciesinstalledPluginsexpectedResult
nonenonetrue
depa, depbnonetrue
depaplugintrue
baddepnonetrue
depa, depbpluginfalse
depabadversion, depbplugintrue

Given:

  • depa and depb = the correct two Maven dependencies,
  • baddep is a Maven dependency completely unrelated
  • depabadversion is the same dependency but a different version
  • plug-in is the gmaven-plugin from codehaus
  • expectedResult is whether or not we can go ahead and install the add-on (i.e. replace what is there)

As I started the third method I saw so much duplication I started to back off. First I removed whatever extra calls I was making to various methods (such as projectOperations.getDependencies()) which made it easier to test (and debug). Then, I pulled out the data table syntax.

Here is what I ended up with. Totally boss, BTW:

package org.sillyweasel.roo.addons.spock
import org.springframework.roo.project.Dependency
import org.springframework.roo.project.Plugin
import org.springframework.roo.project.ProjectOperations
import org.springframework.roo.project.maven.Pom
import spock.lang.Unroll

class SpockOperationsImplTest extends spock.lang.Specification {

  static def depa = 
    new Dependency("org.springframework", "spock-core", "0.6-groovy-1.8")
  static def depb = 
    new Dependency ("org.springframework", "spock-spring", "0.6-groovy-1.8")
  static def depabadversion =
    new Dependency("org.springframework", "spock-core", "0.6-groovy-1.0")
  static def baddep = 
    new Dependency ("org.springframework", "sbad", "0.6-groovy-1.8")
  static def plugin = 
    new Plugin("org.codehaus.gmaven", "gmaven-plugin", "1.4")


  @Unroll
  def "install check - #situation"() {
    given:
      def pom = Mock(Pom.class)
      def operations = new SpockOperationsImpl()
      def projectOperations = Mock(ProjectOperations.class)
      operations.projectOperations = projectOperations

    when:
      def commandAvailable = operations.isSetupCommandAvailable()

    then:
      1 * projectOperations.isFocusedProjectAvailable() >> true
      1 * projectOperations.getFocusedModule() >> pom
      1 * pom.getDependencies() >> deps
      pom.getBuildPlugins() >> plugins
      assert commandAvailable == res

    where:
    deps          | plugins | res   | situation
    []            | []      | true  | "no installedDependencies or installedPlugin"
    [ depa, depb] | []      | true  | "all installedDependencies...
                                      but no installedPlugin"
    [ depa ]      | [plugin]| true  | "some installedDependencies...
                                       and installedPlugin"
    [ baddep ]    | []      | true  | "a different dependency and no plugins"
    [ depa, depb ]| [plugin]| false | "all installedDependencies and installedPlugin"
    [ depabadversion, depb ] 
                  | [plugin] | true | "bad version of a plugin"
  }
}

What's even better...

See the method name at the top? It has an embedded hash mark, and uses the `situation` field, plus the `@Unroll` annotation, to turn the method into five separate test methods on the fly at test time. Niiiice!

Roo's challenges - addon testing

Why am I doing all of my testing in Groovy when Roo is a Java-based framework? Three reasons:

  • I want to get rolling on add-ons,
  • I need to understand the weaknesses of the add-on API so I can try to help improve it,
  • I need to make sure I test the most code for the least effort!

To the middle point: Roo is based on Maven and Spring. It manages a Maven `pom.xml` file, as well as Spring, Java and other artifacts.

Because the Maven files are managed by some Java wrapper classes, written for the purpose of generating Maven POMs, they are currently good enough - you can fill them via an Xml DOM, for example, but they aren't inherently testable.

For example, the Pom class can't be mocked, so I can't inject a fake set of dependencies or plugins. That is, unless I install two Maven test dependencies, both of which Spock told me to use as a helpful hint! They are: `cglib-nodep` for mocking interface-less classes, and `objenesis` for creating instances of classes as Mocks without no-arg constructors. To wit:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib-nodep</artifactId>
  <version>2.2.2</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>org.objenesis</groupId>
  <artifactId>objenesis</artifactId>
  <version>1.2</version>
  <scope>test</scope>
</dependency>

With that, I can use:

def pom = Mock(Pom.class)

See you at SpringOne/2GX

I'll be talking about this and other techniques at SpringOne/2GX in two weeks, and I'll post my presentation later as well. I hope to see some of you there.

Tuesday
Sep112012

My Roo add-ons talk at SpringOne/2GX is approved

Hello there. I'll be speaking about Spring Roo add-ons again at SpringOne/2GX this year. If you're heading to the show and want to talk shop on Roo and other things, seek me out. I'll be happy to chat.

Here is a link to the talk.

Thursday
Aug232012

Speaking at SpringOne/2GX - topic - Roo add-ons

Hello, viewers. I've just received word that I'll be speaking at SpringOne/2GX this year. No link yet but I'm working on that. I'll be down there all week, so I hope to see you there.

Sunday
Aug192012

Watch my SpringSource Roo add-ons video w/Srini Penchikala and SpringSource's Josh Long

Sunday
Jul082012

Spock's data tables are sweet!

I'm working with Spock again for updating my CoffeeScript plugin for Roo 1.2.3. There have been a few helpful additions to the add-on APIs (unless I missed them in 1.2.2...) and so my code is getting a tad simpler.

The coolest thing is that converting my tests to Spock, I had an easy way to do a truth table testing condition when I wanted to try out a number of scenarios. Check out this snippet, specifically the "where:" block and the values from the where block variables within the test conditions.

I've switched this test to the more formal given: when: then: syntax, and added the where: section for the data tables settings. I think I was using setup: instead of given: and I also didn't put my expectations in the then: section, which they evaluate in regardless. It's more readable this way.

Note, the version of the coffeescript add-on listed in the test is irrelevant, as the method called to fetch the plugin ignores it but the Plugin constructor requires a version.

@Unroll("evalto #evalto for project available #available, 
         packaging #packaging and pluginList #pluginList")
def "test isPluginInstalled with scenarios"() {
    given:
    def projectOperations = Mock(ProjectOperations.class)
    coffeescriptOperations.projectOperations = projectOperations
    def pom = Mock(Pom.class)

    when:
    def result = coffeescriptOperations.isPluginInstalled()

    then:
    coffeescriptOperations.projectOperations
            .isFocusedProjectAvailable() >> available
    coffeescriptOperations.projectOperations.getFocusedModule() >> pom
    pom.getPackaging() >> packaging
    pom.getBuildPluginsExcludingVersion(_) >> pluginList

    result == evalto

    where:
    pluginList | available | packaging  | evalto
    [] | false | "foo" | false
    [] | true  | "war" | false
    [] | true  | "pom" | false
    [new Plugin("com.theoryinpractise",
            "coffee-maven-plugin", "1.2.0") ] | true | "war" | true
    [new Plugin("com.theoryinpractise",
            "coffee-maven-plugin", "1.2.0")] | true | "pom" | false
}

I was able to fold five specific test cases checking whether the project contains the Coffeescript plugin into one test with five data settings (all false except one):

  • The focused project is not available
  • The packaging is a war, the focused project is available, the add-on is not installed
  • The packaging is a "pom" and the project is available
  • The plugin exists, the type is a war, the focused module is available (happy!)
  • The plugin exists (mounted by hand perhaps) on a pom project which is the current module

I used @Unroll to turn the data table results into five separate tests, each of which listed separately in the test results. Note the method name of the test is comprised of literal strings with data from the datatable columns starting with '#'. Nice, eh?

So I was able to collapse five test scenarios into one with the data tables. Nice.

You can pull the git repository for this add-on by using:

git clone git://git.cloudbees.com/sillyweasel/coffeescript-roo-addon.git