JUnit 使用多个服务运行端到端测试

问题描述 投票:0回答:1

我正在尝试使用 JUnit 和 RestTemplates 运行端到端测试。

我有一个 Spring / Maven 多模块项目,其结构如下:

parent
|-- service-1
|-- service-2
|-- service-3
|-- integration-test

集成测试 POM 如下所示:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>my.project</groupId>
        <artifactId>parent</artifactId>
        <version>${revision}</version>
    </parent>

    <artifactId>integration-test</artifactId>

    <dependencies>
        <dependency>
            <groupId>my.project</groupId>
            <artifactId>service-1</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>my.project</groupId>
            <artifactId>service-2</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>my.project</groupId>
            <artifactId>service-3</artifactId>
            <version>${revision}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

我尝试了以下代码来启动所有服务: (参见此处:https://stackoverflow.com/a/47873906/23364885

    ...

    @BeforeAll
    static void setUp() throws Exception {
        Properties s1p = getProperties("../service-1/src/test/resources/application.yml");
        SpringApplication s1 = new SpringApplicationBuilder(Service1Application.class)
                .properties(s1p).build();
        s1.setAdditionalProfiles("dev");
        s1.run();

        Properties s2p = getProperties("../service-2/test/main/resources/application.yml");
        SpringApplication s2 = new SpringApplicationBuilder(Service2.class)
                .properties(s2p).build();
        s2.setAdditionalProfiles("dev");
        s2.run();

        Properties s3p = getProperties("../service-3/test/main/resources/application.yml");
        SpringApplication s3 = new SpringApplicationBuilder(Service3.class)
                .properties(s3p).build();
        s3.setAdditionalProfiles("dev");
        s3.run();
    }

我的问题是,应用程序正在使用所有服务的完整/混合上下文/依赖项。

例如,服务1正在使用Spring Cloud Gateway。

服务 2 使用 Spring MVC。

现在,服务 1 抛出错误,因为它的类路径上有 Spring MVC。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration$SpringMvcFoundOnClasspathConfiguration': Failed to instantiate [org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration$SpringMvcFoundOnClasspathConfiguration]: Constructor threw exception

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1317)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1202)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:962)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:334)
    at my.package.integration.FullFlowIT.setUp(FullFlow.java:33)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration$SpringMvcFoundOnClasspathConfiguration]: Constructor threw exception
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:221)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:88)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1311)
17 more
Caused by: org.springframework.cloud.gateway.support.MvcFoundOnClasspathException
    at org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration$SpringMvcFoundOnClasspathConfiguration.<init>(GatewayClassPathWarningAutoConfiguration.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:195)
19 more

我无法在我的测试环境中使用docker。我也不想启动 jar 文件(请参见此处:https://stackoverflow.com/a/56694218/23364885)如果我可以无论如何避免它。

当我使用 mvn spring-boot:run 在每个模块目录中“手动”启动所有服务时,所有服务都没有问题启动。

java spring spring-boot junit end-to-end
1个回答
0
投票

不幸的是,我能找到的唯一解决方案是我最初想避免的解决方案。我从这里得到了基本想法:https://stackoverflow.com/a/56694218/23364885

首先,我从集成测试模块扩展了 POM:

<build>
    <plugins>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>**/E2EIntegrationTest</exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

<profiles>
    <profile>
        <id>integrationtest</id>
        <build>
            <plugins>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <executions>
                        <execution>
                            <goals>
                                <goal>test</goal>
                            </goals>
                            <phase>integration-test</phase>
                            <configuration>
                                <excludes>
                                    <exclude>none</exclude>
                                </excludes>
                                <includes>
                                    <include>**/E2EIntegrationTest</include>
                                </includes>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

例如,具有集成测试的类仅在使用集成测试配置文件进行验证的 Maven 目标上执行。例如:

mvn clean verify -Pintegrationtest

通过目标验证,各个 jar 已经构建完毕,这就是我在 E2EIntegrationTest 类中启动/停止它们的原因,如下所示:

@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class E2EIntegrationTest {

    private static final String[] services = {"authorization-server", "devproxy", "portal", "gateway"};

    private static Process[] instances;

    @BeforeAll
    static void setUp() throws IOException, InterruptedException {
        // Find all Executables
        String baseDir = new File(".").getCanonicalFile().getAbsolutePath();
        log.info("Basedir is: {}", baseDir);

        // Fix if started from Parent Folder
        if (baseDir.contains("integration-test"))
            baseDir = new File(".").getCanonicalFile().getParentFile().getAbsolutePath();

        log.info("Basedir is: {}", baseDir);
        File[] jarFiles = new File[services.length];
        instances = new Process[services.length];
        for (int i = 0; i < services.length; i++) {
            FileFilter fileFilter = new WildcardFileFilter("*.jar"); // ToDo Fix
            File jarDir = new File(baseDir + "\\" + services[i] + "\\target\\");
            File[] files = jarDir.listFiles(fileFilter);
            assert files != null;
            jarFiles[i] = Arrays.stream(files)
                    .filter(f -> !f.getAbsolutePath().contains("javadoc"))
                    .filter(f -> !f.getAbsolutePath().contains("sources"))
                    .findFirst()
                    .orElseThrow();
            log.info(
                    "Found for Service {} Jar File {}, executable {}",
                    services[i],
                    jarFiles[i].getAbsolutePath(),
                    jarFiles[i].canExecute());
        }

        log.info("Checkpoint: All Jar-Files found!");

        // Start all Services
        for (int i = 0; i < services.length; i++) {
            String command =
                    "java -Dspring.profiles.active=dev -jar %s --spring.datasource.url=jdbc:h2:file:./target/e2e-test;AUTO_SERVER=true;Mode=Oracle --spring.jpa.hibernate.ddl-auto=create-drop"
                        .formatted(jarFiles[i]);
            log.info("Starting Service {} with command {}", services[i], command);
            instances[i] = Runtime.getRuntime().exec(command);
            Executors.newSingleThreadExecutor().submit(new ProcessStdOutPrinter(services[i], instances[i]));
            Thread.sleep(5000);
        }
        log.info("Checkpoint: All Services started!");
    }

    @AfterAll
    static void tearDown() {
        for (int i = 0; i < services.length; i++) {
            log.info("Stopping Service {}", services[i]);
            instances[i].destroy();
        }
    }

    // Test Methods ...

}

要查看各个服务的日志,我使用了以下帮助程序类:

public class ProcessStdOutPrinter implements Runnable {
    private final InputStream inputStream;
    private final String serviceName;

    public ProcessStdOutPrinter(String serviceName, Process process) {
        this.serviceName = serviceName;
        this.inputStream = process.getInputStream();
    }

    @Override
    public void run() {
        new BufferedReader(new InputStreamReader(inputStream))
                .lines()
                .forEach(l -> System.out.printf("%s\t: %s%n", serviceName, l));
    }
}

这不是最佳解决方案,但它有效。

© www.soinside.com 2019 - 2024. All rights reserved.