在 JUnit 测试之间启动和停止 Jetty 服务器

11

我想模拟程序的各种运行情况,因此在@Before方法中启动Jetty服务器,并在@After方法中关闭它。

我的第一个测试成功运行,但在随后的测试中,尝试进行POST数据时出现了com.sun.jersey.api.client.ClientHandlerException: java.net.SocketException: Software caused connection abort: recv failed。有没有办法让我的服务器(和客户端?)在测试之间干净地关闭?

我的Before和After代码如下:

@Before
public void startServer() {
    try {
        server = new Server(8080);
        ServletContextHandler root = new ServletContextHandler(server, "/ingest", ServletContextHandler.SESSIONS);

        root.addServlet(new Servlet(), "/*");

        server.start();

        client = new Client();
        client.setChunkedEncodingSize(16 * 1024);

        FileInputStream stream = new FileInputStream(testFile);
        try {
            client.resource(uri).type(MediaType.APPLICATION_OCTET_STREAM).post(stream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            Closeables.closeQuietly(stream);
            client.destroy();
        }
    } catch (Exception e) {
        e.printStackTrace();
        fail("Unexpected Exception when starting up server.");
    }
}

@After
public void shutDown() {
    if (output.exists()) {
        output.delete();
    }
    try {
        server.stop();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
6个回答

8

在测试场景中的最佳实践是不要硬编码端口。这样只会在其他地方运行时导致冲突,尤其是在具有中等负载或各种项目的CI系统上。

在Jetty 9中(6、7、8中的相同思路)

_server = new Server();
_connector = new ServerConnector(_server);  
_server.setConnectors(new Connector[] { _connector });
_server.start();
int port = _connector.getLocalPort();

对于Jetty 6,我必须使用SelectChannelConnector(ServerConnector不存在)。 - Dennie

2
原来我的代码是有效的,但由于server.stop()是异步的,我的新服务器在前一个服务器的关闭线程完全执行之前就尝试实例化了。
server.stop()后简单地加上Thread.sleep(n)可以给服务器所需的关闭时间。不幸的是,服务器似乎会过早地宣称已经停止,从而阻止了通过检查服务器状态获得精确解决方案 - 但也许有什么东西可以轮询服务器; 可能检查线程池可以提供一致的结果?
无论如何,由于这只是用于测试目的,仅在@BeforeClass中启动服务器并在@AfterClass中关闭它即可避免整个服务器关闭混乱,但要注意不要在测试套件中使用相同的端口启动另一个服务器。

你可能只需要使用 server.join() 来等待它停止。 - Andrew Mao

0

我的猜测是它出现了端口冲突。我们实际上为了进行测试而这样做,令人惊讶的是效率损失并不大。一开始我们按照答案所建议的在所有测试之前启动一个单独的服务器,但后来我们不得不切换到支持变异测试。依赖于Maven的一个缺点是你必须在IDE中启动它来运行单个测试。

对于任何有兴趣的人,我们的实现在这里:embedded-test-jetty。它可以同时在不同端口上运行多个服务器(用于并行测试),检查端口可用性,支持SSL等功能。


1
在我看来,如果你认为一个代码解决方案是相关的,没有必要询问是否有人感兴趣,直接发布就好了,不要再问了 : )。如果太长的话,可以放链接到你的仓库。 - bool.dev
我不确定我们何时会将其从我们的内部存储库中移出,但现在它已经上线了 :) - Kyle Winter

0
我使用了一些方法来处理这个问题。首先,在每次测试之后,请确保关闭服务器并在其上调用join()。根据您的需求,可以在@After@AfterClass中执行此操作。
server.stop();
server.join();

接下来,在每个测试之前,请确保端口可用。我使用的代码片段可以在Java中使用套接字发现端口可用性找到。
然后,设置代码变成了:
public static void waitForPort(int port) {
    while( !available(port) ) {
        try { Thread.sleep(PORT_SLEEP_MILLIS); } 
        catch (InterruptedException e) {}
    }
}

@Before
public void setUp() throws Exception {
    waitForPort(9876);
    waitForPort(9877);

    // Make sure the ports are clear
    Thread.sleep(500);
}

在端口可用之前稍微多等待一会儿确保它可用;因为仅仅检查它是否可用可能会使系统不再重用它。另一个选项是,在检查端口后打开它时设置SO_REUSEADDR


-1

尝试:

server = new Server();
    SocketConnector connector = new SocketConnector();
    connector.setPort(8080);
    server.setConnectors(new Connector[] { connector });
    WebAppContext context = new WebAppContext();
    context.setServer(server);
    context.setContextPath("/your-context");
    context.setWar("path to war");
    server.addHandler(context);
    Thread monitor = new MonitorThread();
    monitor.start();
    server.start();
    server.join();

然后你在某个地方说:

    server.stop()

有用的文章:
http://www.codeproject.com/Articles/128145/Run-Jetty-Web-Server-Within-Your-Application


-1

我知道这并没有直接回答你的问题...但是在每个集成测试需要运行服务器时,在@Before@After方法中启动和停止服务器是低效的,因为服务器将在每个测试中重新启动。

您可能希望考虑在整个测试套件周围启动和停止服务器。如果您正在使用Maven进行构建,则可以使用failsafe和Jetty插件的组合来实现此目的。


请参考 http://maven.apache.org/plugins/maven-failsafe-plugin/usage.html 了解如何使用 Jetty 和 Failsafe。 - Kkkev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接