如何使用 Testcontainers 和 PostgreSQL 编写集成测试

发布于:2023-01-04 ⋅ 阅读:(324) ⋅ 点赞:(0)

如何使用 Testcontainers 和 PostgreSQL 编写集成测试

测试容器是测试应用程序的最有效工具之一。

扫码关注《Java学研大本营》,加入读者群,分享更多精彩

测试容器是测试应用程序的最有效工具之一。因为您可以在 prod 环境中如此接近地运行测试。您不需要模拟存储库层。

它有各种各样的容器替代品,例如数据库(SQL 和 NoSQL 选项)、消息传递、MockServer、AWS Localstack等。

这篇文章将解释如何使用 PostgreSQL 测试容器编写集成测试。

我们需要在我们的计算机上安装一个 Docker 引擎来进行测试。因此,您可以查看兼容性并下载所需的版本。

(https://www.testcontainers.org/supported_docker_environment/)

需要的依赖

可以看到相关的依赖,分别是Testcontainers和PostgreSQL。可以从 repo 中看到整个依赖项。

<dependencies>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers-bom</artifactId>
            <version>${testcontainers.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

实体

我们的 User 实体类如下。我们将在测试中从 dockerized PostgreSQL 数据库中获取这些数据。

package com.example.demo.model;

import lombok.Data;

import javax.persistence.*;

@Data
@Entity
@Table(name = "app_user")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  @Column(name = "id", nullable = false)
  private Long id;

  @Column(name = "name", nullable = false)
  private String name;

  @Column(name = "status")
  private int status;
}

初始脚本

此初始脚本放置在测试的存储库文件夹下。该脚本将在 dockerized PostgreSQL 实例中使用。我们将基本上创建一个表并插入两条记录。

CREATE TABLE app_user
(
    id     bigserial NOT NULL PRIMARY KEY,
    name   text      NOT NULL,
    status integer   NOT NULL
);

insert into app_user (name, status)
values ('Turan', 1);

insert into app_user (name, status)
values ('Ulus', 1);

我们的测试类

如果在第一个测试中将数据插入到表中,我们将获取数据。在下面的测试中,我们将对用户的状态属性进行简单的计数检查。

在这两个测试中,我们启动了一个实际的 Web 应用程序实例。因此,这两个测试都通过 API 运行。

我们将在测试中使用的一些注释:

@Testcontainers:这个注解自动处理容器的生命周期。它负责启动和关闭我们测试中的每个容器。

@Container:标记要由 Testcontainers 扩展管理的容器。

@DynamicPropertySource:此注解用于分配外部属性。

package com.example.demo.controller;

import com.example.demo.model.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public class UserControllerIntegrationTest {

  @LocalServerPort private int port;

  @Autowired protected TestRestTemplate testRestTemplate;

  @Container
  static final PostgreSQLContainer<?> postgreSQLContainer =
      new PostgreSQLContainer<>("postgres:11.1")
          .withUsername("sa")
          .withPassword("password")
          .withInitScript("init_script.sql")
          .withDatabaseName("integration-tests-db");

  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
  }

  @Test
  void testGetUserById_successful() {
    ResponseEntity<User> response =
        this.testRestTemplate.getForEntity("http://localhost:" + port + "/users/1", User.class);

    Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
    Assertions.assertEquals("Turan", response.getBody().getName());
  }

  @Test
  void testCountByStatus_successful() {
    ResponseEntity<Long> response =
        this.testRestTemplate.getForEntity(
            "http://localhost:" + port + "/users/count-by-status/1", Long.class);

    Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
    Assertions.assertEquals(2, response.getBody());
  }
}package com.example.demo.controller;

import com.example.demo.model.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public class UserControllerIntegrationTest {

  @LocalServerPort private int port;

  @Autowired protected TestRestTemplate testRestTemplate;

  @Container
  static final PostgreSQLContainer<?> postgreSQLContainer =
      new PostgreSQLContainer<>("postgres:11.1")
          .withUsername("sa")
          .withPassword("password")
          .withInitScript("init_script.sql")
          .withDatabaseName("integration-tests-db");

  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
  }

  @Test
  void testGetUserById_successful() {
    ResponseEntity<User> response =
        this.testRestTemplate.getForEntity("http://localhost:" + port + "/users/1", User.class);

    Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
    Assertions.assertEquals("Turan", response.getBody().getName());
  }

  @Test
  void testCountByStatus_successful() {
    ResponseEntity<Long> response =
        this.testRestTemplate.getForEntity(
            "http://localhost:" + port + "/users/count-by-status/1", Long.class);

    Assertions.assertTrue(response.getStatusCode().is2xxSuccessful());
    Assertions.assertEquals(2, response.getBody());
  }
}

