使用 Maven 构建单独的 JAR 文件以对自定义类加载器进行单元测试

发布于 2024-08-04 03:31:47 字数 351 浏览 3 评论 0原文

作为我当前项目的一部分,我创建了一个自定义类加载器。自定义加载程序的部分单元测试涉及使用一些 JAR 文件来演示加载程序的正确行为。

我想在运行实际的单元测试之前从 Java 源构建测试 JAR 文件。此外,运行单元测试时,测试 JAR 文件不能位于类路径上,因为我想在测试执行期间动态加载它们。

是否有一个标准模式来完成这种“在测试阶段之前构建一些 JAR,但将它们排除在类路径之外”的要求?我不敢相信我是第一个尝试使用 Maven 2 执行此操作的人,但我似乎无法找到正确的 POM 结构和依赖项。通常我最终会在测试阶段之前没有构建一些测试 jar,但我也遇到了构建顺序不一致的问题,导致构建可以在一台机器上正常工作,但无法构建一些在另一个上测试罐子。

As part of my current project I've created a custom class loader. Part of the unit tests for the custom loader involves using some JAR files to demonstrate the proper behavior of the loader.

I'd like to build the test JAR files from Java sources ahead of running the actual unit tests. Further, the test JAR files cannot be on the class path when the unit tests are run, since I want to dynamically load them during the test execution.

Is there a standard pattern for accomplishing this sort of "build some JARs on the side before the test phase but leave them out of the class path" requirement? I can't believe I'm the first person to try doing this with Maven 2, but I can't seem to hit on the right POM structure and dependencies. Usually I end up with some of the test jars not being built ahead of the test phase, but I've also had problems with inconsistent order-of-build causing the build to work properly on one machine, but fail to build some of the test jars on another.

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

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

发布评论

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

评论(3

终弃我 2024-08-11 03:31:47

最简单的做法是设置另一个项目来打包测试 jar 的类,然后将其设置为正常的 测试范围 依赖项。

如果您不想/不能这样做,您可以使用程序集插件在 process-test-classes 阶段创建一个 jar(即在编译测试之后但之前)测试已执行)。下面的配置将调用程序集插件,在目标目录的该阶段创建一个名为 classloader-test-deps 的 jar。然后,您的测试可以根据需要使用该 jar。

程序集插件使用程序集描述符(在 src/main/assembly 中,称为 test- assembly.xml)来打包目标/测试类的内容。我设置了一个过滤器来包含 com.test 包及其子包的内容。这假设您有一些可以应用于 jar 内容的包名称约定。

默认情况下,程序集插件会将 jar 作为附加工件附加,通过将 attach 指定为 false,它将不会被安装/部署。

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>2.2-beta-2</version>
  <executions>
    <execution>
      <id>create-test-dependency</id>
      <phase>process-test-classes</phase>
      <goals>
        <goal>single</goal>
      </goals>
      <configuration>
        <finalName>classloader-test-deps</finalName>
        <attach>false</attach>
        <descriptors>
          <descriptor>src/main/assembly/test-assembly.xml</descriptor>
        </descriptors>
      </configuration>
    </execution>
  </executions>
</plugin>

这是test- assembly.xml的内容

