docker和服务器集成_与docker和testcontainers进行集成测试

暴奕
2023-12-01

docker和服务器集成

We, as good developers, create unit tests for every piece of code we write, so we can be confident to iterate on the code. Unit tests are fast, reliable and relatively easy to write and maintain. Integration testing on the other hand, is not a solved issue. I know this from personal experience.

作为优秀的开发人员,我们为编写的每段代码创建单元测试,因此我们可以自信地遍历代码。 单元测试快速,可靠并且相对易于编写和维护。 另一方面,集成测试不是解决的问题。 我从个人经验中知道这一点。

My team and I had been building a dashboard and some backend services for it. But as we started to shape our testing pyramid, we realized that most of our code is integration code: we didn’t have our own database (at least, not yet) and we got our data from a third-party service. We developed tons of tests that emulated the responses of that service, but we also needed to ensure our services would get the right data when talking to the real service, especially because we tell it how to query the database.

我和我的团队一直在构建仪表板和一些后端服务。 但是,当我们开始塑造测试金字塔时,我们意识到我们的大多数代码都是集成代码:我们没有(至少现在还没有)自己的数据库,并且我们从第三方服务获取了数据。 我们开发了许多测试来模拟该服务的响应,但是我们还需要确保在与真实服务进行对话时,我们的服务能够获取正确的数据,尤其是因为我们告诉它如何查询数据库。

Nowadays, there are many more ways to build applications than there used to be in the past. Applications are now made of micro services that speak with each other and there are a lot of integration points to be tested. Shaping our testing pyramid has become quite challenging because we don’t want our build to take forever to run. We want fast and reliable feedback and we also don’t want a complex setup. But at the same time, we need to ensure our services will work when integrating with other services.

如今,构建应用程序的方式比过去更多。 现在,应用程序由相互对话的微服务组成,并且有很多集成点需要测试。 塑造我们的测试金字塔变得非常具有挑战性,因为我们不希望我们的构建永久运行。 我们需要快速而可靠的反馈,我们也不需要复杂的设置。 但是同时,我们需要确保我们的服务在与其他服务集成时能够正常工作。

码头工人 (Docker)

This is where Docker steps in. Docker is a free software for launching applications in software containers. Containers are a standardized unit of software that allows developers to isolate their app from its environment, solving the “it works on my machine” headache.

这就是Docker介入的地方。Docker是一个免费软件,用于在软件容器中启动应用程序。 容器是软件的标准化单元,它使开发人员可以将其应用程序与环境隔离开来,从而解决“在我的机器上工作”的难题。

You can run anything in a Docker container, which makes them very ideal for integration testing. Using Docker containers, you can create throwaway instances of anything you need, including your external dependencies.

您可以在Docker容器中运行任何东西,这使其非常适合进行集成测试。 使用Docker容器,您可以创建任何需要的扔掉的实例,包括外部依赖项。

Throughout this post, I will use Redis as the “external dependency” for all the examples. Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker.

在本文中,我将使用Redis作为所有示例的“外部依赖项”。 Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。

Docker CLI (Docker CLI)

Assuming you have Docker installed, to run the Redis container via Docker cli you can simply open a terminal and run:

假设您已经安装了Docker,要通过Docker cli运行Redis容器,您只需打开一个终端并运行:

docker run -p 6379:6379 redis

This will expose and publish Redis on port 6379 on your machine and, because you know what port Redis will be running on, you can reach it from your tests.

这将在计算机上的端口6379上公开和发布Redis,并且由于您知道Redis将在哪个端口上运行,因此可以通过测试来访问它。

So something like this would be possible:

所以这样的事情是可能的:

$ docker run -p 6379:6379 redis
$ run-integration-tests

That’s easy right? Why would you need any more than that?

那很容易吧? 为什么您还需要更多呢?

Here are some things you need to be aware of:

