当应该优先考虑使用Spock的@Shared注解而不是静态字段?

33

没有太多可补充的,整个问题都在标题中。

考虑一个Spock规范中使用的Foo类的这两个实例。

@Shared Foo foo1 = new Foo()

static Foo foo2 = new Foo()

总的来说,我知道@Shared注解背后的想法,但我认为更好的做法是使用语言特性,比如这种情况下应该使用static字段。

是否有特定情况应该优先考虑其中之一,还是只是口味问题?


1
据我所知,从功能上来说,它们实际上是相同的,但是@Shared更能表达你的意图。 - tim_yates
4个回答

29

Spock强调表达性和清晰度。

Static是Java中的一个关键字,仅显示类的内部(即该字段对所有实例都相同)。

@Shared是Spock的一个特性,它告诉读者这个变量对于所有特性方法都是相同的。它是针对单元测试的指令,使单元测试对读者更加清晰。

对于Spock的主要块也可以这样说。如果你仔细想想,它们在代码上并没有真正改变任何东西。

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
}

两个单元测试在技术上执行完全相同的操作。但第二个清楚地显示了意图。 when:then:标签实际上并不对代码做任何事情,只是澄清其意图。

因此,总结一下,@Shared 使测试更易读。(另请参见@Issue@Title等,它们存在的目的相同)


嗯,在有和没有'then'块之间存在轻微差异。在这样的块中,断言对于任何非void行都是隐式的。但是同意,最大的价值在于可读性。感谢您的答案,我会点赞它,但还要等一段时间看看是否会出现其他答案。我很想知道是否有其他不需要'syntactic sugar'的理由来优先选择@Shared而不是静态(或相反)。 - topr

11

与JUnit相反,您需要声明字段变量静态并在其中分配值

@BeforeClass
public static void setupClass()

因此,它仅针对测试套件进行了一次初始化(而不是每个方法),在Spock中,您可以使用实例字段变量并用@Shared进行注释。

考虑以下示例:

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}"
    }
}

运行SubclassSpec会给您以下输出:

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

虽然无法解释打印顺序,但这是由于AST导致的。


1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - topr

3

作为一种更详尽的方法,这里提供了一个带有输出结果的样例测试:

@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"
    }
}

我感到惊讶的是,无论是在类的setup()方法中的变量,还是全局实例化变量,在每次测试中都会被重置(可能是因为每个测试用例都重新实例化了该类)。与此同时,static@Shared变量的工作方式符合预期。因此,后两者也能够在where子句中进行访问,在每个测试用例中列出的一些其他子句之前运行。

1
实例变量为每个@Test用例实例化,这对于JUnit来说也是一样的。您能详细解释一下最后一句话吗?我不太明白。 - Valya

1

静态字段只应用于常量。否则,共享字段更可取,因为它们在共享方面的语义更加明确定义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接