如何动态检测 JUnit 测试?

Posted

技术标签:

【中文标题】如何动态检测 JUnit 测试?【英文标题】:How to dynamically instrument JUnit tests? 【发布时间】:2020-06-15 09:10:30 【问题描述】:

在上下文开始之前,我正在使用 Invesdwin (https://github.com/subes/invesdwin-instrument) 在 main 方法中将 java 代理动态加载到我的 Spring Boot 项目中:

DynamicInstrumentationLoader.waitForInitialized();
DynamicInstrumentationLoader.initLoadTimeWeavingContext();

ApplicationContext springContext = SpringApplication.run(Some_Service.class);
...

这很有效,因为它消除了在从命令行运行 java -jar 命令时添加 -javaagent 参数的需要。

当涉及到单元测试时,就会出现问题。由于它们没有 main 方法(我可以利用它),因此我无法在 Spring Context 初始化之前使这两行运行。如果没有这些参数,每个测试都会导致上下文加载失败并出现此错误:

ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-instrument-version.jar

我可以在最终构建期间通过在我的 POM 中设置 Surefire 插件来解决此问题:

<!--Maven Surefire Plugin-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>test</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <argLine>
                    -javaagent:lib/aspectjweaver-1.9.5.jar 
                    -javaagent:lib/spring-instrument-5.2.3.RELEASE.jar
                </argLine>
            </configuration>
          </plugin>

不幸的是,这仅在最终构建阶段有效。在 Eclipse 中运行单个测试方法将失败,除非我手动将这些参数添加到该测试的运行配置中,这至少可以说是一件痛苦的事情。

我创建了一个自定义运行器类,试图让代码在 Spring 上下文初始化之前运行,如下所示:

public class WeavingRunner extends SpringJUnit4ClassRunner

    public WeavingRunner(Class<?> clazz) throws InitializationError 
    
        super(clazz);

        DynamicInstrumentationLoader.waitForInitialized();
        DynamicInstrumentationLoader.initLoadTimeWeavingContext();
    

虽然当我的基础测试使用此运行器类而不是 SpringRunner 时,Eclipse 控制台确实提示我正在发生编织,但我得到了各种编织错误,这些错误似乎表明动态编织还没有很快发生:

java.lang.NoSuchMethodException: com.something.SomeAspectClass.aspectOf()

在运行 JUnit 测试时,是否有一种已知的方法可以复制在 main 方法中运行的代码?

******编辑******

我注意到这很奇怪。如果我将包含测试的包作为 JUnit 测试运行,它就可以工作!仅当在 Eclipse 中将 src/test/java 文件夹作为 JUnit 测试运行或将应用程序本身作为 JUnit 测试运行(我最终需要)时,才会出现上述编织错误。动态编织正在工作,但不知何故,它只在运行单个测试或作为 JUnit 测试的封闭包时才起作用。我希望这是有道理的!

我开始怀疑我的 aop.xml 文件有问题,但如果运行单个测试甚至整个包都可以正常工作,这怎么可能是问题?!

【问题讨论】:

我知道 Eclipse 中的 AspectJ 支持 (AJDT) 在大多数方面都比 IntelliJ IDEA 更好,但是仍然存在问题,并且由于缺乏人力而缺乏维护。我通常静态编译我的方面或使用编织代理,而不是动态编织器附件,因为它对类加载的顺序很敏感。但是您可以尝试编写自己的检测 JUnit 运行器。无论如何,为了重现和分析您的问题,MCVE 会很好,最好是在 GitHub 上。我们最终可能会得到解决方法和/或 Eclipse 错误票。 我上面的 WeavingRunner 课程是一个(糟糕的)尝试编写我自己的仪器运行程序的尝试,但在某些情况下某些事情在时间方面表现不佳。我将尝试整理一个完整的示例。 【参考方案1】:

JUnit 可能会进行类路径扫描以发现单元测试。此外,在可以在测试中调用 invesdwin-instrument 之前,将加载具有所有依赖项的 junit 类。因此单元测试类本身不能使用方面。我知道的唯一解决方法是将方面使用放入一个嵌套类中,该类在初始化测试类并因此加载 invesdwin-instrument 后加载。这里有一个测试@Transactional 方面的模式示例:https://github.com/invesdwin/invesdwin-context-persistence/blob/master/invesdwin-context-persistence-parent/invesdwin-context-persistence-jpa-hibernate/src/test/java/de/invesdwin/context/persistence/jpa/hibernate/MultiplePersistenceUnitsTest.java

【讨论】:

以上是关于如何动态检测 JUnit 测试?的主要内容,如果未能解决你的问题,请参考以下文章

JUnit test 如何做Java静态方法测试

当 pom.xml 中存在 TestContainers 依赖项时,未检测到 JUnit 5 测试

JUnit 测试用例未检测控制器中的方法

我可以同时运行@TestFactory生成的Junit动态测试吗?

Junit 单元测试在 intelliJ IDEA 中的安装

如果 JUnit 覆盖率低于某个阈值,如何使 Maven 构建失败