Spring 引导 - 如何从多模块项目中的姐妹模块启动我的 Spring 引导应用程序?
Spring Boot - How to start my Spring Boot Application from sister module in a multi-module project?
我有一个包含两个项目的多模块项目:backend 和 client。后端是正常的 Spring Boot Rest API,没什么特别的。 client 模块只是一个使用 Rest API 的 Java 库。
后端有"war"的封装作为后端,因为它也使用了JSP,需要部署到一个servlet容器中。使用@SpringBootTest.
仍然可以轻松测试后端
现在我想在 client 模块中使用后端模块作为沙箱服务器进行一些集成测试。
要使用我添加的客户端模块中的所有后端 classes
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
并使用 classes
将后端配置为客户端中的测试依赖项
在我的 client/src/test/java 中,我有一个助手 class 可以启动后端模块
@Configuration
public class SandboxServer {
@Bean
public ConfigurableApplicationContext backend() {
return
new SpringApplicationBuilder(BackendApplication.class)
.sources(SandboxServerConfig.class)
.run("spring.profiles.active=sandbox")
}
}
配置文件"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
这可能是正确的,因为它不在客户端模块的根 class 路径中。
所以它并没有真正起作用。我想不可能只在集成测试中启动兄弟模块。
如何实现启动兄弟spring引导模块进行集成测试?像这样的场景的最佳实践是什么?
您可以像这样使用 TestPropertySource 覆盖 application.properties
位置:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BlaApplication.class)
@TestPropertySource(locations="/path/to/backend/src/main/resources/application.properties")
public class ExampleApplicationTests {
}
我找到了一个更可靠的解决方案。在我的同级项目 "frontend" 中,我有一个组件以集成模式启动后端服务器当且仅当它尚未 运行ning.
好处:
- 实测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;
}
}
我有一个包含两个项目的多模块项目:backend 和 client。后端是正常的 Spring Boot Rest API,没什么特别的。 client 模块只是一个使用 Rest API 的 Java 库。
后端有"war"的封装作为后端,因为它也使用了JSP,需要部署到一个servlet容器中。使用@SpringBootTest.
仍然可以轻松测试后端现在我想在 client 模块中使用后端模块作为沙箱服务器进行一些集成测试。
要使用我添加的客户端模块中的所有后端 classes
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
</configuration>
</plugin>
并使用 classes
将后端配置为客户端中的测试依赖项在我的 client/src/test/java 中,我有一个助手 class 可以启动后端模块
@Configuration
public class SandboxServer {
@Bean
public ConfigurableApplicationContext backend() {
return
new SpringApplicationBuilder(BackendApplication.class)
.sources(SandboxServerConfig.class)
.run("spring.profiles.active=sandbox")
}
}
配置文件"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
这可能是正确的,因为它不在客户端模块的根 class 路径中。
所以它并没有真正起作用。我想不可能只在集成测试中启动兄弟模块。
如何实现启动兄弟spring引导模块进行集成测试?像这样的场景的最佳实践是什么?
您可以像这样使用 TestPropertySource 覆盖 application.properties
位置:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BlaApplication.class)
@TestPropertySource(locations="/path/to/backend/src/main/resources/application.properties")
public class ExampleApplicationTests {
}
我找到了一个更可靠的解决方案。在我的同级项目 "frontend" 中,我有一个组件以集成模式启动后端服务器当且仅当它尚未 运行ning.
好处:
- 实测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;
}
}