在单元测试运行期间禁用某些方面

发布于 2024-12-04 22:08:24 字数 2438 浏览 2 评论 0原文

我有集成测试(加载上下文)和单元测试一起运行。我的代码使用 spring 进行aspectj 编译时编织。

我的问题是我声明的建议也在我的一些单元测试期间运行。这消除了单元测试的概念,这就是为什么我想禁用它们。

是否有一些东西可以放在切入点声明上、一些我可以调用的方法、一些 spring 配置或 Maven 命令来禁用所有 *UnitTest.java 之类的建议?

感谢您的帮助。


示例:

我有以下单元测试:

@RunWith(MockitoJUnitRunner.class)
public class CompanyServiceImplTest {
    @Test
    public void createCampaignTest() throws Exception {
        when(companyDaoMock.saveCompany(any(Campaign.class))).thenReturn(77L);

        Long campaignId = companyService.createCampaign(campaignMock);

        assertEquals(Long.valueOf(77L), Long.valueOf(campaignId));
    }
}

和以下服务方法:

@Override
@Transactional
@EventJournal(type = EventType.CAMPAIGN_CREATE, owner = EventOwner.TERMINAL_USER)
public Long createCampaign(Campaign campaign) {
    return companyDao.saveCompany(campaign);
}

方面:

@Aspect
public class EventJournalAspect {

    @Autowired
    private EventJournalService eventJournalService;

    @Pointcut(value="execution(public * *(..))")
    public void anyPublicMethod() {}

    @Pointcut("within(com.terminal.service..*)")
    private void inService() {}

    @AfterReturning(pointcut = "anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", returning = "id")
    public void process(Object id, EventJournal eventJournal, AbstractDomainEntity entity)
            throws Throwable {
        if (eventJournal.type() != EventType.CAMPAIGN_PAYMENT || id != null) {
            saveEvent(eventJournal, EventStatus.SUCCESS, entity, (Long) id);
        }
    }

    @AfterThrowing(pointcut = "anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", throwing="ex")
    public void processException(EventJournal eventJournal, AbstractDomainEntity entity, Exception ex) throws Throwable {
        saveEvent(eventJournal, EventStatus.FAILURE, entity, null);
    }

    private void saveEvent(EventJournal eventJournal, EventStatus status, AbstractDomainEntity entity, Long persistentId)   {
        EventType type = eventJournal.type();
        EventOwner owner = eventJournal.owner();
        eventJournalService.saveEvent(type, owner, EventStatus.SUCCESS, entity, persistentId);
    }

}

测试执行时 - eventJournalService 为 null。因此我看到 NullPointerException

I have integration tests (load context) and unit tests running together. My code does aspectj compile time weaving using spring.

My problem is that my declared advises also run during some of my unit tests. This kills the notion of a unit test, which is why I would like to disable them.

Is there something I can put on the pointcut declaration, some method I can call, some spring configuration, or maven command that disables these advises for something like all *UnitTest.java?

Thanks for the help.


example:

I have the following unit test:

@RunWith(MockitoJUnitRunner.class)
public class CompanyServiceImplTest {
    @Test
    public void createCampaignTest() throws Exception {
        when(companyDaoMock.saveCompany(any(Campaign.class))).thenReturn(77L);

        Long campaignId = companyService.createCampaign(campaignMock);

        assertEquals(Long.valueOf(77L), Long.valueOf(campaignId));
    }
}

and following service method:

@Override
@Transactional
@EventJournal(type = EventType.CAMPAIGN_CREATE, owner = EventOwner.TERMINAL_USER)
public Long createCampaign(Campaign campaign) {
    return companyDao.saveCompany(campaign);
}

aspect:

@Aspect
public class EventJournalAspect {

    @Autowired
    private EventJournalService eventJournalService;

    @Pointcut(value="execution(public * *(..))")
    public void anyPublicMethod() {}

    @Pointcut("within(com.terminal.service..*)")
    private void inService() {}

    @AfterReturning(pointcut = "anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", returning = "id")
    public void process(Object id, EventJournal eventJournal, AbstractDomainEntity entity)
            throws Throwable {
        if (eventJournal.type() != EventType.CAMPAIGN_PAYMENT || id != null) {
            saveEvent(eventJournal, EventStatus.SUCCESS, entity, (Long) id);
        }
    }

    @AfterThrowing(pointcut = "anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", throwing="ex")
    public void processException(EventJournal eventJournal, AbstractDomainEntity entity, Exception ex) throws Throwable {
        saveEvent(eventJournal, EventStatus.FAILURE, entity, null);
    }

    private void saveEvent(EventJournal eventJournal, EventStatus status, AbstractDomainEntity entity, Long persistentId)   {
        EventType type = eventJournal.type();
        EventOwner owner = eventJournal.owner();
        eventJournalService.saveEvent(type, owner, EventStatus.SUCCESS, entity, persistentId);
    }

}

When test executes - eventJournalService is null. Thus I see NullPointerException

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

始终不够 2024-12-11 22:08:24

答案很简单:您想使用 if()切入点表达式


更新(在问题也更新之后):上面最初提供的链接应该包含足够的信息,但对于它的价值,有一个简短的解释和一个简单的示例:

An if() 切入点是一个返回布尔值静态方面方法。如果返回值为true,则表示任何组合切入点如myPointcut() &&只要 myPointcut() 匹配,if() 就匹配。对于返回值 false ,整个组合切入点不匹配,从而有效地停用连接到切入点的任何建议。