<assembly>
  <id>test-classloader</id>
  <formats>
    <format>jar</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <fileSets>
    <fileSet>
      <directory>${project.build.testOutputDirectory}</directory>
      <outputDirectory>/</outputDirectory>
      <!--modify/add include to match your package(s) -->
      <includes>
        <include>com/test/**</include>
      </includes>
    </fileSet>
  </fileSets>
</assembly>

The simplest thing to do is to set up another project to package the classes for your test jar, then set that as a normal test-scoped dependency.

If you don't want/aren't able to do that, you can use the assembly plugin to create a jar in the process-test-classes phase (i.e. after the tests have been compiled but before the tests are executed). The configuration below will invoke the assembly plugin to create a jar called classloader-test-deps in that phase in the target directory. Your tests can then use that jar as needed.

The assembly plugin uses an assembly descriptor (in src/main/assembly, called test-assembly.xml) that packages the contents of target/test-classes. I've set up a filter to include the contents of com.test package and its children. This assumes you have some package name convention you can apply for the contents of the jar.

The assembly plugin will by default attach the jar as an additional artifact, by specifying attach as false, it will not be installed/deployed.

<plugin>
  <artifactId>maven-assembly-plugin</artifactId>
  <version>2.2-beta-2</version>
  <executions>
    <execution>
      <id>create-test-dependency</id>
      <phase>process-test-classes</phase>
      <goals>
        <goal>single</goal>
      </goals>
      <configuration>
        <finalName>classloader-test-deps</finalName>
        <attach>false</attach>
        <descriptors>
          <descriptor>src/main/assembly/test-assembly.xml</descriptor>
        </descriptors>
      </configuration>
    </execution>
  </executions>
</plugin>

This is the content of test-assembly.xml

<assembly>
  <id>test-classloader</id>
  <formats>
    <format>jar</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <fileSets>
    <fileSet>
      <directory>${project.build.testOutputDirectory}</directory>
      <outputDirectory>/</outputDirectory>
      <!--modify/add include to match your package(s) -->
      <includes>
        <include>com/test/**</include>
      </includes>
    </fileSet>
  </fileSets>
</assembly>
几味少女 2024-08-11 03:31:47

我会尝试在测试中设置您的测试所需的一切。主要优点是测试中没有隐含的神奇的看不见的设置。该测试可以在每个环境中运行。此外,添加新的严格隔离的场景要容易得多,因为您不依赖于某些混合场景设置。

设置应该不会太难:

  • 序列化一个 java 类:
    • 带有一些类型代码工程库
    • 或者,使用重命名为 .class 以外的某个文件后缀的 java 类文件。将其放在测试资源文件夹下并使用类加载器加载(getResourceAsStream(...))。
  • zip 类文件 (`java.util.zip.GZIPOutputStream`)
  • 使用类加载器加载类文件

还有一种替代方法,它使用 java 类加载器设计,无需生成其他类即可工作。

Java 有一个类加载器层次结构。每个类加载器都有一个父类加载器。类加载器层次结构的根是引导类加载器。当一个类被类加载器加载时,它将尝试首先使用父类加载器加载该类,然后再加载它自己。

您可以使用当前的类加载器加载测试类。将其打包并使用您自己的类加载器加载它。唯一的区别是您将父类加载器设置为无法加载测试类的父类加载器。

String resource = My.class.getName().replace(".", "/") + ".class";

//class loader of your test class
ClassLoader myClassLoader = currentThread().getContextClassLoader();
assert ! toList(myClassLoader.getResources(resource)).isEmpty();

//just to be sure that the resource cannot be loaded from the parent classloader
ClassLoader parentClassloader = getSystemClassLoader().getParent();
assert toList(parentClassloader.getResources(resource)).isEmpty();

//your class loader
URLClassLoader myLoader = new URLClassLoader(new URL[0], parentClassloader);
assert toList(myLoader.getResources(resource)).isEmpty();

I would try to set up everything your tests needs from within the test. The main advantage is that there is no magic unseen setup that is implicit for the test. The test can run in every environment. Additionally it is much easier to add new strictly isolated scenarios as you are not dependent on some mixed scenario setup.

The setup should not be too hard:

  • serialize a java class:
    • with some type code engineering library
    • Alternatively, use a java class file renamed to some file suffix other than .class. Put it under the test resource folder and load with the class loader (getResourceAsStream(...)).
  • zip the class file (`java.util.zip.GZIPOutputStream`)
  • load the class file with your class loader

There is an alternative approach that uses the java class loader design and works without generation of additional classes.

Java has a class loader hierarchy. Every class loader has a parent class loader. The root of the class loader hierarchy is the boot class loader. When a class is loaded with a class loader it will try to load the class first with the parent class loader and then itself.

You can load the test class with the current class loader. Jar it and load it with your own class loader. The only difference is that you set the parent class loader to one that cannot load your test class.

String resource = My.class.getName().replace(".", "/") + ".class";

//class loader of your test class
ClassLoader myClassLoader = currentThread().getContextClassLoader();
assert ! toList(myClassLoader.getResources(resource)).isEmpty();

//just to be sure that the resource cannot be loaded from the parent classloader
ClassLoader parentClassloader = getSystemClassLoader().getParent();
assert toList(parentClassloader.getResources(resource)).isEmpty();

//your class loader
URLClassLoader myLoader = new URLClassLoader(new URL[0], parentClassloader);
assert toList(myLoader.getResources(resource)).isEmpty();
山色无中 2024-08-11 03:31:47

Maven 通过依赖性分析来解析构建顺序,因此通常您的 JAR 会按顺序构建,因为使用测试 JAR 的 JAR 会简单地将它们声明为依赖项。但是,依赖项也放置在类路径上。依赖项的“范围”决定了它所在的类路径。例如,“编译”依赖项位于用于编译、测试和运行的类路径上; “运行时”依赖项位于用于测试和运行的类路径上; “测试”依赖项仅在测试期间位于类路径上。不幸的是,您遇到了任何可用范围都没有涵盖的情况:您有一个依赖项,但您不希望它出现在类路径上。这是一个边缘用例,也是您难以发现示例的原因。

因此,除非某些 Maven 专家提出相反的观点,否则我认为如果不编写特殊的 Maven 插件,这是不可能的。然而,除此之外,我还推荐其他东西。您真的需要定制 JAR 来测试您的类加载器吗?我觉得这听起来很可疑。也许您可以使用任何旧的 JAR?如果是这样,我将使用 maven-dependency-plugin 将一些已知始终位于存储库中的 JAR(例如 log4j)复制到本地模块的目标目录中。然后,您的测试可以通过 target/log4j-xxx.jar 处的文件路径访问该 JAR,然后您就可以做您的事情了。

Maven resolves build order via dependency analysis, so normally your JARs would build in order because the one that uses your test JARs would simply declare them as dependencies. However, dependencies are also placed on the classpath. The "scope" of a dependency determines which classpath it goes on. For example 'compile' dependencies are on the classpath for compiling, testing, and running; 'runtime' dependencies are on the classpath for testing and running; 'test' dependencies are only on the classpath during test. Unfortunately, you have a case not covered by any of the available scopes: you have a dependency, but you don't want it on the classpath. This is a fringe use case and is why you are having trouble discovering examples.

So, unless some Maven guru rears up to indicate the contrary, I suggest this is impossible without writing a special Maven plugin. Instead of that, however, I recommend something else. Do you really need custom-built JARs to test your classloader? That sounds fishy to me. Perhaps you can use any old JAR? If so, I would use the maven-dependency-plugin to copy some JAR known to always be in your repository (log4j for example) into your local module's target directory. Your test can then access that JAR via filepath at target/log4j-xxx.jar and you can do your thing.

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