因为工作原因要做一个全局留痕切面,在springboot中编写切面功能很容易,也很快就写完了,但是我是一个喜欢写自动化测试代码的人。就开始研究如何在springboot中针对aop切面写单元测试了。
经过一番bing和实战,终于完成了aop切面的测试单元代码。
以下是aop切面代码,主要是定义GlobalLoggingAspect切面,拥有GlobalLogging注解的方法会执行这个切面。切面的主要功能就是截取方法名和参数,然后入库。
@Component
@Aspect
@Slf4j
public class GlobalLoggingAspect {
private final GlobalLoggingDao globalLoggingDao;
@Autowired
public GlobalLoggingAspect(GlobalLoggingDao globalLoggingDao) {
this.globalLoggingDao = globalLoggingDao;
}
@Pointcut("@annotation(com.gome.cb.common.domain.aop.GlobalLogging)")
public void logPointCut() {
}
/**
* 切面,全局留痕至数据库
*
* @param joinPoint
*/
@Around(value = "logPointCut()")
public Object doGlobalLogging(ProceedingJoinPoint joinPoint) throws Throwable {
Exception ex = null;
Object retObj = null;
try {
retObj = joinPoint.proceed();
} catch (Exception e) {
ex = e;
}
Object[] params = joinPoint.getArgs();
if (params == null || params.length == 0) {
return retObj;
}
final Integer opResult = (
(retObj instanceof ServiceResult)
&& (!StringUtils.equals(ServiceResultType.SUCCESS.getCode(),
((ServiceResult) retObj).getResultType().getCode()))
) || ex != null
? 0 : 1;
String className = joinPoint.getTarget().getClass().getSimpleName();
MethodSignature method = (MethodSignature) joinPoint.getSignature();
GlobalLogging gl = GlobalLogging.builder()
.userName(getCurrentLoginName())
.methodName(className + ":" + method.getName())
.methodParams(convertParams2Json(params))
.status(opResult)
.build();
globalLoggingDao.insert(gl);
if (ex != null) {
throw ex;
}
return retObj;
}
private String getCurrentLoginName() {
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
return (session != null && session.getAttribute("USER_NAME") != null)
? (String) session.getAttribute("USER_NAME") : "";
}
private String convertParams2Json(Object[] params) {
final int[] i = {0};
Map<String, Object> map = new HashMap<>();
Stream.of(params).filter(p -> p != null).forEach(p -> {
map.put(i[0] + "." + p.getClass().getSimpleName(), p);
i[0]++;
});
return JSON.toJSONString(map, SerializerFeature.IgnoreErrorGetter);
}
}
如下是测试类主体,利用了testng来进行测试。AopConfig内部类配置开启了切面功能。因为目标测试类即切面GlobalLoggingAspect中使用了dao数据库层的服务,但是测试类只会自动扫描和注入AopConfig中定义的ComponentScan包路径(测试类默认也是只会扫描和注入本类包下面的组件),而dao代码并不在这个包下,所以这里使用MockBean注解模拟了这个dao实例。
再多说一句,测试类启动时其实只会默认扫描和注入启动类所在包以及其子包下面定义的组件,或者使用ComponentScan自定义扫描包路径。只有被springboot自动加载注入的切面,切面才会生效,所以aop单元测试的一个重点就是使springboot去扫描和注入aop切面。
@RunWith(SpringRunner.class)
@SpringBootTest
@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
@ContextConfiguration(classes = { GlobalLoggingTest.class, GlobalLoggingTest.AopConfig.class })
public class GlobalLoggingTest extends AbstractTestNGSpringContextTests {
@SpyBean
private GlobalLoggingAspect globalLoggingAspect;
@MockBean
private GlobalLoggingDao globalLoggingDao;
@Autowired
private GlobalLoggingAopTest globalLoggingAopTest;
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan({"com.gome.cb.common.domain.aop"})
class AopConfig {
}
需要pom中添加testng包,否则会报错:cannot access org.testng.IHookable class file for org.testng.IHookable not found
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.7</version>
<scope>test</scope>
</dependency>
接下来就是准备测试代码,即加上切面注解GlobalLogging的方法。我直接在测试类中增加了内部测试类。
image.png
最后就是测试逻辑了,直接调,然后验证是否正确的走了切面。
test0()是目标方法没有测试,按照切面的逻辑,是不会走dao服务的,所以验证的是走了aop,但是没有走dao
test1()表示有一个String类型的参数,会走aop切面和dao方法。所以验证的是走了aop和dao方法,并且截获dao方法参数来验证是否正确的截取到了方法参数。
其他的test是增加方法参数类型和数量来验证aop切面是否能正确数据各种各样的参数并正确的序列化为json。
image.png
参考链接:
https://blog.csdn.net/yangyangrenren/article/details/85840173
网友评论