使用 TestContainer 库使用真实的 Postgres 实例测试 Java 应用程序的集成层

创建高效的集成测试是[构建微服务时的常见挑战](https://dextra.com.br/pt/por-que-devo-testar-minhas-aplicacoes/)。确保开发环境和生产环境之间的环境平等可能会很痛苦。主要是当工具和服务需要太多手动配置时。发生这种情况时,我们通常会走捷径,在开发和测试环境中采用轻量级工具。这使得环境之间的差异更大。

Twelve-Factor Apps指南告诉我们,环境平等是构建云原生应用程序的成功关键之一。在类似环境中进行开发和测试使我们能够创建弹性应用程序,从而带来安全性和部署速度。

在这篇博文中,我们将了解如何使用Testcontainer库创建集成测试。该测试将启动一个 PostgreSQL 容器并在一个真实的数据库实例上运行读写测试。

什么是测试容器?

Testcontainer 是一个 java 库,它支持我们创建单元测试。它提供了通用数据库或任何运行到Docker容器中的应用程序的轻量级和一次性实例。它还可以使用容器轻松创建集成测试,而无需使用复杂的配置

前置要求

要运行 Github](https://github.com/educostadev/poc-testcontainers)上的示例代码[,您需要安装 git 和 IDE。除此之外,您还需要拥有 Docker 并能够执行以下命令:

docker info

如果您使用的是 Linux,请查看该命令是否没有“sudo”前缀。您必须能够在没有此前缀的情况下执行此命令,因为在运行测试期间,库将使用 Docker 下载图像并创建容器。

要运行不带“sudo”前缀的 docker 命令,应通过以下命令将您的用户添加到 docker 用户组:

sudo usermod -aG docker $USER

运行此命令后,您需要注销并登录才能使更改生效。

示例项目

这个项目有一个集成测试,它启动一个 PostgreSQL 容器,设置运行 SQL 脚本的数据库初始状态,运行测试,最后销毁容器。

要查看代码,请克隆运行以下命令的 git 存储库:

git clone https://github.com/educostadev/poc-testcontainers.git

在 IDE 中将项目作为 Maven 项目导入。如果您使用 IntelliJ,这个链接有更多关于如何导入 Maven 项目的信息。

Testcontainer 使用 pom.xml 中指定的依赖项与JUnit4和JUnit5很好地集成。从代码示例中打开 pom.xml 文件并查看负责将 Testcontainer 与 JUnit5 一起使用的依赖项。

<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>junit-jupiter</artifactId>
   <version>1.12.3</version>
   <scope>test</scope>
</dependency>

如果您使用的是 Testcontainer 已经支持的数据库,那么您需要在项目中添加正确的依赖项。在_ this link_中,您可以找到 Testcontainer 支持的数据库列表。在我们的示例中,我们使用的是 PostgreSQL 实例,并且需要以下依赖项:

<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>postgresql</artifactId>
   <scope>test</scope>
</dependency>

项目文件遵循标准的 Spring Boot 应用程序。在 application.yml 文件中,您可以找到所有数据库连接条目。注意使用名为 DB_URLDB_USERNAMEDB_PASSWORD 的环境变量。

server:
  port: 8083
logging:
  level:
    ROOT: info
    org.hibernate.tool.hbm2ddl: debug
    org.hibernate.SQL: debug
    org.hibernate.type.descriptor.sql: trace
spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: 5
      connection-timeout: 20000
    initialization-mode: always
  jpa:
    database-platform: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: create

DummyDao 类利用 jdbcTemplate 库在数据库中执行 SQL 查询。这是将使用 PostgreSQL 容器测试的类。

@Repository
public class DummyDao {
  @Autowired
  private JdbcTemplate template;
  public Dummy findDummyById(int id) {
    return template
        .queryForObject("SELECT * FROM dummy_test where id=?", new Object[]{id},
            new DummyRowMapper());
  }

  public List readAll() {
    return template.query("SELECT id,name_value FROM dummy_test", new DummyRowMapper());
  }

  public int save(Dummy dummy) {
    return template.update("INSERT INTO dummy_test (id,name_value) VALUES (?,?) ",
        new Object[]{dummy.getId(), dummy.getName()});
  }

  static class DummyRowMapper implements RowMapper {
    @Override
    public Dummy mapRow(ResultSet rs, int rowNumber) throws SQLException {
      return new Dummy(rs.getInt("id"), rs.getString("name_value"));
    }
  }
}

看下面的测试,它注入了一个 DummyDAO 实例,下面的方法执行了一个读写集成测试。因为这是一个集成测试,所以我们不是_ [_mocking](https://pt.wikipedia.org/wiki/Objeto_Mock)__)_DummyDAO_类的行为,这意味着需要与数据库建立真正的连接。

PostgresQL 容器的静态实例在类开头创建。即使这个实例没有在类体中使用,它也负责创建和启动容器。

属性的静态初始化允许我们在其他正在运行的测试中重用相同的容器实例。这避免了在每次执行测试时创建和销毁新容器。 Tha @TestContainer@Container 注释负责容器生命周期,并允许它启动一次并在测试结束时销毁。

@SpringBootTest
@ContextConfiguration
@Testcontainers
public class DummyDaoTest {

@Container
public static PostgreSQLContainer postgreSQLContainer = CustomPostgresContainer.getInstance();

@Autowired
DummyDao dao;

@Test
void injectedComponentsAreNotNull() {
  assertNotNull(dao);
}

@Test
void save_new_value() {
  Dummy value = new Dummy(1000, "dummy");
  int affectedRows = dao.save(value);
  assertEquals(1, affectedRows);
}

@Test
void read_all() {
  assertThat(dao.readAll()).isNotEmpty();
}

}

CustomPostgresContainer _class 负责下载镜像,创建并启动 alpine 版本的 _PostgreSQL 容器。您可以使用Docker Hub中的任何可用映像,甚至可以使用自定义映像。

在这个演示示例中,启动容器并加载 SQL 脚本以使用一些数据设置数据库。

被覆盖的方法 start() 在这个测试基础设施中扮演着重要的角色。它从启动的容器中获取 URL、用户和密码,并将这些值写入环境变量。这允许测试连接到一个真实的数据库实例。

public class CustomPostgresContainer extends PostgreSQLContainer {

  private static final Logger logger = LoggerFactory.getLogger(CustomPostgresContainer.class);
  private static final String IMAGE_VERSION = "postgres:alpine";
  private static CustomPostgresContainer container;

  private CustomPostgresContainer() {
    super(IMAGE_VERSION);
  }

  public static CustomPostgresContainer getInstance() {
    if (container == null) {
      container = new CustomPostgresContainer();
    }
    return container;
  }

  @Override
  public void start() {
    super.start();
    logger.debug("POSTGRES INFO");
    logger.debug("DB_URL: " + container.getJdbcUrl());
    logger.debug("DB_USERNAME: " + container.getUsername());
    logger.debug("DB_PASSWORD: " + container.getPassword());
    System.setProperty("DB_URL", container.getJdbcUrl());
    System.setProperty("DB_USERNAME", container.getUsername());
    System.setProperty("DB_PASSWORD", container.getPassword());
  }

  @Override
  public void stop() {
    //do nothing, JVM handles shut down
  }
}

使用 Testcontainer 的测试执行流程总结如下:

  • 开始测试的第一个测试;

  • 调用 CustomPostgresContainer 类的 getInstance() 方法。

  • _Testcontainer_库检查本地是否存在所需的镜像;

  • 如果本地不存在镜像,则从_docker hub_下载;

  • 基于下载的镜像创建容器并启动数据库;

  • 以下所有测试都使用启动的数据库;

  • 在所有测试执行结束时,容器被销毁。

结论

在本文中,我们看到了 Testcontainer 库如何使用 docker 镜像轻松创建测试集成。它让我们减少了,开发环境和生产环境之间的环境差异,也带来了部署的安全性。该库每天都在发展,并为许多其他数据库带来原生支持,并允许您创建图像。在此视频,Kevin Wittek中,Testcontainer 提交者之一向我们展示了有关该库的更多信息。

您是否创建了使用真实服务实例的集成测试?你用了什么工具?在下面发表评论!

后与 TestContainer的集成测试首先出现在Be Cloud Native上。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