您需要注意以下几点:

  • Orchestration — how do you know when Redis will be ready to receive traffic after you start it? You’d need some kind of health check and waiting strategy.

    编排-您如何知道Redis启动后何时准备接收流量? 您需要某种健康检查和等待策略。
  • Parallelism — Since port 6379 will be allocated, you cannot run multiple tests in parallel. You could choose not to expose that specific port and the Docker daemon would automatically bind it to a random free port on your machine, but then you wouldn’t know what port to connect to from your tests to interact with Redis.

    并行性-由于将分配端口6379,因此您无法并行运行多个测试。 您可以选择不公开该特定端口,而Docker守护进程会自动将其绑定到计算机上的随机空闲端口,但是您将不知道要从测试连接到哪个端口以与Redis进行交互。
  • Test lifecycle — normally you would want to recreate the container in between tests to have an isolated and clean environment for each test scenario.

    测试生命周期-通常,您需要在两次测试之间重新创建容器,以为每个测试方案提供隔离和干净的环境。
  • Cleanup — You need to stop and remove the container after your tests run.

    清理-运行测试后,您需要停止并卸下容器。

Docker API (Docker API)

Docker has an API and several clients. You can connect to the API with the language of your choice and use the corresponding client to do everything you want with Docker (just like you do via cli, but from code).

Docker有一个API和几个客户端。 您可以使用您选择的语言连接到API,并使用相应的客户端来完成Docker所需的一切(就像通过cli一样,但从代码中进行)。

This makes it easy to automate the Docker setup and deployment and integrate it with test code to create an easy setup of the dev environment, uniform build and test environments (self-contained and portable). A bonus is that it requires no installation of external software, except Docker of course.

这使得自动化Docker安装和部署以及将其与测试代码集成在一起变得容易,从而可以轻松设置dev环境,统一的构建和测试环境(独立且可移植)。 一个好处是,除了Docker,它不需要安装任何外部软件。

That’s very convenient. But an even better news is that someone already had the idea of interacting with Docker containers from test code and created Testcontainers.

那很方便。 但是一个更好的消息是,已经有人想到了通过测试代码与Docker容器进行交互并创建Testcontainer的想法

测试容器 (Testcontainers)

Testcontainers is a library for instrumenting Docker containers to use them as part of your tests. It uses the Docker API to provide lightweight, throwaway instances of common databases, selenium web drivers, or anything that you can run in a Docker container. It’s test-friendly, it’s open-source and it’s available in multiple languages.

Testcontainers是一个用于检测Docker容器以将其用作测试一部分的库。 它使用Docker API提供常见数据库,Selenium Web驱动程序或您可以在Docker容器中运行的任何对象的轻量,一次性的实例。 它易于测试,是开源的,并且支持多种语言

Here is an example of how easy it is to get a container up and running and ready to run your tests using testcontainers-java:

这是一个使用testcontainers-java来启动并运行容器并准备运行测试的过程的简单示例

GenericContainer redis = new GenericContainer("redis:5.0.8-alpine3.11").withExposedPorts(6379);redis.start();// run your testsredis.stop();

This is just a simple example of creating an instance of a container that you can interact with, but there are already some interesting things going on here:

这只是创建可以与之交互的容器实例的简单示例,但是这里已经发生了一些有趣的事情:

Testcontainers will automatically pull the Redis image if necessary.

如有必要,测试容器将自动拉出Redis图像。

The start command is a blocking command, which means that it will wait until the application inside the container is ready. By default, it will wait for the container’s mapped network port to start listening. Of course, readiness can mean different things in different applications, that’s why there are other specific wait strategies that can be used with Testcontainers, but the default behavior should be already enough for most applications.

start命令是一个阻止命令,这意味着它将等待直到容器内的应用程序准备就绪。 默认情况下,它将等待容器的映射网络端口开始侦听。 当然,准备就绪在不同的应用程序中可能意味着不同的事情,这就是为什么可以在Testcontainer中使用其他特定的等待策略的原因,但是默认行为对于大多数应用程序来说已经足够了。

The stop command will shut down and delete the container after the test.

stop命令将在测试后关闭并删除容器。

端口绑定和API (Port binding and API)

We usually don’t want to publish to a specific port on the host, to avoid port collisions with locally running software or in between parallel test runs, so we let the Docker decide on which port to publish.

