如何为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
相关产品推荐
相关产品推荐

