Question

Suppose we have a Spock specification class like below.

class SomeFeature extends Specification {
    def "some scenario"() {
        given: "some resource"
        def resource = someResource()

        when: "some action is taken"
        someAction()

        then: "some condition must be met"
        true == someCondition()
    }
}

How can I extract the BDD meta-statements like some scenario, given, when, then? It is straightforward to process the source file, but I am wondering if it is possible to use reflection to achieve that.

BTW, the motivation to get that information is to facilitate the communication between product owner and developer, such that the product owner can know what behaviors have been implemented and verified without looking at the source code.

Thank you very much.

No correct solution

OTHER TIPS

In the cold hard light of day, there are some issues with this.

  1. It's probably quite brittle and would need sorting out to handle different cases and formats (unrolled descriptions? helper methods? parameterised descriptions?)
  2. It will just blindly dump out everything even if the test is never executed.

I think a much better and more stable solution would be the one in @PeterNiederwieser's comment above.

I'll leave this here for prosperity though, as it's quite a good example of how to generate an AST from some Groovy code as a String...


I don't think reflection will help, as it won't get you the contents of the methods.

You can do it by generating an AST from the source code, and then walking it looking for the nodes of interest.

So given the code in a String like so:

def code = '''import spock.*

class SomeFeature extends Specification {
    def "some scenario"() {
        given: "some resource"
        def resource = someResource()

        when: "some action is taken"
        someAction()

        then: "some condition must be met"
        true == someCondition()
    }
    def "another"() {
       given: 'a value 1'
          def value = 1
       then: '1 == 1'
          value == 1
    }
}'''

You can generate an AST:

import org.codehaus.groovy.antlr.*
import org.codehaus.groovy.antlr.parser.*

def ast = new GroovyRecognizer(
              new GroovyLexer(
                  new StringReader( code ) ).plumb() ).with { p ->
  p.compilationUnit()
  p.AST
}

And then you can do something like this (this is probably not the cleanest way of doing it, I was under time constraints) ;-)

while( ast ) {
    if( ast.type == GroovyTokenTypes.CLASS_DEF ) {
        def child = ast.firstChild.nextSibling
        println "Specification '${child.text}'"
        while( child && child.type != GroovyTokenTypes.OBJBLOCK ) {
            child = child.nextSibling
        }
        if( child ) {
            child = child.firstChild
            while( child ) {
                if( child.type == GroovyTokenTypes.METHOD_DEF ) {
                    def method = child.firstChild
                    println "    Scenario '${method.nextSibling?.nextSibling?.text}'"
                    while( method ) {
                        if( method.type == GroovyTokenTypes.SLIST ) {
                            def statements = method.firstChild
                            while( statements ) {
                                if( statements.type == GroovyTokenTypes.LABELED_STAT ) {
                                    def label = statements.firstChild
                                    println "        ${label.text.toUpperCase()} '${label.nextSibling?.firstChild?.text}'"
                                }
                                statements = statements.nextSibling
                            }
                        }
                        method = method.nextSibling
                    }
                }
                child = child.nextSibling
            }
        }
    }
    ast = ast.nextSibling
}

That gives me the output:

Specification 'SomeFeature'
    Scenario 'some scenario'
        GIVEN 'some resource'
        WHEN 'some action is taken'
        THEN 'some condition must be met'
    Scenario 'another'
        GIVEN 'a value 1'
        THEN '1 == 1'

Hope it helps...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top