结论

您可以看到 Testcontainers 使创建准确的数据库变得简单。这些测试在 Docker 引擎基础设施和真实的 Web 应用程序(本例中为 Tomcat)上运行。因此,这可能是 CI/CP 管道的开销。应该针对需要的点运行类似于以前的测试。

参考文章: https://medium.com/@turanulus/how-to-write-an-integration-test-with-testcontainers-and-postgresql-67425e124753

推荐书单

1.《精通PostgreSQL 11(第2版)》

购买链接:https://item.jd.com/12859900.html

本书详细阐述了与PostgreSQL 11相关的基本解决方案,主要包括PostgreSQL概述、理解事务和锁定、使用索引、处理高级SQL、日志文件和系统统计信息、优化查询性能、编写存储过程、管理PostgreSQL的安全性、处理备份和恢复、理解备份与复制、选取有用的扩展、检修PostgreSQL、迁移到PostgreSQL等内容。此外,本书还提供了相应的示例、代码,以帮助读者进一步理解相关方案的实现过程。

本书适合作为高等院校计算机及相关专业的教材和教学参考书,也可作为相关开发人员的自学教材和参考手册。

本书帮助读者用新的PostgreSQL版本为企业应用构建动态数据库方案,能让数据库分析师轻松地设计物理和技术方面的系统架构。

2.《由浅入深PostgreSQL》

购买链接:https://item.jd.com/47420584201.html

本书从一位PostgreSQL 专家在多年咨询、技术支持工作中的切身体会出发,深入介绍了开源数据库管理系统PostgreSQL 9.6 版本中的主要特性,其内容涵盖了作为一个PostgreSQL 数据库从业人员经常会接触到的主题:事务和锁定、索引的使用、高级SQL 处理、日志文件和统计信息、查询优化、存储过程、安全性、备份与恢复、复制、各类扩展、故障排查、系统迁移。

作者通过亲身经历和直观的例子,详细介绍了PostgreSQL 主要特性的工作原理、常用配置以及常见的误区,是一本实用性很强的PostgreSQL 进阶指南,能帮助有一定PostgreSQL 知识的读者深入了解PostgreSQL 中更多更全面的高级特性。

本书适合数据库管理人员和开发人员了解和学习PostgreSQL。通过阅读本书,读者可以对PostgreSQL有一个全面透彻的了解。

3.《名师讲坛:Java微服务架构实战(SpringBoot+SpringCloud+Docker+RabbitMQ)》

购买链接:https://item.jd.com/12793864.html

Java微服务架构是当下流行的软件架构设计方案,可以快速地进行代码编写与开发,维护起来也非常方便。利用微架构技术,可以轻松地实现高可用、分布式、高性能的项目结构开发,同时也更加安全。

《名师讲坛:Java微服务架构实战(SpringBoot+SpringCloud+Docker+RabbitMQ)》一共15章,核心内容为SpringBoot、SpringCloud、Docker、RabbitMQ消息组件。其中,SpringBoot 是SpringMVC技术的延伸,使用它进行程序开发会更简单,服务整合也会更容易。SpringCloud是当前微架构的核心技术方案,属于SpringBoot的技术延伸,它可以整合云服务,基于RabbitMQ和GITHUB进行微服务管理。除此以外,该书还重点分析了OAuth统一认证服务的应用。

《名师讲坛:Java微服务架构实战(SpringBoot+SpringCloud+Docker+RabbitMQ)》适用于从事Java开发且有架构与项目重构需求的读者,也适用于相关技术爱好者,同时也可作为应用型高等院校及培训机构的学习教材。

精彩回顾

深入理解Docker网络通信原理

详细&全面的RxJava架构原理与设计讲解

Java面试宝典大集锦

扫码关注《Java学研大本营》,加入读者群,分享更多精彩

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到