我们通常不希望发布到主机上的特定端口,以避免与本地运行的软件或在并行测试运行之间发生端口冲突,因此我们让Docker决定发布哪个端口。

Since we don’t know the port Docker will pick, Testcontainers has additional APIs to get the actual mapped port after the container starts, so we can inject it into our tests and use it.

由于我们不知道Docker将选择哪个端口,因此,在容器启动后,Testcontainers拥有其他API来获取实际的映射端口,因此我们可以将其注入测试中并使用它。

This can be done using the getMappedPort method, which takes the original (container) port as an argument, so for the Redis generic container example, you would do the following:

可以使用getMappedPort方法完成此操作,该方法将原始(容器)端口作为参数,因此对于Redis通用容器示例,您将执行以下操作:

Integer mappedPort = container.getMappedPort(6379);

When running with a local Docker daemon, exposed ports will usually be reachable on localhost. However, if you ever need to obtain the container address, you can do:

使用本地Docker守护程序运行时,通常可以在localhost上访问公开的端口。 但是,如果您需要获取容器地址,则可以执行以下操作:

String ipAddress = container.getHost();

Normally you’d want the host and the port together when constructing addresses:

通常,在构造地址时,您希望主机和端口一起使用:

String address = container.getHost() + ":" + container.getMappedPort(6379);

一次性实例 (Throwaway instances)

You can grab a new instance of the Redis container before each test scenario. For instance, if you use Junit, you can add the following code to your setUp method to have Testcontainers recreate the Redis container before each test:

您可以在每个测试方案之前获取Redis容器的新实例。 例如,如果使用Junit,则可以将以下代码添加到setUp方法中,以使Testcontainers在每次测试之前重新创建Redis容器:

private RedisBackedCache underTest;

@Container
public GenericContainer redis = new GenericContainer("redis:5.0.8-alpine3.11") .withExposedPorts(6379);

@BeforeEach
public void setUp() {
String address = redis.getHost();
Integer port = redis.getFirstMappedPort();

underTest = new RedisBackedCache(address, port);
}

The benefit of throwaway instances is that your tests become more reliable as they will always start in a known state, without any contamination between test runs.

一次性实例的好处在于,您的测试将变得更加可靠,因为它们将始终以已知状态启动,并且在两次测试运行之间不会受到任何污染。

清理 (Cleanup)

One of the benefits of Testcontainers is the automatic cleanup of the test environment. Testcontainers automatically spawns a Ryuk container, which is responsible for container removal and automatic cleanup of dead containers. This means that it will stop and remove all containers after your test completes.

Testcontainer的好处之一是自动清除测试环境。 Testcontainers会自动生成Ryuk容器,该容器负责容器的移除和自动清理死容器。 这意味着它将在测试完成后停止并删除所有容器。

其他测试容器功能 (Other Testcontainers features)

  • Specialized containers (databases, selenium, kakfa): There are some specialized containers available for use with Testcontainers, like the Selenium web driver, that you can start a chrome web driver just by doing:

    专用容器(数据库,Selenium,kakfa):有一些可用于Testcontainer的专用容器,例如Selenium Web驱动程序,您可以通过以下操作启动chrome Web驱动程序:

public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer()
.withCapabilities(new ChromeOptions());chrome.start();
  • Dockerfile: In situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly from a Dockerfile.

    Dockerfile:在没有预先存在的Docker映像的情况下,Testcontainers可以从Dockerfile即时创建一个新的临时映像。

  • Docker-compose: Similar to generic containers support, it’s also possible to run a set of services specified in a docker-compose.yml file. This is intended to be useful on projects where Docker Compose is already used in dev or other environments to define services that an application may be dependent upon.

    Docker-compose:类似于通用容器支持,还可以运行在docker-compose.yml文件中指定的一组服务。 这对于在开发人员或其他环境中已经使用Docker Compose定义应用程序可能依赖的服务的项目很有用。

  • And much more…

    以及更多…

最后一些建议 (Some last recommendations)

