1. Overview
1.概述
Apache Cassandra is an open-source distributed NoSQL database. It was designed to handle large amounts of data with fast read-write performance and with no single point of failure.
Apache Cassandra是一个开源的分布式NoSQL数据库。它被设计为处理大量的数据,具有快速的读写性能,并且没有单点故障。
In this tutorial, we’ll look at testing a Spring Boot application that uses a Cassandra database. We’ll explain how to set up integration tests using a Cassandra container from the Testcontainers library. In addition, we’ll make use of the Spring Data repository abstraction to work with Cassandra’s data layer.
在本教程中,我们将探讨如何测试一个使用 Cassandra 数据库的 Spring Boot 应用程序。我们将解释如何使用来自Testcontainers库的Cassandra容器设置集成测试。此外,我们还将利用 Spring Data 存储库的抽象,与 Cassandra 的数据层一起工作。
Finally, we’ll show how to reuse a shared Cassandra container instance across multiple integration tests.
最后,我们将展示如何在多个集成测试中重复使用一个共享的Cassandra容器实例。
2. Test Containers
2.测试容器
Testcontainers is a Java library that provides lightweight, throwaway instances of Docker containers. Hence, we commonly use it in Spring for integration testing of applications that use databases. Testcontainers enables us to test on a real database instance without requiring us to install and manage the database on our local machine.
Testcontainers是一个Java库,提供轻量级的、可抛弃的Docker容器实例。因此,我们通常在Spring中使用它来对使用数据库的应用程序进行集成测试。Testcontainers使我们能够在真实的数据库实例上进行测试,而不需要我们在本地机器上安装和管理数据库。
2.1. Maven Dependencies
2.1.Maven的依赖性
Cassandra containers are available in the Cassandra Testcontainers module. This enables the usage of containerized Cassandra instances.
Cassandra 容器在Cassandra Testcontainers 模块中可用。这样就可以使用容器化的 Cassandra 实例。
Unlike the cassandra-unit library, the Testcontainers library is fully compatible with JUnit 5. Let’s start by listing the required Maven dependencies:
与cassandra-unit库不同,Testcontainers库与JUnit 5完全兼容。让我们首先列出所需的Maven依赖项。
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>cassandra</artifactId>
<version>1.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.15.3</version>
<scope>test</scope>
<dependency>
2.2. Cassandra Container
2.2 卡桑德拉容器
Containerized database instances are commonly used for integration testing. As well as ensuring that our data access layer code is fully compatible with the specific database version.
容器化的数据库实例通常用于集成测试。以及确保我们的数据访问层代码与特定数据库版本完全兼容。
To begin with, we’ll need to annotate our test class with both @SpringBootTest and @Testcontainers:
首先,我们需要用@SpringBootTest和@Testcontainers来注释我们的测试类。
@SpringBootTest
@Testcontainers
class CassandraSimpleIntegrationTest {}
Then, we can define a Cassandra container and expose its specific port:
然后,我们可以定义一个Cassandra容器并公开其特定的端口。
@Container
public static final CassandraContainer cassandra
= (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);
Here we’re exposing the container port 9042. We should note, however, that Testcontainers will link it to a random host port, which we can get later on.
这里我们暴露了容器的端口9042.,但是我们应该注意,Testcontainers将把它链接到一个随机的主机端口,我们可以在后面得到这个端口。
Using the above, the Testcontainers library automatically takes care of starting a dockerized Cassandra container instance for us aligned with the lifecycle of the test class:
使用上述方法,Testcontainers库会自动为我们启动一个docker化的Cassandra容器实例与测试类的生命周期一致。
@Test
void givenCassandraContainer_whenSpringContextIsBootstrapped_thenContainerIsRunningWithNoExceptions() {
assertThat(cassandra.isRunning()).isTrue();
}
Now we have a running Cassandra container. However, the Spring application does not yet know about it.
现在我们有一个正在运行的Cassandra容器。然而,Spring应用程序还不知道它的存在。
2.3. Overriding Test Properties
2.3.重写测试属性
In order for Spring Data to be able to establish a connection with the Cassandra container, we’ll need to provide a few connection properties. We’ll override the default Cassandra connection properties by defining system properties via the java.lang.System class:
为了让Spring Data能够与Cassandra容器建立连接,我们需要提供一些连接属性。我们将通过java.lang.System类定义系统属性来覆盖默认的Cassandra连接属性。
@BeforeAll
static void setupCassandraConnectionProperties() {
System.setProperty("spring.data.cassandra.keyspace-name", KEYSPACE_NAME);
System.setProperty("spring.data.cassandra.contact-points", cassandra.getContainerIpAddress());
System.setProperty("spring.data.cassandra.port", String.valueOf(cassandra.getMappedPort(9042)));
}
Now we configured Spring Data to connect with our Cassandra container. However, we’ll still need to create a keyspace.
现在我们配置了Spring Data,使其与我们的Cassandra容器连接。然而,我们仍然需要创建一个密钥空间。
2.4. Creating a Keyspace
2.4.创建一个钥匙空间
As the last step before creating any tables in Cassandra, we’ll need to create a keyspace:
作为最后一步在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'};");
}
}
A keyspace in Cassandra is very similar to a database in an RDBMS. It defines how data replicates on nodes in a Cassandra cluster.
Cassandra中的密钥空间与RDBMS中的数据库非常相似。它定义了数据如何在Cassandra集群的节点上复制。
3. Spring Data for Cassandra
3.卡桑德拉的Spring数据
Spring Data for Apache Cassandra applies core Spring concepts to the development of applications using Cassandra. It provides repositories, query builders, and simple annotations for rich object mapping. Thus, it offers a familiar interface to Spring developers working with different databases.
Spring Data for Apache Cassandra 将Spring的核心概念应用于使用Cassandra开发应用程序。它提供了资源库、查询生成器以及用于丰富对象映射的简单注释。因此,它为使用不同数据库的Spring开发人员提供了一个熟悉的界面。
3.1. Data Access Object
3.1.数据访问对象
Let’s start by preparing a simple DAO class which we’ll be using later in our integration tests:
让我们先准备一个简单的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
}
The key here is to annotate the class with @Table annotation from the org.springframework.data.cassandra.core.mapping package. In fact, this annotation enables automatic domain object mapping.
这里的关键是用@Table注解从org.springframework.data.cassandra.core.mapping包中对该类进行标注。事实上,这个注解可以实现域对象的自动映射。
3.2. Cassandra Repository
3.2.卡桑德拉资源库
Spring Data makes it very simple to create a repository for our DAO. To begin with, we’ll need to enable Cassandra repositories in our Spring Boot main class:
Spring Data使得为我们的DAO创建一个存储库变得非常简单。首先,我们需要在Spring Boot的主类中启用Cassandra资源库。
@SpringBootApplication
@EnableCassandraRepositories(basePackages = "org.baeldung.springcassandra.repository")
public class SpringCassandraApplication {}
Then, we simply need to create an interface that extends the CassandraRepository:
然后,我们只需要创建一个扩展CassandraRepository的接口。
@Repository
public interface CarRepository extends CassandraRepository<Car, UUID> {}
Before starting with the integration tests, we’ll need to define two additional properties:
在开始进行集成测试之前,我们需要定义两个额外的属性。
spring.data.cassandra.local-datacenter=datacenter1
spring.data.cassandra.schema-action=create_if_not_exists
The first property defines the default local data center name. The second one will ensure that Spring Data automatically creates the required database tables for us. We should note that this setting shouldn’t be used in production systems.
第一个属性定义了默认的本地数据中心名称。第二个将确保Spring Data自动为我们创建所需的数据库表。我们应该注意,这个设置不应该在生产系统中使用。
Since we are using Testcontainers, we don’t need to worry about dropping the tables once the tests are finished. A new container will be started for us every time we run our tests.
由于我们使用的是Testcontainers,所以我们不需要担心测试完成后丢弃表。每次我们运行测试时,都会为我们启动一个新的容器。
4. Integration Tests
4.集成测试
Now that have our Cassandra container, a simple DAO class, and a Spring Data repository set up, we are ready to start writing integration tests.
现在我们已经有了Cassandra容器、一个简单的DAO类和一个Spring Data存储库,我们准备开始编写集成测试。
4.1. Saving Record Test
4.1.保存记录测试
Let’s start by testing the insertion of a new record into the Cassandra database:
让我们先测试一下向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. Updating Record Test
4.2.更新记录测试
Then, we can write a similar test for updating an existing database record:
然后,我们可以写一个类似的测试来更新一个现有的数据库记录。
@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. Deleting Record Test
4.3.删除记录测试
Finally, let’s write a test for deleting an existing database record:
最后,我们来写一个删除现有数据库记录的测试。
@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. Shared Container Instance
5.共享的容器实例
Most of the time, when working with integration tests, we would like to reuse the same database instance across multiple tests. We can share the same container instance by making use of multiple nested test classes:
大多数时候,在进行集成测试时,我们希望在多个测试中重复使用同一个数据库实例。我们可以通过使用多个嵌套测试类来共享同一个容器实例。
@Testcontainers
@SpringBootTest
class CassandraNestedIntegrationTest {
private static final String KEYSPACE_NAME = "test";
@Container
private static final CassandraContainer cassandra
= (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);
// Set connection properties and create keyspace
@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);
}
// Tests for update and delete
}
}
Since Docker containers take time to start up, a shared container instance between multiple nested test classes will ensure faster execution. We should note, however, that this shared instance won’t be automatically cleared between tests.
由于Docker容器需要时间来启动,多个嵌套测试类之间的共享容器实例将确保更快的执行。然而,我们应该注意到,这种共享实例在测试之间不会被自动清除。
6. Conclusion
6.结论
In this article, we explored using a Cassandra container for testing a Spring Boot application that uses a Cassandra database.
在这篇文章中,我们探讨了使用Cassandra容器来测试使用Cassandra数据库的Spring Boot应用程序。
In the examples, we covered setting up a dockerized Cassandra container instance, overriding test properties, creating a keyspace, a DAO class, and a Cassandra repository interface.
在例子中,我们涵盖了设置一个docker化的Cassandra容器实例,覆盖测试属性,创建一个钥匙空间,一个DAO类,以及一个Cassandra存储库接口。
We saw how to write integration tests that make use of a Cassandra container. Thus our example tests required no mocking. Finally, we saw how to reuse the same container instance across multiple nested tests classes.
我们看到如何编写利用Cassandra容器的集成测试。因此,我们的例子测试不需要嘲弄。最后,我们看到如何在多个嵌套测试类中重用同一个容器实例。
As always, the source code is available over on GitHub.
像往常一样,源代码可在GitHub上获得。