【问题标题】:Spring Boot - How to start my Spring Boot Application from sister module in a multi-module project?Spring Boot - 如何从多模块项目中的姊妹模块启动我的 Spring Boot 应用程序?
【发布时间】:2018-12-09 13:07:54
【问题描述】:

我有一个包含两个项目的多模块项目:backend 和 client。后端是一个普通的 Spring Boot Rest API,没什么特别的。 client 模块只是一个使用 Rest API 的 Java 库。

后端也使用JSP封装了“war”作为后端,需要部署到servlet容器中。使用@SpringBootTest 仍然可以轻松测试后端。

现在我想在 client 模块中使用后端模块作为沙箱服务器进行一些集成测试。

要使用我添加的客户端模块中的所有后端类

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <configuration>
    <attachClasses>true</attachClasses>
  </configuration>
</plugin>

并将后端配置为带有类的客户端中的测试依赖项

在我的 client/src/test/java 中,我有一个帮助类来启动后端模块

@Configuration  
public class SandboxServer {

  @Bean
  public ConfigurableApplicationContext backend() {
    return 
      new SpringApplicationBuilder(BackendApplication.class)
      .sources(SandboxServerConfig.class)
      .run("spring.profiles.active=sandbox")
  }
}

配置文件“沙盒”用于设置测试数据库等。但我遇到了更多问题。第一个问题是关于文档根目录,所以我配置了它:

public class SandboxServerConfig 
  implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
  @Override
  public void customize(TomcatServletWebServerFactory factory) {
    factory.setDocumentRoot(new File("../backend/src/main/webapp"));
  }
}

但它仍然不起作用,因为 Spring 没有拾取 backend/src/main/resources/application.properties

这可能是正确的,因为它不在客户端模块的根类路径中。

所以它并没有真正起作用。我想在集成测试中只启动兄弟模块是不可能的。

如何实现启动兄弟spring boot模块进行集成测试?像这样的场景的最佳做法是什么?

【问题讨论】:

  • 您决定部署到单独的 servlet 容器而不是使用嵌入式服务器的具体原因是什么——这是 Spring Boot 应用程序的推荐部署机制。似乎这个决定让你陷入了困境,让你有一些非常奇怪的解决方法。
  • 它是一个嵌入式服务器。但是它使用了一些 JSP 页面,所以我还有一个 src/main/webapp 文件夹。
  • 如果它是一个嵌入式服务器,那么整个.war 的东西完全是假的。 .war` 部署在外部 servlet 上。

标签: java spring spring-boot testing integration-testing


【解决方案1】:

您可以像这样使用TestPropertySource 覆盖application.properties 位置:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BlaApplication.class)
@TestPropertySource(locations="/path/to/backend/src/main/resources/application.properties")
public class ExampleApplicationTests {

}

【讨论】:

  • 好主意,但设置 TestProperties 会影响测试本身。我试了一下,但感觉就像把它搞砸了。
【解决方案2】:

我找到了一个更可靠的解决方案。在我的兄弟项目“前端”中,我有一个组件以集成模式启动后端服务器,当且仅当它尚未运行时。

好处:

  • 真正的WAR经过测试
  • 您可以先在 IDE 中启动 WAR,然后让测试快速运行
  • 如果你用 maven 运行它,它只会在所有测试之前启动一次
  • 无需构建配置(如 maven 中的预集成)
  • 进程与 Junit 运行时分离,因此无需繁琐的设置。

缺点:

  • 您需要先构建包,然后才能在前端运行任何集成测试。但是,嘿,你应该在测试之前构建你的包。这就是集成测试的全部意义所在。

这是我的 SandboxServerProcess.class。

import org.springframework.stereotype.Component;   
import javax.annotation.*;
import javax.annotation.*;
import java.io.*;
import java.net.*;
import java.util.*;

@Component
@Profile("integration")
public class SandboxServerProcess {
    private static final String WAR = "../backend/target/backend.war";
    private final static int PORT = 8081;
    private boolean startedByMe;

    @PostConstruct
    public void start() throws Exception {
        if (isStarted()) {
            return;
        }
        testWarExists();
        packagedWar("start");
        if (waitForStartup()) {
            startedByMe = true;
            return;
        }
        throw new RuntimeException("Sandbox Server not started");
    }

    private void testWarExists() {
        File file = new File(WAR);
        if (!file.exists()) {
            throw new RuntimeException("WAR does not exist:" + file.getAbsolutePath());
        }
    }

    @PreDestroy
    public void stop() throws IOException {
        if (startedByMe) {
            packagedWar("stop");
        }
    }

    private void packagedWar(String command) throws IOException {
        ProcessBuilder builder = new ProcessBuilder();
        builder.environment().put("MODE", "service");
        builder.environment().put("SPRING_PROFILES_ACTIVE", "integration");
        builder.environment().put("APP_NAME", "backend");
        builder.environment().put("PID_FOLDER", "./");
        builder.environment().put("LOG_FOLDER", "./");
        List<String> commands = new ArrayList<>();
        commands.add(WAR);
        commands.add(command);
        builder.command(commands);
        builder.inheritIO();
        builder.redirectErrorStream(true);
        builder.start();
    }

    private boolean isStarted() {
        try {
            Socket socket = new Socket();
            InetSocketAddress sa = new InetSocketAddress("localhost", PORT);
            socket.connect(sa, 500);
            logger.warn("SandboxServer is started");
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    private boolean waitForStartup() throws InterruptedException {
        for (int i = 1; i < 30; i++) {
            if (isStarted()) {
                return true;
            }
            logger.warn("SandboxServer not yet ready, tries: " + i);
            Thread.sleep(1000);
        }
        return false;
    }
}

【讨论】:

  • 哎呀。这是比较恶心的。为什么不直接使用@SpringBootTest 甚至SpringApplication.run?
猜你喜欢
  • 2018-10-07
  • 2015-10-10
  • 1970-01-01
  • 2021-01-01
  • 2019-05-24
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 2016-03-27
相关资源
最近更新 更多