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

如何为Spock测试的given/when/then等步骤添加自动日志功能?

Spock测试子步骤自动日志与耗时统计实现方案

核心思路:利用Spock扩展机制拦截块执行

Spock提供了原生的扩展API,可监听测试生命周期的各个阶段,包括given/when/then/and等块的执行前后事件,通过自定义扩展实现自动日志和耗时统计,无需手动修改每个测试用例。


方案一:自定义Spock扩展(推荐)

这是最贴合Spock设计理念的方案,通过实现块拦截器,精准捕获每个测试块的执行时机并记录日志。

步骤1:实现块拦截器

创建拦截器类,分别在块执行前后记录开始时间、结束时间并计算耗时:

import org.spockframework.runtime.extension.AbstractMethodInterceptor
import org.spockframework.runtime.extension.IMethodInvocation
import org.spockframework.runtime.model.BlockKind
import org.slf4j.Logger
import org.slf4j.LoggerFactory

class BlockLoggingInterceptor extends AbstractMethodInterceptor {
    private static final Logger log = LoggerFactory.getLogger(BlockLoggingInterceptor)

    @Override
    void interceptBeforeBlockExecution(IMethodInvocation invocation) throws Throwable {
        def block = invocation.method.block
        // 记录块开始执行日志
        log.info("Start block: *{}* | Spec: *{}* | Test method: *{}*",
            block.kind.name(),
            invocation.specification.name,
            invocation.method.name)
        // 存储开始时间到Invocation上下文
        invocation.metaClass.startTime = System.currentTimeMillis()
        // 继续执行原块逻辑
        invocation.proceed()
    }

    @Override
    void interceptAfterBlockExecution(IMethodInvocation invocation) throws Throwable {
        def block = invocation.method.block
        // 计算耗时
        long elapsedMs = System.currentTimeMillis() - invocation.startTime
        // 记录块完成日志及耗时
        log.info("Finish block: *{}* | Spec: *{}* | Test method: *{}* | Elapsed: {}ms",
            block.kind.name(),
            invocation.specification.name,
            invocation.method.name,
            elapsedMs)
        // 继续执行后续逻辑
        invocation.proceed()
    }
}

步骤2:创建扩展注册类

编写扩展类,将拦截器绑定到测试规范的所有块上:

import org.spockframework.runtime.extension.ExtensionAnnotation
import org.spockframework.runtime.model.SpecInfo
import java.lang.annotation.*

// 自定义注解,用于启用块日志
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtensionAnnotation(BlockLoggingExtension)
@interface EnableBlockLogging {}

// 扩展实现类
class BlockLoggingExtension implements org.spockframework.runtime.extension.ISpecificationExtension {
    @Override
    void visitSpecSpecification(SpecInfo spec) {
        // 为当前规范下的所有测试块添加拦截器
        spec.allBlocks.each { block ->
            block.addInterceptor(new BlockLoggingInterceptor())
        }
    }
}

步骤3:绑定到自定义Specification基类

在你已有的自定义基类上添加@EnableBlockLogging注解,所有继承该基类的测试用例都会自动启用块日志:

@EnableBlockLogging
abstract class BaseSpecification extends Specification {
    // 你的基类原有逻辑(如全局setup、通用工具方法等)
}

方案二:元编程快速实现(适合简单场景)

如果不想写完整扩展,也可以在自定义基类中通过Groovy元编程重写块的执行逻辑,但灵活性不如扩展方案:

abstract class BaseSpecification extends Specification {
    private static final Logger log = LoggerFactory.getLogger(BaseSpecification)

    def setup() {
        // 重写块的执行逻辑,添加日志和计时
        this.metaClass.invokeMethod = { String name, args ->
            if (['given', 'when', 'then', 'and', 'where'].contains(name)) {
                long startTime = System.currentTimeMillis()
                log.info("Start block: *{}* | Test method: *{}*", name.toUpperCase(), specificationContext.currentFeature.name)
                def result = delegate.metaClass.getMetaMethod(name, args).invoke(delegate, args)
                long elapsedMs = System.currentTimeMillis() - startTime
                log.info("Finish block: *{}* | Test method: *{}* | Elapsed: {}ms", name.toUpperCase(), specificationContext.currentFeature.name, elapsedMs)
                return result
            }
            delegate.metaClass.getMetaMethod(name, args).invoke(delegate, args)
        }
    }
}

注意事项

  • 日志依赖:确保项目中引入了SLF4J及对应的实现(如Logback、Log4j2)
  • 块过滤:如果不需要记录WHERE块的日志,可以在拦截器中通过block.kind != BlockKind.WHERE进行过滤
  • 并行测试:Spock的IMethodInvocation是线程隔离的,无需担心多线程下的计时混乱

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:18:27