1. 概述

Apache Cassandra 是一个开源的分布式 NoSQL 数据库。它专为处理海量数据而设计,具备高速读写能力且无单点故障

本教程将探讨如何测试使用 Cassandra 数据库的 Spring Boot 应用。我们将讲解如何使用 Testcontainers 库中的 Cassandra 容器设置集成测试,并利用 Spring Data 仓库抽象层操作 Cassandra 数据层。

最后,我们将展示如何在多个集成测试间复用共享的 Cassandra 容器实例。

2. Testcontainers 容器

Testcontainers 是一个 Java 库,提供轻量级、可丢弃的 Docker 容器实例。因此,在 Spring 中我们常用它来测试使用数据库的应用。Testcontainers 让我们能在真实数据库实例上测试,无需在本地安装和管理数据库。

2.1. Maven 依赖

Cassandra 容器由 Cassandra Testcontainers 模块 提供。这让我们能使用容器化的 Cassandra 实例。

cassandra-unit 库不同,Testcontainers 完全兼容 JUnit 5。先列出所需的 Maven 依赖:

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

2.2. Cassandra 容器

容器化数据库实例常用于集成测试,确保数据访问层代码与特定数据库版本完全兼容

首先,在测试类上添加 @SpringBootTest@Testcontainers 注解:

@SpringBootTest
@Testcontainers
class CassandraSimpleIntegrationTest {}

然后定义 Cassandra 容器并暴露其端口

public static final CassandraContainer<?> cassandra 
  = new CassandraContainer<>("cassandra:3.11.2").withExposedPorts(9042);

这里暴露了容器端口 9042。注意 Testcontainers 会将其映射到随机主机端口,后续可获取该端口。

使用上述代码,Testcontainers 会自动管理容器生命周期,随测试类启动 Docker 化的 Cassandra 实例:

@Test
void givenCassandraContainer_whenSpringContextIsBootstrapped_thenContainerIsRunningWithNoExceptions() {
    assertThat(cassandra.isRunning()).isTrue();
}

现在容器已运行,但 Spring 应用还不知道它的存在。

2.3. 覆盖测试属性

为使 Spring Data 连接到 Cassandra 容器,需提供连接属性。通过 java.lang.System 类设置系统属性覆盖默认配置:

@BeforeAll
static void setupCassandraConnectionProperties() {
    System.setProperty("spring.cassandra.keyspace-name", KEYSPACE_NAME);
    System.setProperty("spring.cassandra.contact-points", cassandra.getContainerIpAddress());
    System.setProperty("spring.cassandra.port", String.valueOf(cassandra.getMappedPort(9042)));
}

现在 Spring Data 已配置连接容器,但仍需创建键空间。

2.4. 创建键空间

在 Cassandra 创建表之前,需先创建键空间:

private static void createKeyspace(Cluster cluster) {
    try (Session session = cluster.connect()) {
        session.execute("CREATE KEYSPACE IF NOT EXISTS " + KEYSPACE_NAME +
          " WITH replication = \n" +
          "{'class':'SimpleStrategy','replication_factor':'1'};");
    }
}

Cassandra 的键空间类似于关系型数据库中的数据库,定义了数据在集群节点间的复制策略。

3. Spring Data for Cassandra

Spring Data for Apache Cassandra 将 Spring 核心概念应用于 Cassandra 开发,提供仓库、查询构建器和注解实现对象映射,为 Spring 开发者提供熟悉的数据库操作接口。

3.1. 数据访问对象

准备一个简单的 DAO 类用于后续集成测试:

@Table
public class Car {

    @PrimaryKey
    private UUID id;
    private String make;
    private String model;
    private int year;

    public Car(UUID id, String make, String model, int year) {
        this.id = id;
        this.make = make;
        this.model = model;
        this.year = year;
    }

    //getters, setters, equals and hashcode
}

关键点:使用 org.springframework.data.cassandra.core.mapping 包下的 @Table 注解,该注解启用自动对象映射。

3.2. Cassandra 仓库

Spring Data 让创建仓库变得简单。首先在 Spring Boot 主类启用 Cassandra 仓库:

