当前位置: 首页 > 知识库问答 >
问题:

Spock-Unit test:如何为@around注释编写Spock unit test

易祯
2023-03-14

嗨,我正在使用下面的代码在我的webflux应用程序中使用aop打印日志,我在编写单元/集成测试时遇到了麻烦?我们可以在这里验证日志交互吗?任何帮助都将不胜感激

@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.METHOD) 
public @interface Loggable {} 
@Aspect
@Slf4j
public class LoggerAspect {

  @Around("@annotation(Loggable)")
  public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {

    long start = System.currentTimeMillis();
    var result = joinPoint.proceed();
    if (result instanceof Mono) {
      var monoResult = (Mono) result;
      AtomicReference<String> traceId = new AtomicReference<>("");

      return monoResult
        .doOnSuccess(o -> {
          var response = "";
          if (Objects.nonNull(o)) {
            response = o.toString();
          }
          log.info("Enter: {}.{}() with argument[s] = {}",
            joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
            joinPoint.getArgs());
          log.info("Exit: {}.{}() had arguments = {}, with result = {}, Execution time = {} ms",
            joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
            joinPoint.getArgs()[0],
            response, (System.currentTimeMillis() - start));
        });
    }
  }
}

测试失败。不知为什么,当我调试指针不在doOnNext方法中时,我不确定如何在上面的日志记录方面断言日志交互。在Junit5中,我知道我可以为每个方法使用mockito并返回一些东西,但在Spock中我如何重新调用

class LogAspectTest extends Specification {
  private static final String MOCK_METHOD_LOG_VALUE = "mockMethodLogValue"
  private Logger log = Mock()
  private ProceedingJoinPoint mockJoinPoint = Mock()
  private static Mono<String> methodReturn = Mono.just(["Data", "Data"])
  private LogAspect logAspect = new LogAspect(log)

  @Unroll
  def 'logAround verify log interaction'() {
    given:
    mockJoinPoint.proceed() == Mono.just("Hello")
    final Method method = TestClass.class.getMethod("mockMethod")

    when:
    logAspect.logAround(mockJoinPoint)

    then:
    interaction { mockJoinPointAndMethodSignatureInteractions(method, methodReturnToUse) }

    where:
    resultType | methodReturnToUse
    'Mono'     | methodReturn
  }

  private void mockJoinPointAndMethodSignatureInteractions(Method method, Publisher result) {
    1 * mockJoinPoint.proceed() >> result
    1 * log.info() >> ""

  }

  private static class TestClass {
    @Loggable
    Mono<String> mockMethod() { return Mono.just("data") }

  }
}

是否建议为@loggable注释编写集成测试,因为它只是日志记录,不确定如何编写断言日志语句的集成测试

共有1个答案

虞俊美
2023-03-14

正如我在评论中所说的,如果不使用PowerMock或类似的附加工具,就不能轻松地模拟私有静态final字段。我认为每当您需要类似的东西时,您应该重构代码以获得更好的可测试性。这里有一个远非完美的想法,但我想给你一个关于如何单元测试方面的想法。至于集成测试,您也可以这样做,但是问问自己您想要测试什么:方面或Spring AOP切入点匹配是否正确?

无论如何,让我们假设测试中的类是:

package de.scrum_master.stackoverflow.q64164101;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Loggable {}
package de.scrum_master.stackoverflow.q64164101;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import java.util.Objects;
import java.util.function.Consumer;

@Aspect
public class LogAspect {
  private static final Logger log = LoggerFactory.getLogger(LogAspect.class.getName());

  @Around("@annotation(Loggable)")
  public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = joinPoint.proceed();
    if (result instanceof Mono)
      return ((Mono) result).doOnSuccess(getConsumer(joinPoint, start));
    return result;
  }

  public Consumer getConsumer(ProceedingJoinPoint joinPoint, long start) {
    return o -> {
      String response = "";
      if (Objects.nonNull(o))
        response = o.toString();
      log.info("Enter: {}.{}() with argument[s] = {}",
        joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
        joinPoint.getArgs());
      log.info("Exit: {}.{}() had arguments = {}, with result = {}, Execution time = {} ms",
        joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(),
        joinPoint.getArgs()[0],
        response, (System.currentTimeMillis() - start));
    };
  }
}

看到我如何将lambda分解成一个帮助器方法了吗?它有两个效果:

    null
package de.scrum_master.stackoverflow.q64164101

import org.aspectj.lang.ProceedingJoinPoint
import reactor.core.publisher.Mono
import spock.lang.Specification

class LogAspectTest extends Specification {
  LogAspect logAspect = Spy()
  ProceedingJoinPoint joinPoint = Mock()

  def "aspect target method returns a Mono"() {
    given:
    joinPoint.proceed() >> Mono.just("Hello")

    when:
    logAspect.logAround(joinPoint)

    then:
    1 * logAspect.getConsumer(joinPoint, _)
  }

  def "aspect target method does not return a Mono"() {
    given:
    joinPoint.proceed() >> "dummy"

    when:
    logAspect.logAround(joinPoint)

    then:
    0 * logAspect.getConsumer(joinPoint, _)
  }
}

请注意我如何使用spy(即基于原始对象的部分mock)来选择性地存根helper方法。

更新:对于更集成的测试,另一种选择是配置您的日志框架以登录到您可以控制和验证的目标中,例如,登录到内存中的数据库或您可以访问的缓冲区中。

 类似资料:
  • 问题内容: 我可能只是看错了方向,但我发现有关批注处理的JSE文档非常稀疏。我想编写一个注释处理器,该处理器处理带注释的String字段和局部变量,以计算的String表达式替换它们。这应该不会太复杂,但是我对于Javax.annotation.processing的Javadoc却迷失了。 编辑:我需要在编译时处理批注,因为我想修改生成的代码。它应将带注释的常量String表达式替换为计算的St

  • 本文向大家介绍Python 中如何写注释,包括了Python 中如何写注释的使用技巧和注意事项,需要的朋友参考一下 在写 Python 代码的时候,一个很好的编码实践就是使得你的代码简洁,易懂。组织代码,设置变量,以及给函数有意义的名字,都是几个不错的方法。 另外一个提高代码可读性的方式就是使用注释。一个注释就是可以用来解释代码的一段人类可读的解释或者一个注解。例如,如果你写了一个复杂的正则表达式

  • 我想为上面的内容编写单元测试,以测试我正在使用的注释的sampleURL,比如如果我给出任何应该与regex模式匹配的URL。我浏览了以下链接:如何在spring中进行单元测试验证注释,如何使用JUnit测试类的验证注释?但它们没有多大帮助,我也有setSampleURL函数。那么,如何为sampleURL变量编写测试呢。基本上,我想为regex模式编写测试,即我给sampleURL的值是否与re

  • 本文向大家介绍在JSX中如何写注释?相关面试题,主要包含被问及在JSX中如何写注释?时的应答技巧和注意事项,需要的朋友参考一下

  • 本文向大家介绍如何在JSP页面中写注释?,包括了如何在JSP页面中写注释?的使用技巧和注意事项,需要的朋友参考一下 JSP注释标记到JSP容器应忽略的文本或语句。当您想隐藏或“注释掉” JSP页面的一部分时,JSP注释非常有用。 以下是JSP注释的语法- 以下示例显示了JSP注释- 上面的代码将产生以下结果- 您可以在各种情况下使用少量特殊结构来插入注释或字符,否则将对其进行特殊处理。这是一个摘要