使用TestContainers进行容器Docker测试 – Emmanouil


Testcontainers是一个Java库,支持JUnit测试,它提供了常见的数据库,Selenium Web浏览器或其他可以在Docker容器中运行的轻型的一次性实例。
假设我们在本教程中使用maven:

<properties>
    <junit-jupiter.version>5.4.2</junit-jupiter.version>
    <testcontainers.version>1.15.0</testcontainers.version>
</properties>
 
<dependencies>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>testcontainers</artifactId>
        <version>${testcontainers.version}</version>
        <scope>test</scope>
    </dependency>
 
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>${testcontainers.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

这里将使用Hoverfly作为被测试的案例。
可以通过使用Java运行Hoverfly或在Hoverfly容器中预加载测试用例。这里使用测试Hoverfly容器

package com.gkatzioura.hoverfly.docker;
 
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
 
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
 
@Testcontainers
public class ContainerBasedSimulation {
 
    private static final String SIMULATION_HOST_PATH = ContainerBasedSimulation.class.getClassLoader().getResource("simulation.json").getPath();
 
    @Container
    public static GenericContainer gcs = new GenericContainer(
"spectolabs/hoverfly")
            .withExposedPorts(8888)
            .withExposedPorts(8500)
            .withCommand(
"-webserver","-import","/var/hoverfly/simulation.json")
            .withClasspathResourceMapping(
"simulation.json","/var/hoverfly/simulation.json" ,BindMode.READ_ONLY);
 
 
    @Test
    void testHttpGet() {
        var hoverFlyHost = gcs.getHost();
        var hoverFlyPort = gcs.getMappedPort(8500);
        var client = HttpClient.newHttpClient();
        var request = HttpRequest.newBuilder()
                .uri(URI.create(
"http://"+hoverFlyHost+":"+ hoverFlyPort +"/user"))
                .build();
        var res = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .join();
        Assertions.assertEquals(
"{\"username\":\"test-user\"}",res);
    }
 
}

讲解如下:
Jupiter集成需要@Testcontainers批注。
@Testcontainers
public class ContainerBasedSimulation {
}

这时需要使用的容器的镜像还没有加载,这时使用GenericContainer :
@Container
public static GenericContainer gcs = new GenericContainer("spectolabs/hoverfly")

由于我们要将模拟应用加载到容器,因此需要从主机上设置模拟路径。通过使用withClasspathResourceMapping,我们可以直接在类路径中指定文件,例如测试资源。

.withClasspathResourceMapping("simulation.json","/var/hoverfly/simulation.json",BindMode.READ_ONLY);

Hoverfly需要暴露模拟和管理端口,因此我们将指示Testcontainer暴露那些端口并将其映射到主机。
new GenericContainer("spectolabs/hoverfly")
            .withExposedPorts(8888)
            .withExposedPorts(8500)

我们需要在容器上放置一个模拟。通过使用withFileSystemBind,我们可以指定本地路径和容器上的路径。
...
.withFileSystemBind(SIMULATION_HOST_PATH,"/var/hoverfly/simulation.json" ,BindMode.READ_ONLY)
...

此外,docker映像可能还需要一些其他命令,因此我们将使用.withCommand来传递所需的命令。
...
.withCommand("-webserver","-import","/var/hoverfly/simulation.json")
...

从技术上讲,我们可以说已经准备好连接到容器,但是在运行测试容器时,这时外部正常访问不能通过指定用于绑定的端口访问该容器,因为如果测试并行运行,将会发生冲突。因此,测试容器要做的就是将容器的裸露端口映射到随机的本地端口。这样避免了端口冲突。
@Test
void testHttpGet() {
    var hoverFlyHost = gcs.getHost();
    var hoverFlyPort = gcs.getMappedPort(8500);
    var client = HttpClient.newHttpClient();
    var request = HttpRequest.newBuilder()
            .uri(URI.create("http://"+hoverFlyHost+":"+ hoverFlyPort +"/user"))
            .build();
    var res = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .join();
    Assertions.assertEquals(
"{\"username\":\"test-user\"}",res);
}

使用GenericContainer.getMappedPort(8500),我们可以获取用于与容器交互的端口。另外,getHost()也是必不可少的,因为它并不总是直接指向localhost。
最后:

docker ps 
>04a322447226        testcontainers/ryuk:0.3.0   "/app"