@SpringBootApplication
@EnableCassandraRepositories(basePackages = "org.baeldung.springcassandra.repository")
public class SpringCassandraApplication {}

然后创建继承 CassandraRepository 的接口:

@Repository
public interface CarRepository extends CassandraRepository<Car, UUID> {}

开始集成测试前,需定义两个额外属性:

spring.data.cassandra.local-datacenter=datacenter1
spring.data.cassandra.schema-action=create_if_not_exists
  • 第一个属性定义本地数据中心名称
  • 第二个属性让 Spring Data 自动创建所需表(生产环境禁用此配置

由于使用 Testcontainers,测试结束后无需手动清理表——每次测试都会启动新容器。

4. 集成测试

现在我们已准备好 Cassandra 容器、DAO 类和 Spring Data 仓库,可以开始编写集成测试。

4.1. 保存记录测试

测试向 Cassandra 插入新记录:

@Test
void givenValidCarRecord_whenSavingIt_thenRecordIsSaved() {
    UUID carId = UUIDs.timeBased();
    Car newCar = new Car(carId, "Nissan", "Qashqai", 2018);

    carRepository.save(newCar);

    List<Car> savedCars = carRepository.findAllById(List.of(carId));
    assertThat(savedCars.get(0)).isEqualTo(newCar);
}

4.2. 更新记录测试

测试更新现有记录:

@Test
void givenExistingCarRecord_whenUpdatingIt_thenRecordIsUpdated() {
    UUID carId = UUIDs.timeBased();
    Car existingCar = carRepository.save(new Car(carId, "Nissan", "Qashqai", 2018));

    existingCar.setModel("X-Trail");
    carRepository.save(existingCar);

    List<Car> savedCars = carRepository.findAllById(List.of(carId));
    assertThat(savedCars.get(0).getModel()).isEqualTo("X-Trail");
}

4.3. 删除记录测试

测试删除现有记录:

@Test
void givenExistingCarRecord_whenDeletingIt_thenRecordIsDeleted() {
    UUID carId = UUIDs.timeBased();
    Car existingCar = carRepository.save(new Car(carId, "Nissan", "Qashqai", 2018));

    carRepository.delete(existingCar);

    List<Car> savedCars = carRepository.findAllById(List.of(carId));
    assertThat(savedCars.isEmpty()).isTrue();
}

5. 共享容器实例

集成测试中,通常需要在多个测试间复用同一数据库实例。通过嵌套测试类实现容器共享:

@Testcontainers
@SpringBootTest
class CassandraNestedIntegrationTest {

    private static final String KEYSPACE_NAME = "test";

    @Container
    private static final CassandraContainer<?> cassandra 
      = new CassandraContainer<>("cassandra:3.11.2").withExposedPorts(9042);

    // 设置连接属性和创建键空间

    @Nested
    class ApplicationContextIntegrationTest {
        @Test
        void givenCassandraContainer_whenSpringContextIsBootstrapped_thenContainerIsRunningWithNoExceptions() {
            assertThat(cassandra.isRunning()).isTrue();
        }
    }

    @Nested
    class CarRepositoryIntegrationTest {

        @Autowired
        private CarRepository carRepository;

        @Test
        void givenValidCarRecord_whenSavingIt_thenRecordIsSaved() {
            UUID carId = UUIDs.timeBased();
            Car newCar = new Car(carId, "Nissan", "Qashqai", 2018);

            carRepository.save(newCar);

            List<Car> savedCars = carRepository.findAllById(List.of(carId));
            assertThat(savedCars.get(0)).isEqualTo(newCar);
        }

        // 更新和删除测试
    }
}

由于 Docker 容器启动耗时,共享容器实例能显著提升嵌套测试的执行速度。但注意:共享实例不会在测试间自动清理数据。

6. 总结

本文探讨了使用 Cassandra 容器测试 Spring Boot 应用的方法,涵盖:

  • 设置 Docker 化 Cassandra 容器
  • 覆盖测试属性
  • 创建键空间、DAO 类和仓库接口
  • 编写无需模拟的真实集成测试
  • 在嵌套测试间复用容器实例

所有示例代码均可在 GitHub 获取。


原始标题:Using Test Containers With Spring Data Cassandra | Baeldung