常见问题:我正在一家具有面向服务的体系结构的大公司中测试Web应用程序。外部服务通常会由于背景噪音而在我们的测试环境中失败。这将阻止对我们服务的集成测试正常运行,因为除非成功调用这些外部服务,否则我们的服务将无法正常工作。因此,我们希望能够模拟来自外部服务的响应,这样我们就不必依赖它们,而可以隔离地测试我们自己的服务。

我们希望使用一个名为Mockey的工具。这是一个Java程序,它通过嵌入式Jetty服务器运行,并充当服务调用的代理。我们的Web应用程序已重新配置为调用Mockey,而不是外部服务。然后,将Mockey配置为根据传入的URL和标头数据为这些调用提供动态模拟的响应。

为了利用此工具,我们希望能够在Maven生命周期的预集成测试阶段启动Mockey,以便在集成测试阶段可以使用它。

特定问题:为了在Maven生命周期的集成前测试阶段和集成后测试阶段启动和关闭Mockey,我编写了一个名为mockey-maven-plugin的Maven 3插件:

mockey-maven-plugin pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mycompany.mockey</groupId>
    <artifactId>mockey-maven-plugin</artifactId>
    <packaging>maven-plugin</packaging>
    <version>1.3</version>

    <dependencies>

        <!-- Maven plugin dependencies -->
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.4</version>
            <scope>provided</scope>
        </dependency>

        <!-- Mockey dependency -->
        <dependency>
            <groupId>com.mycompany.mockey</groupId>
            <artifactId>Mockey</artifactId>
            <version>1.16.2015</version>
        </dependency>

    </dependencies>

    <build>

        <plugins>

            <!-- This plugin is used to generate a plugin descriptor
                 xml file which will be packaged with the plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.4</version>
            </plugin>

        </plugins>

    </build>

</project>


mockey-maven-plugin StartMockey类:

@Mojo(name="start-mockey")
@Execute(phase= LifecyclePhase.PACKAGE) // Not sure about this annotation
public class StartMockey extends AbstractMojo
{
    /**
     * Flag which controls Mockey startup.
     */
    @Parameter(property="mockey.skipStartup", defaultValue="false", required=true)
    private Boolean skipStartup;

    // Do I need these getters and setters or does Maven ignore them?

    public Boolean getSkipStartup()
    {
        return skipStartup;
    }

    public void setSkipStartup(Boolean skipStartup)
    {
        this.skipStartup = skipStartup;
    }

    // *SNIP* Defining Mockey parameters...

    // Maven will call this method to start the mockey-maven-plugin
    public void execute()
    {
        if(skipStartup)
        {
            getLog().info("Skipping Mockey startup");
            return;
        }

        getLog().info("Starting Mockey");

        // Load specified parameters into array
        List<String> argsList = new ArrayList<>();

        // *SNIP* Adding Mockey parameters to argList...

        String[] args = new String[argsList.size()];
        argsList.toArray(args);

        // Start Mockey with specified parameters and wait for it to return
        try
        {
            JettyRunner.main(args);
        }
        catch(Exception e)
        {
            getLog().error("Mockey died... :(");
        }
        getLog().info("mockey-maven-plugin now exiting");
    }
}


mockey-maven-plugin ShutdownMockey类:

@Mojo(name="shutdown-mockey")
public class ShutdownMockey extends AbstractMojo
{
    /**
     * Flag which controls Mockey shutdown.
     */
    @Parameter(property="mockey.skipShutdown")
    private Boolean skipShutdown;

    // Again, Do I need these getters and setters or does Maven ignore them?

    public Boolean getSkipShutdown()
    {
        return skipShutdown;
    }

    public void setSkipShutdown(Boolean skipShutdown)
    {
        this.skipShutdown = skipShutdown;
    }

    public void execute()
    {
        if(skipShutdown)
        {
            getLog().info("Skipping Mockey shutdown");
            return;
        }
        getLog().info("Shutting down Mockey");
        JettyRunner.stopServer();
        getLog().info("mockey-maven-plugin now exiting");
    }
}


我的团队项目的pom.xml文件中的mokey-maven-plugin的插件条目:

<plugin>
    <groupId>com.mycompany.mockey</groupId>
    <artifactId>mockey-maven-plugin</artifactId>
    <version>1.3</version>
    <configuration>
        <skipShutdown>${keepMockeyRunning}</skipShutdown>
        <skipStartup>${skipMockey}</skipStartup>

        <!-- *SNIP* Other Mockey parameters... -->

    </configuration>
    <executions>
        <execution>
            <id>start-mockey</id>
            <goals>
                <goal>start-mockey</goal>
            </goals>
            <phase>pre-integration-test</phase>
        </execution>
        <execution>
            <id>shutdown-mockey</id>
            <goals>
                <goal>shutdown-mockey</goal>
            </goals>
            <phase>post-integration-test</phase>
        </execution>
    </executions>
