Java 如何模仿一个方面

Java 如何模仿一个方面,java,testing,mocking,aspectj,Java,Testing,Mocking,Aspectj,我目前正在使用aspectj开发一些监控工具。因为这个工具应该是技术独立的(尽可能),所以我不使用Spring进行注射。但我想让我的方面进行单元测试 方面示例: @Aspect public class ClassLoadAspect { private Repository repository; public ClassLoadAspect() { repository = OwlApiRepository.getInstance(); }

我目前正在使用aspectj开发一些监控工具。因为这个工具应该是技术独立的(尽可能),所以我不使用Spring进行注射。但我想让我的方面进行单元测试

方面示例:

@Aspect
public class ClassLoadAspect {
    private Repository repository;

    public ClassLoadAspect() {
        repository = OwlApiRepository.getInstance();
    }  

    @After("anyStaticInitialization()")
    public void processStaticInitilization(JoinPoint jp) {
        Class type = jp.getSourceLocation().getWithinType();
        if (type.isInterface()) {
            repository.storeInterfaceInitialization(type);
        } else if (type.isEnum()) {
            repository.storeEnumInitialization(type);
        } else {
            repository.storeClassInitialization(type);
        }

    }

    @Pointcut("staticinitialization(*) && !within(cz.cvut.kbss.odra..*)")
    public void anyStaticInitialization() {
    }

    public Repository getRepository() {
        return repository;
    }

    public void setRepository(Repository repository) {
        this.repository = repository;
    }  
}
然而,我真的不知道如何构造单元测试(repository字段应该被模拟(使用mockito)),但是我没有控制方面的创建,因此我无法手动设置依赖项。我应该调用什么来获取实例?或者还有其他一些场景如何对aspectj方面进行单元测试


谢谢。

您可以拆分测试。首先测试方面的逻辑。这是一个pojo。你可以随意测试它。第二部分是测试切入点。在这种情况下,创建另一个具有相同切入点的简单方面(例如,将它们提取为常量)。也许有一些专用的测试工具,但我不知道有没有,这是我想到的最简单的方法

我当前的解决方案是引入AspectJ hack,以覆盖Singleton factory方法

@Aspect
public class MockingAspect {

    @Around("call(synchronized static OwlApiRepository *(..))")
    public OwlApiRepository processGetInstance(ProceedingJoinPoint jp) {      
        System.out.println("getting mock");
        return MockHolder.getMock();
    }
}

沿着这些思路,基本上继续保留您的方面,在方面内部将行为委托给另一个接口,并为您的测试模拟该接口,而不是模拟方面本身。下面是一个伪代码:

public interface ClassLoadHelper{
    void processStaticInitialization(Class<?> clazz);
}

public class ClassLoadHelperImpl implements ClassLoadHelper{
    private Repository repository;

    public ClassLoadHelperImpl() {
        repository = OwlApiRepository.getInstance();
    }  

    void processStaticInitialization(Class<?> clazz){
        if (type.isInterface()) {
            this.repository.storeInterfaceInitialization(type);
        } else if (type.isEnum()) {
            this.repository.storeEnumInitialization(type);
        } else {
            this.repository.storeClassInitialization(type);
        }        
    }
}


@Aspect
public class ClassLoadAspect {
    private ClassLoadHelper classLoadHelper;


    @After("anyStaticInitialization()")
    public void processStaticInitilization(JoinPoint jp) {
        Class<?> type = jp.getSourceLocation().getWithinType();
        this.classLoadHelper.processStaticInitialization(type);

    }

    @Pointcut("staticinitialization(*) && !within(cz.cvut.kbss.odra..*)")
    public void anyStaticInitialization() {
    }

    public ClassLoadHelper getClassLoadHelper() {
        return classLoadHelper;
    }

    public void setClassLoadHelper(ClassLoadHelper classLoadHelper) {
        this.classLoadHelper = classLoadHelper;
    }  
}

你说你找到了自己的方法来引入模拟对象hacky。你到底不喜欢什么?你怎么想?我只能猜测:

您是否不喜欢在元方面全局替换对
OwlApiRepository.getInstance()
的调用?然后,您可以专门将模拟对象注入限制在方面的构造函数中(我使用本机AspectJ语法,因为我对POJO注释样式感到不舒服):

您还可以看到,meta(方面测试)方面的这个变体也有一个开关,可以随意打开和关闭它。也许这也是你不喜欢的。正如我所说,我在猜测。在您的反馈之后,我可能能够更具体地回答

编辑:关于您的担忧,我想我已经尽可能地解决了:

  • 您不需要模拟支架

  • 该特性可以(取消)激活。它很容易使其激活依赖于其他条件,因此它仅在您的测试环境中处于活动状态。如果这还不够,请对生产方面使用编译时编织,对测试方面使用加载时编织。这样,它的字节码甚至不会出现在您的生产环境中

  • 我的版本不会在全球范围内取代任何东西,但就像一个好的外科医生一样,只会在一个地方进行微创切割

  • 我无法真正理解您对字节码操纵的担忧,原因有几个:您使用AspectJ,即固有的字节码操纵(编织)。您可以使用Mockito,它在运行时创建类。我也不明白您在哪里看到AspectJ的缺陷。您没有解释您希望“语言的标准方法”如何运行,或者它应该为测试提供什么接口。即使你有,我也不能为你改变你自己选择的语言(AJ)和工具(Mockito)


您只需要单元测试,对吗? 这是一个小单元测试,用于测试带有方面的自定义注释,目的是在自定义应用程序异常中包装一个throwable。(Testng+Mockito)


在我的解决方案中我不喜欢的是:静态方法的全局替换使得测试变得困难——我必须手动重置存储库模拟(而不是设置一个新的)。我还必须引入一个mock持有者来访问mock repository对象。第三件事是,我不喜欢为了设置mock而对字节码进行调整,我真的认为这必须通过语言的标准方式来完成(如果不可能,那么它显示了aspectj设计中的缺陷)。但从代码来看,您的解决方案可能会(至少不需要持有者):-)。重点是,赏金是您的。谢谢:-)对不起,太吵了。我决定在回答中添加注释,因为可用于注释的字符数太少。不过还是要谢谢你赏金给我
ClassLoadAspect.aspectOf().setClassLoadHelper(mockClassLoadHelper);
public privileged aspect ClassLoadTestAspect {
    static boolean active = true;

    declare precedence : ClassLoadTestAspect, ClassLoadAspect;
    pointcut classLoadAspect() :
        if(active) && 
        withincode(ClassLoadAspect.new()) && 
        call(* OwlApiRepository.getInstance());

    Object around() : classLoadAspect() {
        return new MockRepository();
    }
}
public class ResourceApplicationExceptionAspectTest {
@Mock
private ProceedingJoinPoint pjp;
@Mock
private ResourceApplicationException resourceApplicationException; //annotation definition

@BeforeMethod
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

}

@Test(groups ="unit", expectedExceptions = ResourceApplicationException.class)
public void testWrapExceptionAdvice() throws Throwable {

    ResourceApplicationExceptionAspect aspect = new ResourceApplicationExceptionAspect();

    when(pjp.proceed()).thenThrow(new NullPointerException());
    aspect.wrapExceptionAdvice(pjp, resourceApplicationException);
}