那么在静态 if() 切入点中可以做什么呢?

  • 评估某些工具类的静态布尔成员,例如 TestMode.ACTIVE,仅在单元或集成测试期间为 true
  • 评估仅在测试期间设置的环境变量 评估
  • 仅在测试期间设置的 Java 系统属性
  • 以及更多的事情

如果你想做一些更奇特(和更棘手)的事情并且性能不是那么重要,你还可以尝试动态确定自动连接的方面成员变量是否等于 null ,并且仅在注入对象时激活切入点实际上是存在的。这里唯一的问题是如何从静态方法中确定成员变量。我对Spring AOP一无所知,但在普通的AspectJ中,有一个辅助类Aspects,它有几个名为aspectOf(..)的重载方法。假设您的方面被实例化为单例,您可以执行以下操作:

@Pointcut("if()")
public static boolean isActive() {
    return Aspects.aspectOf(PerformanceMonitorAspect.class).eventJournalService != null;
}

// ...

@AfterReturning(pointcut = "isActive() && anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", returning = "id")
// ...

@AfterThrowing(pointcut = "isActive() && anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", throwing="ex")
// ...

The answer is simple: You want to use an if() pointcut expression.


Update (after the question has also been updated): The originally provided link above should contain enough information, but for what it is worth, a short explanation and a simple example:

An if() pointcut is a static aspect method returning a boolean. If the return value is true, it means that any combined pointcut like myPointcut() && if() matches as long as myPointcut() matches. For a return value of false the whole combined pointcut does not match, effectively deactivating any advice connected to the pointcut.

So what can you do in a static if() pointcut?

  • evaluate a static boolean member of some tool class like TestMode.ACTIVE which is only true during unit or integration testing
  • evaluate an environment variable which is only set during testing
  • evaluate a Java system property which is only set during testing
  • and many more things

If you want to do something fancier (and trickier) and performance is not so important, you can also try to dynamically determine whether the auto-wired aspect member variable equals null or not and only activate your pointcuts if the injected object is actually present. The only problem here is how to determine a member variable from a static method. I have no idea about Spring AOP, but in plain AspectJ there is the helper class Aspects with several overloaded methods named aspectOf(..). Assuming that your aspect is instantiated as a singleton, you could do something like this:

@Pointcut("if()")
public static boolean isActive() {
    return Aspects.aspectOf(PerformanceMonitorAspect.class).eventJournalService != null;
}

// ...

@AfterReturning(pointcut = "isActive() && anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", returning = "id")
// ...

@AfterThrowing(pointcut = "isActive() && anyPublicMethod() && inService() && @annotation(eventJournal) && args(entity,..)", throwing="ex")
// ...
黎歌 2024-12-11 22:08:24

我只能猜测:
首先要有一个单独的Spring applicationContext-test.xml,
无需组件扫描;
在 Maven 中,您可以添加一个阶段运行时,不包括用于测试的编织 jar。

I can only guess:
The first thing is to have a separate Spring applicationContext-test.xml,
without component-scan;
In maven you can add a phase runtime excluding weaving jars for test.

土豪 2024-12-11 22:08:24

编译时编织会将建议调用内联到由您拥有的切入点标识的目标方法中。我个人认为,在编译时编织到位的情况下进行单元测试是很好的,因为在运行时您的单元确实包含带有内联建议的类?

我不想包含建议的想法是有两个不同的编译目标,一个有编译时编织,一个没有,你应该能够通过 maven 配置文件来做到这一点,一个不编织建议的 dev 配置文件和一个 prod 配置文件将各个方面编织进去。

Compile time weaving would inline the advice calls in the targeted methods identified by the pointcuts that you have. I personally feel that it is good to unit test with the compile time weaving in place, because at runtime your unit does include the class with the advice inlined?

The thought I have to not include advice would be to have two different compile targets, one with compile time weaving, and one without, you should be able to do this through maven profiles, a dev profile not weaving advice in and a prod profile to weave the aspects in.

浮世清欢 2024-12-11 22:08:24

您可以编写一个方法,如果当前执行是使用 JUnit 框架启动的,则返回该方法。

该方法可以使用 Thread.currentThread().getStackTrace() 检查堆栈跟踪并搜索 MockitoJUnitRunner 是否存在。

我使用 SpringJUnit4ClassRunner 测试了这个解决方案,但我认为可以与 MockitoJUnitRunner 一起使用。

此外,您还可以拥有一个静态布尔字段,例如:

private static boolean TEST_ENVIRONMENT = false;

在项目中(而不是测试中)中存在的类中,并检查控制方法中的值,而不是使用堆栈跟踪。

运行测试时,可以使用 @BeforeClass 注释来设置 TEST_ENVIRONMENT = true。

此解决方案仅为您提供了一种了解代码是否正在测试中运行的方法。

You can write a method that returns if current execution was launched using JUnit framework.

The method can check stack trace with Thread.currentThread().getStackTrace() and search for MockitoJUnitRunner presence.

I tested this solution using a SpringJUnit4ClassRunner but I think could work with MockitoJUnitRunner.

Also, you can have got a static boolean field like:

private static boolean TEST_ENVIRONMENT = false;

In a class present in your project (not in your tests) and check the value in the control method instead of use stack trace.

When you run your tests, you can use @BeforeClass annotation to set TEST_ENVIRONMENT = true.

This solution only gives you a way to know if your code is running from a test or not.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文