</plugin>


该插件可以在集成前测试阶段启动Mockey,但在Mockey退出之前一直阻止构建。我不确定为什么会这样,因为我专门添加了此注释来防止该问题:

 @Execute(phase= LifecyclePhase.PACKAGE)


我实际上是从另一个插件复制了此批注的,该插件正是我在这里所做的(我们使用maven-tomcat7-plugin在集成前测试阶段在本地启动我们的Web应用程序,并在后置测试中将其关闭集成测试阶段)。我以为这将以相同的方式工作,但是我看到了不同的行为。

这是我想看到的情况:


Maven构建从单个线程开始。
该线程贯穿从验证到程序包(reference)的所有生命周期阶段,并执行所有与这些阶段绑定目标的插件。
线程进入集成前测试阶段,看到嘲笑Maven插件的start-mockey目标已绑定到集成前测试阶段,并尝试执行start-mockey目标。
注释start-mockey目标可以在第二个线程(而不是第一个线程)上执行,而不必在新线程上事先或之后在任何其他生命周期阶段运行任何其他目标。第二个线程通过调用JettyRunner.main(args)启动Mockey的Jetty服务器,并暂时在该方法上阻止(它正在运行Mockey)。
第一个线程继续进行其他目标和阶段(即:运行集成测试)。
第一个线程进入集成后测试阶段,看到mathey-maven-plugin的shutdown-mockey目标绑定到集成后测试阶段,并执行shutdown-mockey目标。
shutdown-mockey目标将调用JettyRunner.stopServer(),它会挂接到JettyRunner类内部的静态对象中,并向第一个线程发出信号以关闭Jetty。同时,第一个线程等待第二个线程发出的信号(或者可能是轮询,我不太清楚),表明Jetty已关闭。
第二个线程完成关闭Jetty的操作,向第一个线程发出信号,表明它可以继续进行并杀死自己。
第一个线程继续进行任何其他目标和Maven生命周期阶段。


这是我实际看到的情况:


Maven构建从单个线程开始。
该线程贯穿从验证到程序包(reference)的所有生命周期阶段,并执行所有与这些阶段绑定目标的插件。
线程进入集成前测试阶段,看到嘲笑Maven插件的start-mockey目标已绑定到集成前测试阶段,并尝试执行start-mockey目标。
注释start-mockey目标以在第二个线程上执行。第二个线程从验证阶段开始重新启动整个Maven生命周期。
第一个线程在等待第二个线程退出时阻塞。
第二个线程一直贯穿整个打包阶段,然后终止自身。
第一个线程被解除阻塞,并在中断的地方继续执行。它自己执行start-mockey目标(永远不会由第二个线程运行)。这将调用JettyRunner.main(args),然后线程在运行Mockey的Jetty服务器时阻塞。
直到Jetty服务器被手动杀死(以及Maven生命周期的其余部分),线程才保持阻塞状态。


这主要使我感到困惑,因为Maven似乎与我所熟悉的概念不同。对我而言,分叉意味着在某个特定点发散,而不是重新开始,并且不影响原始过程。当我们在Unix中派生一个进程时,它会复制第一个进程的堆栈和函数指针。它不会从程序的开头重新开始。同样,在派生代码存储库时,我们将从原始存储库中当前存在的所有文件和目录开始。我们不会从空白开始。那么,为什么当我们“分叉” Maven生命周期时,它会放弃一切,重新开始并阻塞原始线程?这似乎根本不像我分叉。这是我阅读的一些文档,它们描述了Maven中的“分叉”:


Running the ZipForkMojo will fork the lifecycle
Any plugin that declares @execute [phase] will cause the build to fork
goal=goal to fork... lifecycle=lifecycle id to fork... phase=lifecycle phase to fork...


剩余的问题:


在我熟悉的意义上,如何让Maven分叉?
将Maven生命周期分叉到要分叉的阶段之前意味着什么?例如,从集成前测试阶段分叉到封装阶段意味着什么?
您为什么认为Tomcat7插件正在执行此操作(从集成前测试阶段分叉到软件包阶段)?
导致相同注释在我的插件中行为不同的Tomcat7插件有什么不同?


回答的问题(见下文):
-是否需要在插件的注释中指定另一个阶段,以使其按预期运行,还是应该以根本不同的方式使用execute注释?

最佳答案

https://books.sonatype.com/mvnref-book/reference/writing-plugins-sect-plugins-lifecycle.html

该文档似乎表明您应该创建仅包含start-mockey目标的自定义生命周期。然后,您的@Execute批注应指定目标和生命周期。那应该分派执行,但只能执行您的start-mockey。我认为您可以正常运行End-mockey。

09-11 21:16