1. 引言

在这篇文章中,我们将学习如何在Java应用程序中模拟亚马逊S3(简单存储服务)来编写集成测试。

为了演示其工作原理,我们将创建一个使用AWS SDK与S3交互的CRUD(创建、读取、更新、删除)服务,然后为每个操作编写集成测试,使用模拟的S3服务。

2. S3概述

亚马逊简单存储服务(S3)是由亚马逊网络服务(AWS)提供的高度可扩展和安全的云存储服务。它采用对象存储模型,允许用户从网络上的任何地方存储和检索数据

通过REST风格的API访问此服务,AWS提供了Java应用程序SDK,用于执行如创建、列出和删除S3桶和对象等操作。

接下来,让我们开始使用AWS SDK创建Java的S3 CRUD服务,并实现创建、读取、更新和删除操作。

3. 示例S3 CRUD Java服务

在开始使用S3之前,我们需要在项目中添加AWS SDK的依赖:

<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>2.20.52</version>
</dependency>

要查看最新版本,请参阅Maven中央仓库

接下来,我们创建带有software.amazon.awssdk.services.s3.S3Client依赖的S3CrudService类:

class S3CrudService {
    private final S3Client s3Client;

    public S3CrudService(S3Client s3Client) {
        this.s3Client = s3Client;
    }

    // ...
}

现在我们有了服务,让我们使用AWS SDK提供的S3Client API来实现createBucket()createObject()getObject()deleteObject()操作

void createBucket(String bucketName) {
    // build bucketRequest
    s3Client.createBucket(bucketRequest);
}

void createObject(String bucketName, File inMemoryObject) {
    // build putObjectRequest
    s3Client.putObject(request, RequestBody.fromByteBuffer(inMemoryObject.getContent()));
}

Optional<byte[]> getObject(String bucketName, String objectKey) {
    try {
        // build getObjectRequest
        ResponseBytes<GetObjectResponse> responseResponseBytes = s3Client.getObjectAsBytes(getObjectRequest);
        return Optional.of(responseResponseBytes.asByteArray());
    } catch (S3Exception e) {
        return Optional.empty();
    }
}

boolean deleteObject(String bucketName, String objectKey) {
    try {
        // build deleteObjectRequest
        s3Client.deleteObject(deleteObjectRequest);
        return true;
    } catch (S3Exception e) {
        return false;
    }
}

现在我们已经创建了S3操作,接下来将学习如何使用模拟的S3服务来实现集成测试。

4. 使用S3Mock进行集成测试

对于本教程,我们选择了由Adobe提供的、基于Apache V2许可证的S3Mock。S3Mock是一个轻量级服务器,实现了Amazon S3 API中最常用的操作。有关支持的S3操作,可以查看S3Mock仓库readme文件中的相关部分。

库开发者建议单独运行S3Mock服务,最好使用提供的Docker容器。

遵循这一建议,我们将使用Docker和Testcontainers来为集成测试运行S3Mock服务

4.1. 依赖项

接下来,让我们添加运行S3Mock所需的必要依赖,以及Testcontainers:

<dependency>
    <groupId>com.adobe.testing</groupId>
    <artifactId>s3mock</artifactId>
    <version>3.3.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.adobe.testing</groupId>
    <artifactId>s3mock-testcontainers</artifactId>
    <version>3.3.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.19.4</version>
    <scope>test</scope>
</dependency>

可以在Maven Centrals3mock-testcontainersjunit-jupiter上查看最新的版本链接。

4.2. 配置

作为前提条件,我们必须有一个运行的Docker环境,以确保Test Containers能够启动。

当我们在集成测试类上使用@TestContainers@Container注解时,会从注册表拉取并启动最新的S3MockContainer Docker镜像:

@Testcontainers
class S3CrudServiceIntegrationTest {
    @Container
    private  S3MockContainer s3Mock = new S3MockContainer("latest");
}

在运行集成测试之前,让我们在@BeforeEach生命周期方法中创建一个S3Client实例:

@BeforeEach
void setUp() {
    var endpoint = s3Mock.getHttpsEndpoint();
    var serviceConfig = S3Configuration.builder()
      .pathStyleAccessEnabled(true)
      .build();
    var httpClient = UrlConnectionHttpClient.builder()
      .buildWithDefaults(AttributeMap.builder()
        .put(TRUST_ALL_CERTIFICATES, Boolean.TRUE)
        .build());
    s3Client = S3Client.builder()
      .endpointOverride(URI.create(endpoint))
      .serviceConfiguration(serviceConfig)
      .httpClient(httpClient)
      .build();
}

setup()方法中,我们使用S3Client接口提供的构建器初始化了一个实例。在这个初始化过程中,我们为以下参数配置了设置:

  • endpointOverwrite: 这个参数用于定义S3模拟服务的地址。
  • pathStyleAccessEnabled: 我们在服务配置中将这个参数设置为true
  • TRUST_ALL_CERTIFICATES: 另外,我们为httpClient实例配置了所有证书信任,通过将TRUST_ALL_CERTIFICATES设置为true

4.3. 编写集成测试S3CrudService

在基础设施设置完成后,让我们为S3CrudService操作编写一些集成测试。

首先,我们创建一个桶并验证其成功创建:

var s3CrudService = new S3CrudService(s3Client);
s3CrudService.createBucket(TEST_BUCKET_NAME);

var createdBucketName = s3Client.listBuckets().buckets().get(0).name();
assertThat(TEST_BUCKET_NAME).isEqualTo(createdBucketName);

创建成功后,我们将上传一个新的S3对象。

为此,我们首先使用FileGenerator生成一个字节数组,然后createObject()方法将其保存为已创建的桶中的对象:

var fileToSave = FileGenerator.generateFiles(1, 100).get(0);
s3CrudService.createObject(TEST_BUCKET_NAME, fileToSave);

接下来,让我们调用getObject()方法,传入已保存文件的文件名,确认对象是否确实保存在S3中:

var savedFileContent = s3CrudService.getObject(TEST_BUCKET_NAME, fileToSave.getName());
assertThat(Arrays.equals(fileToSave.getContent().array(), savedFileContent)).isTrue();

最后,测试deleteObject()是否按预期工作。首先,我们调用deleteObject()方法,传入桶名和目标文件名。然后再次调用getObject()并检查结果是否为空:

s3CrudService.deleteObject(TEST_BUCKET_NAME,fileToSave.getName());

var deletedFileContent = s3CrudService.getObject(TEST_BUCKET_NAME, fileToSave.getName());
assertThat(deletedFileContent).isEmpty();

5. 结论

在这篇教程中,我们学习了如何使用S3Mock库编写依赖于AWS S3服务的集成测试,通过模拟实际的S3服务。

我们首先实现了一个基本的CRUD服务,它在S3上创建、读取和删除对象,然后使用S3Mock库编写了集成测试。

如往常一样,本文的完整实现可在GitHub上找到。