【问题标题】:How to write the Junit Mockito code for this Aspect class for maximum code coverage如何为这个 Aspect 类编写 Junit Mockito 代码以获得最大的代码覆盖率
【发布时间】:2021-07-01 05:35:55
【问题描述】:

有人可以帮助我为这段代码编写 Junit 并提供资源来学习同样的代码。我一直试图从多种资源中找出答案,但找不到任何东西。我需要模拟切入点和在切入点中调用的方法。是否可以使用 Mockito 进行单元测试

import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.sample.api.rest.account.AccountResource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.CustomLog;
import lombok.extern.slf4j.slf4j;

@Aspect
@CustomLog
public class sample {
    ObjectMapper mapper = new ObjectMapper();
    long startTimeController = 0L;
    long endTimeController = 0L;

    @Pointcut("within(com.sample.api.rest.account. .) || "
    + "within(com.sample.api.rest.metadata..') ")
    public void entryController() {}

    @Pointcut("within(com. sample.api.rest.user..*)")
    public void entryControllerUser() {}

    @Pointcut("within(com.sample.api.service. .*)")
    public void entryService() {}

    @Pointcut("within(com. sample.cmo.repositories..*)")
    public void entryDAO() {}

    @Before("entryController()")
    public void beforeOtherControllerCall(JoinPoint jp) throws JsonProcessingException {
        String methodName = jp.getSignature().getName();
        String className = jp.getTarget().getClass().toString();
        Object[] arguments = jp.getArgs();
        log.info(className + " Method : " + methodName + " Arguments passed : " + 
        mapper.writeValueAsString(arguments));
        startTimeController = System.currentTimeMillis();
    }

    @Before("entryControllerUser()")
    public void beforeUserControllerCall(JoinPoint jp) throws JsonProcessingException {
        String methodName = jp.getSignature().getName();
        String className = jp.getTarget().getClass().toString();
        log.info(className + " Method : " + methodName);
        startTimeController = System.currentTimeMillis();
    }

    @After("entryController() || entryControlleruser()")
    public void afterControllerCall(JoinPoint jp) throws JsonProcessingException {
        endTimeController = System.currentTimeMillis();
        String methodName = jp.getSignature().getName();
        String className = jp.getTarget().getClass().toString();
        log.info(className + " Method : " + methodName + " Values returned :");
        if (endTimeController != 0) {
            log.info("Time consumed in " + className + " " + methodName + " call is "
                    + (endTimeController - startTimeController) + "ms");
        }
    }

    @Around("entryService()")
    public Object executionTimeService(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().getName();
        String className = pjp.getTarget().getClass().toString();
        Object[] arguments = pjp.getArgs();
        log.info(className + " Method: " + methodName + " Arguments passed :" + 
          mapper.writeValueAsString(arguments));
        long startTime = System.currentTimeMillis();
        Object obj = pip.proceed();
        long endTime = System.currentTimeMillis();
        log.info(className + " Method : " + methodName + " Execution time: " + (endTime - 
          startTime) + "ms");
        log.info(className + " Method : " + methodName + " Response received : " + 
         mapper.writeValueAsString(obj));
        return obj;
    }

    @Around("entryDAO()") 
    public Object executionTimeDAO(ProceedingJoinPoint pjp ) throws Throwable {
    String methodName pjp.getSignature().getName();
    String className pjp.getTarget().getClass().toString();
    Object[] arguments = pjp.getArgs();
    log.info(className+" Method : "+methodName+" Arguments passed :" 
      +mapper.writeValueAsString(arguments) );
    long startTime = System.currentTimeMillis();
    Object obj = pip.proceed();
    long endTime = System.currentTimeMillis();
    log.info(className+" method : " + methodName+" Execution time: "
           +(endTime-start Time)+"ms" );
    log.info(className+" Method: "+methodName+" Response received : "+ 
     mapper.writeValueAsString(obj));
    return obj;
    }
}

这是我尝试过的示例

