【问题标题】:How to execute setup method/block only once per feature method?如何每个功能方法只执行一次设置方法/块?
【发布时间】:2020-09-16 18:11:17
【问题描述】:

我在 Spring Boot 中编写了简单的 groovy & spock 测试类,带有 setupexpectcleanupwhere 块,但这里的问题是每次迭代都会执行 setup 块在 where 块中,这不是我需要的方式。所以我的问题是有没有办法让setup() 方法在每个方法之前只执行一次

import spock.lang.Specification
class SpockSpec extends Specification {

  def setupSpec() { println 'setupSpec()' }

  def setup() { println 'setup()' }

  def cleanup() { println 'cleanup()' }

  def cleanupSpec() { println 'cleanupSpec()' }

  def 'test blocks'() {

     setup:
     println 'setup:'

     expect:
     println "expect: $data"

     cleanup:
     println 'cleanup:'

     where:
     data << [1, 2]
  }
}

输出:

setupSpec()
setup()
setup:
expect: 1
cleanup:
cleanup()
setup()
setup:
expect: 2
cleanup:
cleanup()
cleanupSpec()

预期输出:。 //setup() 方法只在方法之前执行一次,而不是在 where 的每次迭代中执行一次

setupSpec()
setup()
setup:
expect: 1
cleanup:
cleanup()
setup:
expect: 2
cleanup:
cleanup()
cleanupSpec()

【问题讨论】:

  • 你为什么不用setupSpec()
  • 我认为您的主题“如何在 groovy spock 测试类中 where 块的所有迭代后执行设置块”并不能反映您真正想要做什么。我将根据您在问题中描述的内容将其更改为我认为正确的内容。请仔细检查。谢谢。
  • 友情提醒:请向试图帮助您的人提供反馈。在您这样做之后,我将删除此评论。 ??????
  • 嗨@kriegaex 我完全忘记了这一点,但根据你的回答我可以使用setupSpec,因为我希望在每个测试方法之后需要执行一些数据库清理代码
  • setupSpec() 运行规范的第一个特性之前,而不是之后。如果您希望代码执行“在每个测试方法之后” 如您所说,您需要使用cleanup() 方法。但这与您在问题中的陈述相矛盾,这很令人困惑。那么现在应该是什么?请下定决心并以一致的方式进行沟通。

标签: java groovy spock


【解决方案1】:

简短的回答是:这不是 Spock 的工作方式。 setup()setup: 都旨在为参数化测试的每次迭代执行,因为从逻辑上讲,每次迭代都是一个独立的特征方法。我觉得这样挺好的。

伦纳德建议:

你为什么不用setupSpec()

setupSpec()如果有多个特征方法,这里就不剪了。

您可以编写一个全局或注释驱动的 Spock 扩展,这可以帮助您在特定情况下跳过 setup() 执行。我已经在 this thread 的 Geb 邮件列表中与 Geb 的维护者 Marcin Erdmann 讨论了这两种变体。您可以复制您喜欢的变体的代码并根据您的需要对其进行调整,即,当一个特性方法被自定义 @SkipSetup 注释注释时,您可以扩展注释以将闭包变为评估为条件或只是硬编码跳过所有迭代,但第一个迭代用于假设的@SetupOnce 注释。

或者您可以重构您的测试以使用规范的 Spock,例如仅当 @Shared 变量具有特定值时,才使您的设置代码执行某些操作。我没有看过你的代码,但我的感觉是,如果你认为你需要这个功能,那么你的测试设计就有问题。


更新:我在想这样的事情:

注解驱动的 Spock 扩展:

package de.scrum_master.testing.extension

import org.spockframework.runtime.extension.ExtensionAnnotation

import java.lang.annotation.Retention
import java.lang.annotation.Target

import static java.lang.annotation.ElementType.METHOD
import static java.lang.annotation.RetentionPolicy.RUNTIME

@Retention(RUNTIME)
@Target(METHOD)
@ExtensionAnnotation(SetupOnceExtension)
@interface SetupOnce {}
package de.scrum_master.testing.extension

import org.spockframework.runtime.extension.AbstractMethodInterceptor
import org.spockframework.runtime.extension.IMethodInvocation

class SetupOnceMethodInterceptor extends AbstractMethodInterceptor {
  Map<String, Boolean> annotatedFeatures = new HashMap<>()

  @Override
  void interceptSetupMethod(IMethodInvocation invocation) throws Throwable {
    if (annotatedFeatures.containsKey(invocation.feature.name)) {
      if (!annotatedFeatures[invocation.feature.name]) {
        invocation.proceed()
        annotatedFeatures[invocation.feature.name] = true
      }
    }
    else
      invocation.proceed()
  }
}
package de.scrum_master.testing.extension

import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.FeatureInfo

class SetupOnceExtension extends AbstractAnnotationDrivenExtension<SetupOnce> {
  SetupOnceMethodInterceptor interceptor

  @Override
  void visitFeatureAnnotation(SetupOnce annotation, FeatureInfo feature) {
    if (!interceptor) {
      interceptor = new SetupOnceMethodInterceptor()
      feature.spec.addSetupInterceptor interceptor
    }
    interceptor.annotatedFeatures[feature.name] = false
  }
}

Spock 规范示例:

package de.scrum_master.testing.extension

import spock.lang.Specification

class SetupOnceTest extends Specification {
  def setup() {
    println "SetupOnceTest -> setup"
  }

  def feature1() {
    setup:
    println "SetupOnceTest -> feature1"
    expect:
    true
  }

  @SetupOnce
  def feature2() {
    setup:
    println "SetupOnceTest -> feature2"
    expect:
    true
  }

  def feature3() {
    setup:
    println "SetupOnceTest -> feature3, iteration $count"
    expect:
    true
    where:
    count << [1, 2, 3]
  }

  @SetupOnce
  def feature4() {
    setup:
    println "SetupOnceTest -> feature4, iteration $count"
    expect:
    true
    where:
    count << [1, 2, 3]
  }
}

控制台日志:

如您所见,对于feature4()setup() 方法只执行一次。不过,扩展不会影响setup: 块,它们将始终被执行。

SetupOnceTest -> setup
SetupOnceTest -> feature1
SetupOnceTest -> setup
SetupOnceTest -> feature2
SetupOnceTest -> setup
SetupOnceTest -> feature3, iteration 1
SetupOnceTest -> setup
SetupOnceTest -> feature3, iteration 2
SetupOnceTest -> setup
SetupOnceTest -> feature3, iteration 3
SetupOnceTest -> setup
SetupOnceTest -> feature4, iteration 1
SetupOnceTest -> feature4, iteration 2
SetupOnceTest -> feature4, iteration 3

【讨论】:

  • 更新:我添加了一个示例扩展、示例测试和控制台日志。
  • setupSpec() won't cut it here if there is more than one feature method. 虽然是这样,但您可以轻松地拥有一个具有一种数据驱动方法的规范。这是对所述问题的最简单解决方案。当然你的扩展更强大,是否有必要取决于其余的代码,这就是我要求OP澄清的原因。顺便说一句,必须使用@Shared,否则setup 初始化的字段在第一次迭代后为空。
猜你喜欢
  • 1970-01-01
  • 2011-02-13
  • 1970-01-01
  • 1970-01-01
  • 2012-03-28
  • 1970-01-01
  • 2020-05-19
  • 1970-01-01
  • 2020-02-04
相关资源
最近更新 更多