使用maven-plugin-testing-harness进行艰难的时间测试

时间:2017-07-21 15:22:38

标签: java maven maven-plugin

我的插件mojo测试类利用maven-plugin-test-harness构建完整的maven环境,包含所有pom配置,plexus容器上下文和repo访问。

以下所有内容都应该有效:

  1. 测试将引用插件项目的测试资源目录中的测试pom.xml
  2. 将使用mojo annotations
  3. 中的默认值填充mojo
  4. 接受测试pom中的所有指定配置
  5. maven项目对象已初始化
  6. 来自回购的所有依赖项都可用
  7. 测试应该通过IntelliJ IDE以及CI服务器上的Maven
  8. 由于缺乏具体的工作示例,我一直在尝试使用我从SO收集的内容以及其他在线博客或文章的不同修补程序。

    我目前正在努力让maven解决这些文物。虽然我从maven项目对象获得了依赖项列表,但工件列表是空的。

    这是我通过剖析AbstractMojoTestCase建立起来的。

    我无法使用MojoRule,因为JUnit5不再使用@Rules

    另外,一些maven API调用已被弃用,但我找不到新的实现。我认为直到mvn4才会出现。请参阅下面的引用。

    @BeforeEach
    protected void setUp() throws Exception {
        super.setUp();
        cleanUp();
        ClassLoader classLoader = getClass().getClassLoader();
        URL url = classLoader.getResource(TEST_POM);
        if (url == null) {
            throw new MojoExecutionException(String.format(
                    "Cannot locate %s", TEST_POM));
        }
        File pom = new File(url.getFile());
        //noinspection deprecation - wait on maven-plugin-testing-harness update
        MavenSettingsBuilder mavenSettingsBuilder = (MavenSettingsBuilder)
                getContainer().lookup(MavenSettingsBuilder.ROLE);
        Settings settings = mavenSettingsBuilder.buildSettings();
        MavenExecutionRequest request = new DefaultMavenExecutionRequest();
        request.setPom(pom);
        request.setLocalRepositoryPath(settings.getLocalRepository());
        MavenExecutionRequestPopulator populator =
                getContainer().lookup(MavenExecutionRequestPopulator.class);
        populator.populateDefaults(request);
        DefaultMaven maven = (DefaultMaven) getContainer().lookup(Maven.class);
        DefaultRepositorySystemSession repoSession =
                (DefaultRepositorySystemSession)
                    maven.newRepositorySession(request);
        LocalRepository localRepository = new LocalRepository(
                request.getLocalRepository().getBasedir());
        SimpleLocalRepositoryManagerFactory factory =
                new SimpleLocalRepositoryManagerFactory();
        LocalRepositoryManager localRepositoryManager =
                factory.newInstance(repoSession, localRepository);
        repoSession.setLocalRepositoryManager(localRepositoryManager);
        ProjectBuildingRequest buildingRequest =
                request.getProjectBuildingRequest()
                        .setRepositorySession(repoSession)
                        .setResolveDependencies(true);
        ProjectBuilder projectBuilder = lookup(ProjectBuilder.class);
        MavenProject project =
                projectBuilder.build(pom, buildingRequest).getProject();
        //noinspection deprecation - wait on maven-plugin-testing-harness update
        MavenSession session = new MavenSession(getContainer(), repoSession, 
                request, new DefaultMavenExecutionResult());
        session.setCurrentProject(project);
        session.setProjects(Collections.singletonList(project));
        request.setSystemProperties(System.getProperties());
        testMojo = (GenerateConfig) lookupConfiguredMojo(session,
                newMojoExecution("configure"));
        copyTestProjectResourcesToTarget(getContainer(), project, session);
    }
    

    [更新2017-07-27] :实际上现在解决了我的大部分问题。

    现在只有几个小问题:

    1. 获取settings.xml的代码标记为@Deprecated,因此我假设有更好的方法(使用MavenSettingsBuilder.buildSettings()
    2. 可能相当多的设置代码是在本机maven中运行时发生的重复过程,但需要在IntelliJ中使用JUnit运行。
    3. [更新2017-08-01] :测试现在需要访问一些属性文件,这些文件位于target/classes目录中的实时环境中的类路径上。

      逻辑上,它们是我的maven-plugin项目中的测试资源,因此我将它们包含在pom.xml目录中的测试src/test/resources/my-test-project所在的目录下。

      这没用,所以我也试过src/test/resources/my-test-project/src/main/resources,但这也不好。

      我很难在测试期间确定插件的类路径上的确切内容,或者确定如何正确构造它。

      [更新2017-08-02] :虽然我已经回答了我自己的问题(而不是扩展这个问题),但整个事情还没有完成,所以我没有标记这已经回答了。

      只是为了记录,这些是所需的依赖项:

          <dependency>
              <groupId>org.junit.jupiter</groupId>
              <artifactId>junit-jupiter-api</artifactId>
              <version>5.0.0-M4</version>
              <scope>test</scope>
          </dependency>
          <dependency>
              <groupId>org.junit.vintage</groupId>
              <artifactId>junit-vintage-engine</artifactId>
              <version>4.12.0-M4</version>
              <scope>test</scope>
          </dependency>
          <dependency>
              <groupId>org.apache.maven</groupId>
              <artifactId>maven-plugin-api</artifactId>
              <version>3.5.0</version>
          </dependency>
          <dependency>
              <groupId>org.apache.maven.plugin-tools</groupId>
              <artifactId>maven-plugin-annotations</artifactId>
              <version>3.5</version>
              <scope>provided</scope>
          </dependency>
          <dependency>
              <groupId>org.apache.maven.plugin-testing</groupId>
              <artifactId>maven-plugin-testing-harness</artifactId>
              <version>3.3.0</version>
              <scope>test</scope>
              <exclusions>
                  <exclusion>
                      <groupId>org.codehaus.plexus</groupId>
                      <artifactId>plexus-container-default</artifactId>
                  </exclusion>
              </exclusions>
          </dependency>
          <dependency>
              <groupId>org.apache.maven</groupId>
              <artifactId>maven-core</artifactId>
              <version>3.5.0</version>
          </dependency>
          <dependency>
              <groupId>org.apache.maven</groupId>
              <artifactId>maven-compat</artifactId>
              <version>3.5.0</version>
              <scope>test</scope>
          </dependency>
          <dependency>
              <groupId>org.twdata.maven</groupId>
              <artifactId>mojo-executor</artifactId>
              <version>2.3.0</version>
              <scope>test</scope>
          </dependency>
          <dependency>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-resources-plugin</artifactId>
              <version>3.0.2</version>
              <scope>test</scope>
          </dependency>
      

      [更新2017-08-09]:

      我必须添加更多功能,并发现测试很好,如果它想要解压缩的依赖项已经在本地repo中,但如果没有,它将无法获取它。

      我现在需要确定如何指示maven从远程仓库获取依赖项。

      我尝试启动依赖插件并在测试设置中调用resolve,但它很糟糕,我认为必须有一种更简单的方法。

3 个答案:

答案 0 :(得分:1)

我找到了从远程存储库中获取依赖项的解决方案。

使用像这样的maven内部,并从已弃用的类和重复功能的数量来判断,它给我的印象是maven v4会使这个多余。

使用此安装例程的一个小故障是它在maven项目目录中创建本地存储库目录树。这显然是不可取的,但需要进行一些调整才能解决。

@BeforeEach
public void setUp() throws Exception {
    super.setUp();
    ClassLoader classLoader = getClass().getClassLoader();
    URL url = classLoader.getResource(TEST_POM);
    if (url == null) {
        throw new MojoExecutionException(String.format(
                "Cannot locate %s", TEST_POM));
    }
    File pom = new File(url.getFile());
    Settings settings = getMavenSettings();
    if (settings.getLocalRepository() == null) {
        settings.setLocalRepository(
                org.apache.maven.repository.RepositorySystem
                        .defaultUserLocalRepository.getAbsolutePath());
    }
    MavenExecutionRequest request = new DefaultMavenExecutionRequest();
    request.setPom(pom);
    ArtifactRepository artifactRepository =
            new org.apache.maven.artifact.repository.
                    DefaultArtifactRepository(
                    "id", settings.getLocalRepository(),
                    new DefaultRepositoryLayout());
    request.setLocalRepository(artifactRepository);
    MavenExecutionRequestPopulator populator =
            getContainer().lookup(MavenExecutionRequestPopulator.class);
    populator.populateFromSettings(request, settings);
    DefaultMaven maven = (DefaultMaven)
            getContainer().lookup(Maven.class);
    DefaultRepositorySystemSession repositorySystemSession =
            (DefaultRepositorySystemSession)
                    maven.newRepositorySession(request);
    SimpleLocalRepositoryManagerFactory factory =
            new SimpleLocalRepositoryManagerFactory();
    LocalRepositoryManager localRepositoryManager =
            factory.newInstance(repositorySystemSession,
                    new LocalRepository(settings.getLocalRepository()));
    repositorySystemSession.setLocalRepositoryManager(
            localRepositoryManager);
    ProjectBuildingRequest buildingRequest =
            request.getProjectBuildingRequest()
                    .setRepositorySession(repositorySystemSession)
                    .setResolveDependencies(true);
    ProjectBuilder projectBuilder = lookup(ProjectBuilder.class);
    ProjectBuildingResult projectBuildingResult =
            projectBuilder.build(pom, buildingRequest);
    MavenProject project = projectBuildingResult.getProject();
    MavenSession session = new MavenSession(getContainer(),
            repositorySystemSession, request,
            new DefaultMavenExecutionResult());
    session.setCurrentProject(project);
    session.setProjects(Collections.singletonList(project));
    request.setSystemProperties(System.getProperties());
    testMojo = (GenerateConfig) lookupConfiguredMojo(session,
            newMojoExecution("configure"));
    testMojo.getLog().debug(String.format("localRepo = %s",
            request.getLocalRepository()));
    copyTestProjectResourcesToTarget(getContainer(), project, session);
    resolveConfigurationFromRepo(repositorySystemSession, project);
}

private Settings getMavenSettings()
        throws ComponentLookupException,
            IOException,
            XmlPullParserException {
    org.apache.maven.settings.MavenSettingsBuilder mavenSettingsBuilder
            = (org.apache.maven.settings.MavenSettingsBuilder)
                getContainer().lookup(
                    org.apache.maven.settings.MavenSettingsBuilder.ROLE);
    return mavenSettingsBuilder.buildSettings();
}

/**
 * This is ugly but there seems to be no other way to accomplish it. The
 * artifact that the mojo finds on its own will not resolve to a jar file
 * on its own in the test harness. So we use aether to resolve it, by
 * cloning the maven default artifact into an aether artifact and feeding
 * an artifact request to the repo system obtained by the aether service
 * locator.
 */
private void resolveConfigurationFromRepo(
        DefaultRepositorySystemSession repositorySystemSession,
        MavenProject project)
        throws ArtifactResolutionException, MojoExecutionException {
    org.apache.maven.artifact.Artifact defaultArtifact =
            testMojo.getConfigArtifact();
    Artifact artifact = new DefaultArtifact(
            defaultArtifact.getGroupId(),
            defaultArtifact.getArtifactId(),
            null,
            defaultArtifact.getType(),
            defaultArtifact.getVersion());
    List<RemoteRepository> remoteArtifactRepositories =
            project.getRemoteProjectRepositories();
    DefaultServiceLocator locator =
            MavenRepositorySystemUtils.newServiceLocator();
    locator.addService(RepositoryConnectorFactory.class,
            BasicRepositoryConnectorFactory.class);
    locator.addService(TransporterFactory.class,
            FileTransporterFactory.class);
    locator.addService(TransporterFactory.class,
            HttpTransporterFactory.class);
    RepositorySystem repositorySystem = locator.getService(
            RepositorySystem.class);
    ArtifactRequest artifactRequest = new ArtifactRequest();
    artifactRequest.setArtifact(artifact);
    artifactRequest.setRepositories(remoteArtifactRepositories);
    ArtifactResult result = repositorySystem.resolveArtifact(
            repositorySystemSession, artifactRequest);
    defaultArtifact.setFile(result.getArtifact().getFile());
    testMojo.getLog().debug( "Resolved artifact " + artifact + " to " +
            result.getArtifact().getFile() + " from "
            + result.getRepository() );
}


/**
 * Need manual copy of resources because only parts of the maven lifecycle
 * happen automatically with this test harness.
 */
private void copyTestProjectResourcesToTarget(PlexusContainer container,
                                              MavenProject project,
                                              MavenSession session)
        throws ComponentLookupException, MojoExecutionException {
    Optional<Dependency> resourcesPluginDepOpt =
            project.getDependencies().stream()
                    .filter(d -> Objects.equals(d.getArtifactId(),
                            MAVEN_RESOURCES_ARTIFACT_ID))
                    .findFirst();
    // don't want to define the version here so we read it from what we have
    if (!resourcesPluginDepOpt.isPresent()) {
        throw new MojoExecutionException("Require " +
                MAVEN_RESOURCES_ARTIFACT_ID);
    }
    Plugin resourcePlugin = MojoExecutor.plugin(
            MojoExecutor.groupId(MAVEN_PLUGINS_GROUP_ID),
            MojoExecutor.artifactId(MAVEN_RESOURCES_ARTIFACT_ID),
            MojoExecutor.version(resourcesPluginDepOpt.get().getVersion()));
    MojoExecutor.executeMojo(resourcePlugin,
            MojoExecutor.goal("resources"),
            MojoExecutor.configuration(),
            MojoExecutor.executionEnvironment(
                    project, session,
                    container.lookup(BuildPluginManager.class)));
}

以下是使用的软件包,使用正确软件包中的类非常重要但很容易混淆:

import org.apache.maven.DefaultMaven;
import org.apache.maven.Maven;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.DefaultMavenExecutionResult;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionRequestPopulator;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transport.file.FileTransporterFactory;
import org.eclipse.aether.transport.http.HttpTransporterFactory;

答案 1 :(得分:0)

MavenProject的maven源代码中的一些注释表示

  

随着3.2.2发布期间的变化,MavenProject在构建之后更接近于不可变,并且从该类中删除了所有组件,并且前期构造完全由@ {ProjectBuilder}完成。仍然存在必须运行生命周期才能找到所有编译源根目录和资源目录的问题,但我希望在Maven 4.0版本(jvz)中处理这个问题。

我认为这整个maven插件集成测试的东西在那之前不会起作用......所以环顾四周,我找到了a great blog entry on invoking plugins。所以我直接调用了maven-resources-plugin来让它复制它的意图。这就是copyTestProjectResourcesToTarget()电话的作用。

private void copyTestProjectResourcesToTarget(PlexusContainer container,
                                              MavenProject project,
                                              MavenSession session)
        throws ComponentLookupException, MojoExecutionException {
    logger.fine("generateConfig dependencies: ");
    project.getDependencies().forEach(d -> logger.fine(d.getArtifactId()));
    Optional<Dependency> resourcesPluginDepOpt =
            project.getDependencies().stream()
                    .filter(d -> Objects.equals(d.getArtifactId(),
                            MAVEN_RESOURCES_ARTIFACT_ID))
                    .findFirst();
    // don't want to define the version here so we read it from what we have
    if (!resourcesPluginDepOpt.isPresent()) {
        throw new MojoExecutionException("Require " +
                MAVEN_RESOURCES_ARTIFACT_ID);
    }
    Plugin resourcePlugin = MojoExecutor.plugin(
            MojoExecutor.groupId(MAVEN_RESOURCES_GROUP_ID),
            MojoExecutor.artifactId(MAVEN_RESOURCES_ARTIFACT_ID),
            MojoExecutor.version(resourcesPluginDepOpt.get().getVersion()));
    MojoExecutor.executeMojo(resourcePlugin,
            MojoExecutor.goal("resources"),
            MojoExecutor.configuration(),
            MojoExecutor.executionEnvironment(
                    project, session,
                    container.lookup(BuildPluginManager.class)));
}

答案 2 :(得分:0)

不需要那么复杂。

<块引用>

我不能使用 MojoRule,因为 JUnit5 不再使用 @Rules。

从 3.2 版开始,您不需要使用 @MojoRule,7 年前。只需按照以下三个步骤操作:

  1. 你的测试类应该扩展 AbstractMojoTestCase

  2. 在测试之前,调用 super.setUp()

  3. 查找您的魔力:

    MyMojo myMojo = (MyMojo) super.lookupMojo("myGoal", "src/test/resources/its/my-test-mojo.pom.xml");

有了它,您就可以在没有开销的情况下使用 Junit 5、Mockito 等。