Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When Spock's @Shared annotation should be preferred over a static field?

There is not much to add, the whole question is in the title.

Consider these two instances of class Foo used in a Spock specification.

@Shared Foo foo1 = new Foo()

static Foo foo2 = new Foo()

Overall, I know the idea behind @Shared annotation but I guess it's better to use language features, which in this case would be static field.

Are there any specific cases in which one should preferred over the other or it's rather a matter of taste?

like image 206
topr Avatar asked Mar 10 '16 17:03

topr


4 Answers

Contrary to JUnit, where you have to declare field variable static and assign value to it in

@BeforeClass
public static void setupClass()

so it was initialized just once per test suite (not each method), in Spock you may use instance field variable and annotate it with @Shared.

Consider the following example:

class SharedTestSpec extends spock.lang.Specification {

    @Shared
    def shared = shared()

    def shared() {
        "I came from ${this.class.simpleName}"
    }

    def 'Test one'() {
        given:
            println("test one, shared: $shared")
        expect: true
    }

    def 'Test two'() {
        given:
            println("test two, shared: $shared")
        expect: true

    }
}

class SubclassSpec extends SharedTestSpec {

    @Override
    def shared() {
        println("They've got me!")
        "I came from ${this.class.simpleName}"
    }
}

Running SubclassSpec gives you the following output:

test one, shared: I came from SubclassSpec
test two, shared: I came from SubclassSpec
They've got me!

Can't explain the print order though, but that's due to AST.

like image 37
Valya Avatar answered Oct 02 '22 23:10

Valya


Spock is all about expressiveness and clarity.

Static is a Java keyword that only shows the internals of the class (that this field is the same for all instances)

@Shared is a Spock feature that says to the reader that this variable is the same for all feature methods. It is an instruction specifically for the unit test and makes the unit test more clear for the reader.

The same can be said for the main Spock blocks. If you think about it they do not really change anything on the code.

public void myScenario(){
  int a = 2 + 3;
  assertEquals(5,a);
}

public void "simple addition scenario"(){
  when: "I add two numbers"
    int a = 2 +3

  then: "I expect the correct result"
  a == 5
}

Both unit tests do exactly the same thing technically. The second however is showing more clearly the intention. The when: and then: labels do not really do anything with the code other than clarifying its intent.

So to sum up, @Shared is making the test more readable. (See also @Issue, @Title etc., they exist for the same purpose)

like image 165
kazanaki Avatar answered Oct 03 '22 00:10

kazanaki


As a more exhaustive approach, here is a sample test with outputs:

@Unroll
class BasicSpec extends Specification {
    
    int initializedVariable
    
    int globalVariable = 200
    
    static int STATIC_VARIABLE = 300
    
    @Shared
    int sharedVariable = 400
    
    void setup() {
        initializedVariable = 100
    }
    
    void 'no changes'() {
        expect:
            printVariables()
            /*
            initializedVariable: 100
            globalVariable: 200
            STATIC_VARIABLE: 300
            sharedVariable: 400
             */
    }
    
    void 'change values'() {
        setup:
            initializedVariable = 1100
            globalVariable = 1200
            STATIC_VARIABLE = 1300
            sharedVariable = 1400
        
        expect:
            printVariables()
            /*
            initializedVariable: 1100
            globalVariable: 1200
            STATIC_VARIABLE: 1300
            sharedVariable: 1400
             */
    }
    
    void 'print values again'() {
        expect:
            printVariables()
            /*
            initializedVariable: 100
            globalVariable: 200
            STATIC_VARIABLE: 1300
            sharedVariable: 1400
             */
    }
    
    private void printVariables() {
        println "initializedVariable: $initializedVariable"
        println "globalVariable: $globalVariable"
        println "STATIC_VARIABLE: $STATIC_VARIABLE"
        println "sharedVariable: $sharedVariable\n"
    }
}

The surprising thing to me is that both the variable in the class' setup() method AS WELL as the global, instanced variable get reset on each test (presumably because the class is re-instantiated for each test case). Meanwhile, the static and the @Shared variable work as expected. As a result, the latter two are also able to be accessed in where clauses, which are run before some of the other ones that are listed prior in each test case.

like image 3
Igor Avatar answered Oct 03 '22 01:10

Igor


Static fields should only be used for constants. Otherwise shared fields are preferable, because their semantics with respect to sharing are more well-defined.

like image 1
jun zhou Avatar answered Oct 02 '22 23:10

jun zhou