@Test 
public void testBeforeOtherControllerCall() throws Throwable{
    JoinPoint joinPoint = mock(JoinPoint.class);
    AspectLogging logging = mock(AspectLogging.class);
    String[] args = {"arg1", "arg2"}; 
    Object[] obj args)
    Signature signature = mock (Signature.class);
    when(joinPoint.getSignature().thenReturn(signature);
    when(signature.getName().thenReturn("MethodName");
    Object object = mock(Object.class);
    when(joinPoint.getTarget().thenReturn(object);
    when(object.getClass().thenReturn(objectClass);
    when(joinPoint.getArgs().thenReturn(obj); 
    logging.beforeOtherControllerCali(joinPoint); 
    verify(joinPoint, times (1)).getSignature().getName().equals("MethodName");
}

【问题讨论】:

  • 欢迎来到 SO。请首先学习如何使用MCVE 提问。非常感谢。 SO 的工作方式是:你提出你的问题和你尝试的解决方案,描述你期望的结果和实际结果是什么。然后有人帮助你修复你的代码。所以不是:“请为我做我的工作,代表我解决我的问题。”您不能指望任何人为您编写整个单元测试。所以请改进这个问题,那么我相信有人会帮助你。 ????
  • 同时,我对AspectJ unit testsAspectJ integration tests 的回答应该可以帮助您入门。两者都涉及使用 Mockito。
  • @kriegaex 非常感谢您让我知道。我还会发布我尝试过的代码,然后相应地修改问题。
  • @kriegaex 我已经根据您给我的资源进行了尝试和发布。你能进一步指导我吗?
  • 我明天可以看看。您能否同时添加对当前状态的描述?测试是否成功运行?有没有错误?堆栈痕迹?或者只是没有足够的覆盖范围?您忘记提及哪里出了问题以及您需要指导的地方。

标签: spring-boot junit mockito spring-aop


【解决方案1】:

前言

当我试图重现你的情况时,我不得不

  • 猜猜你使用了哪些库和版本,

  • 用定期创建的 SLF4J 记录器替换 Lombok 记录注释,因为正如 AspectJ 编译器在警告消息中告诉您的那样,Lombok 的字节码修改不适用于 AspectJ 编译器:java: You aren't using a compiler supported by lombok, so lombok will not work and has been disabled. (...) Lombok supports: sun/apple javac 1.6, ECJ。当使用 Spring AOP 时,它可能适用于 Lombok,因为您只是使用普通的 Java 编译器。然而,我在原生 AspectJ 中进行了测试。

  • 修复方面中的许多语法错误并测试 Java 代码以及方面切入点中的一些语法错误。这些类甚至没有编译,并且切面不能对错误的切入点做任何有意义的事情。如果您修改原始代码以创建独立示例,请在发布之前进行测试。在公开展示你的作品时,作为一名开发人员,你应该有更多的自豪感。这是您的免费拍摄,因为您是 SO 的新手,但下次我将结束这个问题。

请务必阅读MCVE 文章,尝试理解它并在未来提出更好的问题。

固定方面代码

package de.scrum_master.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

  ObjectMapper mapper = new ObjectMapper();
  long startTimeController = 0L;
  long endTimeController = 0L;

  @Pointcut("within(com.sample.api.rest.account..*) || within(com.sample.api.rest.metadata..*)")
  public void entryController() {}

  @Pointcut("within(com.sample.api.rest.user..*)")
  public void entryControllerUser() {}

  @Pointcut("within(com.sample.api.service..*)")
  public void entryService() {}

  @Pointcut("within(com.sample.cmo.repositories..*)")
  public void entryDAO() {}

  @Before("entryController()")
  public void beforeOtherControllerCall(JoinPoint jp) throws JsonProcessingException {
    String methodName = jp.getSignature().getName();
    String className = jp.getTarget().getClass().toString();
    Object[] arguments = jp.getArgs();
    log.info(className + " Method : " + methodName + " Arguments passed : " + mapper.writeValueAsString(arguments));
    startTimeController = System.currentTimeMillis();
  }

  @Before("entryControllerUser()")
  public void beforeUserControllerCall(JoinPoint jp) throws JsonProcessingException {
    String methodName = jp.getSignature().getName();
    String className = jp.getTarget().getClass().toString();
    log.info(className + " Method : " + methodName);
    startTimeController = System.currentTimeMillis();
  }

  @After("entryController() || entryControllerUser()")
  public void afterControllerCall(JoinPoint jp) throws JsonProcessingException {
    endTimeController = System.currentTimeMillis();
    String methodName = jp.getSignature().getName();
    String className = jp.getTarget().getClass().toString();
    log.info(className + " Method : " + methodName + " Values returned :");
    if (endTimeController != 0) {
      log.info("Time consumed in " + className + " " + methodName + " call is " + (endTimeController - startTimeController) + "ms");
    }
  }

  @Around("entryService()")
  public Object executionTimeService(ProceedingJoinPoint pjp) throws Throwable {
    String methodName = pjp.getSignature().getName();
    String className = pjp.getTarget().getClass().toString();
    Object[] arguments = pjp.getArgs();
    log.info(className + " Method: " + methodName + " Arguments passed :" + mapper.writeValueAsString(arguments));
    long startTime = System.currentTimeMillis();
    Object obj = pjp.proceed();
    long endTime = System.currentTimeMillis();
    log.info(className + " Method : " + methodName + " Execution time: " + (endTime - startTime) + "ms");
    log.info(className + " Method : " + methodName + " Response received : " + mapper.writeValueAsString(obj));
    return obj;
  }

  @Around("entryDAO()")
  public Object executionTimeDAO(ProceedingJoinPoint pjp) throws Throwable {
    String methodName = pjp.getSignature().getName();
    String className = pjp.getTarget().getClass().toString();
    Object[] arguments = pjp.getArgs();
    log.info(className + " Method : " + methodName + " Arguments passed :" + mapper.writeValueAsString(arguments));
    long startTime = System.currentTimeMillis();
    Object obj = pjp.proceed();
    long endTime = System.currentTimeMillis();
    log.info(className + " method : " + methodName + " Execution time: " + (endTime - startTime) + "ms");
    log.info(className + " Method: " + methodName + " Response received : " + mapper.writeValueAsString(obj));
    return obj;
  }
}

固定测试

我之前在评论中向您发送了两个链接,向您展示了如何编写单元测试和集成测试。为什么你不做更类似于我的例子?你做错的一些事情是:

  • 您为正在测试的方面类创建了一个模拟。为什么?你想模拟依赖,而不是你真正想要测试的东西。就像在我的示例中一样,您应该正常实例化切面,仅在调用通知方法时注入模拟连接点。

  • 您不能简单地验证相应模拟对象链上的方法调用链,而是需要单独验证它们。因此,verify(joinPoint, times (1)).getSignature().getName().equals("MethodName") 之类的内容不起作用。

  • 你试图存根when(object.getClass()).thenReturn(objectClass),这是不必要的,因为Object.getClass() 已经返回了一些东西。此外,它是 JDK 引导类的最终方法。你不能简单地嘲笑它。

这个怎么样?

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.junit.Test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class LoggingAspectTest {
  @Test
  public void testBeforeOtherControllerCall() throws Throwable {
    JoinPoint joinPoint = mock(JoinPoint.class);
    LoggingAspect logging = new LoggingAspect();
    String[] args = { "arg1", "arg2" };
    Object[] obj = args;
    Signature signature = mock(Signature.class);
    when(joinPoint.getSignature()).thenReturn(signature);
    when(signature.getName()).thenReturn("MethodName");
    Object object = mock(Object.class);
    when(joinPoint.getTarget()).thenReturn(object);
    when(joinPoint.getArgs()).thenReturn(obj);
    logging.beforeOtherControllerCall(joinPoint);
    verify(joinPoint, times(1)).getSignature();
    verify(signature, times(1)).getName();
    verify(joinPoint, times(1)).getTarget();
    verify(joinPoint, times(1)).getArgs();
  }
}

这涵盖了被测方法并验证了对您感兴趣的模拟对象的调用,即使我发现这些验证有些问题。您真的要测试方面的内部吗?如果有的话,您应该测试副作用或结果。当然你也可以像我的例子那样做。

运行覆盖率测试时,我的 IDE 如下所示:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-11
    • 1970-01-01
    • 1970-01-01
    • 2020-06-16
    • 2014-06-22
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    相关资源
    最近更新 更多