You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Spock Specification基于Spring application.properties属性条件执行?

如何让Spock Specification基于Spring配置属性条件执行?

背景

  • 项目基于Java 11与Spring Boot 2.6.6开发
  • 部分功能依赖application.properties中的特定属性启用,相关Spring组件通过@ConditionalOnProperty注解实现条件加载
  • 集成测试采用Groovy语言及Spock框架(版本2.1-groovy-3.0)编写

问题

能否让Spock Specification根据Spring的application.properties中的属性实现条件执行?

可行方案

Spock的@Requires注解支持条件测试,但默认无法直接访问Spring配置上下文。以下两种方案可以实现基于配置属性的条件判断:

方案一:直接读取配置文件(简单场景适用)

这种方式直接读取classpath下的配置文件,解析目标属性值,适合配置仅来自本地文件的场景:

import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Requires
import spock.lang.Specification

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles('integration')
@ContextConfiguration(classes = TestSpringBootApplication)
@Requires(reason = '仅当AAA功能启用时执行此测试', value = { isFeatureAAAEnabled() })
class ConditionalSpec extends Specification {

    // @Autowired
    // private AAAFeatureComponent aaaFeatureComponent

    def "one plus one should equal two"() {
        expect:
        1 + 1 == 2
    }

    private static boolean isFeatureAAAEnabled() {
        def properties = new Properties()
        // 加载默认配置
        loadProperties(properties, "application.properties")
        // 加载active profile对应的配置(覆盖默认值)
        loadProperties(properties, "application-integration.properties")

        // 读取目标属性,默认返回false
        return Boolean.parseBoolean(properties.getProperty("feature.aaa.enabled", "false"))
    }

    private static void loadProperties(Properties properties, String fileName) {
        def resource = ConditionalSpec.class.getClassLoader().getResource(fileName)
        resource?.withInputStream { properties.load(it) }
    }
}

方案二:利用Spring Environment(全配置来源支持)

如果配置来自环境变量、系统属性或远程配置中心,直接读取文件的方式会失效。此时可以自定义Spock扩展,结合Spring的Environment实现条件判断:

  1. 自定义扩展注解:
import org.spockframework.runtime.extension.ExtensionAnnotation
import java.lang.annotation.*

@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE, ElementType.METHOD])
@ExtensionAnnotation(RequiresPropertyExtension)
@interface RequiresProperty {
    String value() // 目标属性键名
    String havingValue() default "true" // 属性预期值
    String reason() default "属性不满足条件,跳过测试"
}
  1. 实现扩展逻辑:
import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.FeatureInfo
import org.spockframework.runtime.model.SpecInfo
import org.springframework.context.ApplicationContext
import org.springframework.test.context.TestContextManager

class RequiresPropertyExtension extends AbstractAnnotationDrivenExtension<RequiresProperty> {

    @Override
    void visitSpecAnnotation(RequiresProperty annotation, SpecInfo spec) {
        checkProperty(annotation, spec)
    }

    @Override
    void visitFeatureAnnotation(RequiresProperty annotation, FeatureInfo feature) {
        checkProperty(annotation, feature.parent)
    }

    private void checkProperty(RequiresProperty annotation, SpecInfo spec) {
        try {
            // 获取Spring应用上下文
            def testContextManager = new TestContextManager(spec.reflection)
            ApplicationContext context = testContextManager.testContext.applicationContext
            def propertyValue = context.environment.getProperty(annotation.value())

            // 判断属性值是否符合预期
            if (!annotation.havingValue().equals(propertyValue)) {
                spec.skipped = true
                spec.skipReason = annotation.reason()
            }
        } catch (Exception e) {
            // 上下文初始化失败时跳过测试
            spec.skipped = true
            spec.skipReason = "Spring上下文初始化失败:${e.message}"
        }
    }
}
  1. 在测试类中使用自定义注解:
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration
import spock.lang.Specification

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles('integration')
@ContextConfiguration(classes = TestSpringBootApplication)
@RequiresProperty(value = "feature.aaa.enabled", havingValue = "true", reason = "AAA功能未启用,跳过测试")
class ConditionalSpec extends Specification {

    def "one plus one should equal two"() {
        expect:
        1 + 1 == 2
    }
}

方案对比

  • 方案一实现简单,无需额外代码,但仅能读取本地配置文件,无法处理环境变量、系统属性等外部配置。
  • 方案二通过Spring Environment获取属性,支持所有Spring兼容的配置来源,灵活性更高,但需要自定义Spock扩展。

内容的提问来源于stack exchange,提问作者Alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 05:06:35