I mentioned in the beginning of this post that my team and I were looking for a fast and reliable test suite and you can definitely achieve that with Testcontainers. But the truth is that the speed of your tests will depend on the startup time of the container you are using, because you need to wait for it to be ready before you can start running your tests.

我在这篇文章的开头提到,我和我的团队正在寻找一种快速,可靠的测试套件,您肯定可以使用Testcontainer实现它。 但是事实是测试的速度取决于您使用的容器的启动时间,因为您需要等待容器准备就绪才能开始运行测试。

Redis starts up pretty fast (less than 2ms), so it’s fine to have a new instance of it for each test scenario. But that is not always the case. Sometimes you’ll have to choose between speed and isolation. If the container you are using takes too long to start, you’ll probably want to consider reusing one instance of the container across multiple tests.

Redis的启动速度非常快(不到2毫秒),因此可以在每个测试场景中为其创建一个新实例。 但这并非总是如此。 有时,您必须在速度和隔离之间进行选择。 如果您使用的容器启动时间太长,则可能要考虑在多个测试中重用该容器的一个实例。

Choose isolation to avoid contamination between tests when you can. Only reuse the same container instance across tests if you don’t depend on any state or if you can easily manage the state. Otherwise, your tests will become unreliable.

选择隔离以避免测试之间的污染。 如果您不依赖任何状态或可以轻松管理状态,则仅在测试之间重用同一容器实例。 否则,您的测试将变得不可靠。

One thing that is worth mentioning about reliability, is that you also shouldn’t depend on latest tags. You should always pin a specific image version so your tests will be more predictable.

关于可靠性值得一提的一件事是,您也不应该依赖latest标签。 您应该始终固定一个特定的映像版本,以便您的测试更加可预测。

环境设定 (Environment Setup)

Testcontainers is a great choice for having an easy setup for your integration tests, because you don’t need to install anything else other than Docker and you can run the tests from your IDE just like you do with unit tests. It will start and stop your dependencies in Docker containers and cleanup everything afterwards.

Testcontainers是轻松设置集成测试的绝佳选择,因为除了Docker之外,您不需要安装其他任何东西,并且可以像使用单元测试一样从IDE运行测试。 它将在Docker容器中启动和停止您的依赖关系,然后清理所有内容。

额外— Docker套接字 (Extra — The Docker Socket)

A good choice for CI is to run your tests in a Docker container, so they become even more portable.

CI的一个不错的选择是在Docker容器中运行测试,因此它们变得更加可移植。

Let’s say that you are testing a java application and you manage your dependencies with gradle, you can create a Docker image based on gradle and run your tests inside this container, so you won’t have to install Java or Gradle in your CI agents, the only thing you need is Docker.

假设您正在测试Java应用程序,并使用gradle管理依赖项,则可以基于gradle创建Docker映像并在此容器中运行测试,因此无需在CI代理中安装Java或Gradle,您唯一需要的就是Docker。

Testcontainers will spawn some containers during your tests, so you need your container to have access to the Docker daemon running on your machine. That can be achieved with mounting the Docker socket:

测试容器将在测试期间生成一些容器,因此您需要您的容器有权访问在计算机上运行的Docker守护程序。 这可以通过安装Docker套接字来实现:

docker run -v /var/run/docker.sock:/var/run/docker.sock test-runner

Now this container will have access to the Docker socket, and will therefore be able to start containers.

现在,此容器将可以访问Docker套接字,因此将能够启动容器。

Testcontainers inside containers open up a whole new range of opportunities, but that might be best left for a separate post. The point is, Testcointainers — and containers in general — are an important component in the toolbox of testing modern software, which is becoming increasingly dependent on finding the right configurations and connections between independent services.

容器中的Testcontainer开辟了一个全新的机会范围,但这最好留给一个单独的职位。 关键是,Testcointainer(通常是容器)是测试现代软件工具箱中的重要组件,而现代软件越来越依赖于在独立服务之间寻找正确的配置和连接。

翻译自: https://medium.com/@jdelucaa/integration-testing-with-docker-and-testcontainers-3e53e6f44a1b

docker和服务器集成

 类似资料: