You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot对接鉴权远程系统测试最佳实践:Azure Blob测试优化问询

问题描述

我编写了依托Azure SDK for Blobs实现Blob存储交互的代码,没有启动live应用测试,而是直接编写了Spring Boot JUnit测试用例,不使用任何Mock框架,直接对接真实Blob存储实例验证Java方法逻辑。
现在我需要重构测试结构,解决当前的两个痛点:

  • 之前为了赶进度把凭证硬编码在源文件中,即使是私有仓库也不希望提交凭证到代码库
  • 需要让这些JUnit测试可以在Azure DevOps流水线中正常运行

现有测试逻辑

测试代码会创建临时容器,执行Blob的存、取、删操作,用GUID生成唯一私有工作区,测试结束后自动清理,现有代码如下:

@SpringBootTest(classes = FileRepositoryServiceAzureBlobImplTest.class)
@SpringBootConfiguration
@TestConfiguration
@TestPropertySource(properties = {
        "azure-storage-container-name:amlcbackendjunit",
        "azure-storage-connection-string:[not going to post it on Stackoverflow before rotating it]"
})
class FileRepositoryServiceAzureBlobImplTest {

    private static final Resource LOREM_IPSUM = new ClassPathResource("loremipsum.txt", FileRepositoryServiceAzureBlobImplTest.class);
    private FileRepositoryServiceAzureBlobImpl uut;
    private BlobContainerClient blobContainerClient;
    private String loremChecksum;

    @Value("${azure-storage-connection-string}")
    private String azureConnectionString;
    @Value("${azure-storage-container-name}")
    private String azureContainerName;

    @BeforeEach
    void beforeEach() throws IOException {
        String containerName = azureContainerName + "-" + UUID.randomUUID();
        blobContainerClient = new BlobContainerClientBuilder()
                .httpLogOptions(new HttpLogOptions().setApplicationId("az-sp-sb-aml"))
                .clientOptions(new ClientOptions().setApplicationId("az-sp-sb-aml"))
                .connectionString(azureConnectionString)
                .containerName(containerName)
                .buildClient();

        blobContainerClient.create();
        uut = spy(new FileRepositoryServiceAzureBlobImpl(blobContainerClient));
        try (InputStream loremIpsumInputStream = LOREM_IPSUM.getInputStream()) {
            loremChecksum = DigestUtils.sha256Hex(loremIpsumInputStream);
        }

        blobContainerClient
                .getBlobClient("fox.txt")
                .upload(BinaryData.fromString("The quick brown fox jumps over the lazy dog"));
    }

    @AfterEach
    void afterEach() throws IOException {
        blobContainerClient.delete();
    }

    @Test
    void store_ok() {
        String desiredFileName = "loremIpsum.txt";

        FileItemDescriptor output = assertDoesNotThrow(() -> uut.store(LOREM_IPSUM, desiredFileName));
        assertAll(
                () -> assertThat(output, is(notNullValue())),
                () -> assertThat(output, hasProperty("uri", hasToString(Matchers.startsWith("azure-blob://")))),
                () -> assertThat(output, hasProperty("size", equalTo(LOREM_IPSUM.contentLength()))),
                () -> assertThat(output, hasProperty("checksum", equalTo(loremChecksum))),
                () -> {
                    String localPart = substringAfter(output.getUri().toString(), "azure-blob://");
                    assertAll(
                            () -> assertTrue(blobContainerClient.getBlobClient(localPart).exists())
                    );
                }
        );
    }
}

生产环境(含SIT/UAT)中Spring Boot应用会从容器环境获取存储连接字符串配置,且当前测试不需要依赖Spring和@TestPropertySource,没有用到上下文Bean。

需求

修改现有测试满足以下要求:

  1. 连接字符串与代码完全解耦
  2. 连接字符串不存在时自动忽略该测试(适配开发者首次拉取项目快速启动的场景,正式应用无连接字符串时会自动降级用本地临时目录)
  3. 配置了对应环境变量的Azure DevOps流水线中可以正常执行测试

现有Azure DevOps构建任务配置如下:

- task: Gradle@2
  displayName: Build with Gradle
  inputs:
    gradleWrapperFile: gradlew
    gradleOptions: -Xmx3072m $(gradleJavaProperties)
    options: -Pci=true -PbuildId=$(Build.BuildId) -PreleaseType=${{parameters.releaseType}}
    jdkVersionOption: 1.11
    jdkArchitectureOption: x64
    publishJUnitResults: true
    sqAnalysisEnabled: true
    sqGradlePluginVersionChoice: specify
    sqGradlePluginVersion: 3.2.0
    testResultsFiles: '$(System.DefaultWorkingDirectory)/build/test-results/**/TEST-*.xml'
    tasks: clean build

解决方案

第一步:重构测试代码,移除Spring依赖,新增条件判断

用JUnit 5的@EnabledIfEnvironmentVariable注解配合环境变量读取配置,不需要依赖Spring上下文,更轻量,没有配置对应环境变量时测试会自动跳过:

import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import java.util.Optional;
import java.util.UUID;
// 其他原有import保持不变

@EnabledIfEnvironmentVariable(named = "AZURE_STORAGE_CONNECTION_STRING", matches = ".+", disabledReason = "未配置Azure存储连接字符串,跳过Blob存储集成测试")
class FileRepositoryServiceAzureBlobImplTest {

    private static final Resource LOREM_IPSUM = new ClassPathResource("loremipsum.txt", FileRepositoryServiceAzureBlobImplTest.class);
    // 直接从环境变量读取配置,无需@Value注解
    private static final String AZURE_CONNECTION_STRING = System.getenv("AZURE_STORAGE_CONNECTION_STRING");
    private static final String AZURE_CONTAINER_NAME = Optional.ofNullable(System.getenv("AZURE_STORAGE_CONTAINER_NAME"))
            .orElse("amlcbackendjunit");
    
    private FileRepositoryServiceAzureBlobImpl uut;
    private BlobContainerClient blobContainerClient;
    private String loremChecksum;

    @BeforeEach
    void beforeEach() throws IOException {
        String containerName = AZURE_CONTAINER_NAME + "-" + UUID.randomUUID();
        blobContainerClient = new BlobContainerClientBuilder()
                .httpLogOptions(new HttpLogOptions().setApplicationId("az-sp-sb-aml"))
                .clientOptions(new ClientOptions().setApplicationId("az-sp-sb-aml"))
                .connectionString(AZURE_CONNECTION_STRING)
                .containerName(containerName)
                .buildClient();

        blobContainerClient.create();
        uut = spy(new FileRepositoryServiceAzureBlobImpl(blobContainerClient));
        try (InputStream loremIpsumInputStream = LOREM_IPSUM.getInputStream()) {
            loremChecksum = DigestUtils.sha256Hex(loremIpsumInputStream);
        }

        blobContainerClient
                .getBlobClient("fox.txt")
                .upload(BinaryData.fromString("The quick brown fox jumps over the lazy dog"));
    }

    @AfterEach
    void afterEach() {
        // 改为deleteIfExists避免容器不存在时报错
        blobContainerClient.deleteIfExists();
    }

    // 测试方法保持原有逻辑不变
    @Test
    void store_ok() {
        String desiredFileName = "loremIpsum.txt";

        FileItemDescriptor output = assertDoesNotThrow(() -> uut.store(LOREM_IPSUM, desiredFileName));
        assertAll(
                () -> assertThat(output, is(notNullValue())),
                () -> assertThat(output, hasProperty("uri", hasToString(Matchers.startsWith("azure-blob://")))),
                () -> assertThat(output, hasProperty("size", equalTo(LOREM_IPSUM.contentLength()))),
                () -> assertThat(output, hasProperty("checksum", equalTo(loremChecksum))),
                () -> {
                    String localPart = substringAfter(output.getUri().toString(), "azure-blob://");
                    assertTrue(blobContainerClient.getBlobClient(localPart).exists());
                }
        );
    }
}

第二步:本地开发适配

开发者本地如果需要运行该测试,只需在本机配置AZURE_STORAGE_CONNECTION_STRING环境变量即可,不需要修改代码,也不会把凭证提交到代码仓库。没有配置该环境变量时测试会自动跳过,不影响项目整体构建。

第三步:Azure DevOps流水线配置

首先在流水线的变量库中添加AZURE_STORAGE_CONNECTION_STRING,设置为保密变量避免泄露。然后修改Gradle任务配置,将环境变量传递给测试进程:

- task: Gradle@2
  displayName: Build with Gradle
  inputs:
    gradleWrapperFile: gradlew
    env:
      AZURE_STORAGE_CONNECTION_STRING: $(AZURE_STORAGE_CONNECTION_STRING)
    gradleOptions: -Xmx3072m $(gradleJavaProperties)
    options: -Pci=true -PbuildId=$(Build.BuildId) -PreleaseType=${{parameters.releaseType}}
    jdkVersionOption: 1.11
    jdkArchitectureOption: x64
    publishJUnitResults: true
    sqAnalysisEnabled: true
    sqGradlePluginVersionChoice: specify
    sqGradlePluginVersion: 3.2.0
    testResultsFiles: '$(System.DefaultWorkingDirectory)/build/test-results/**/TEST-*.xml'
    tasks: clean build

如果Gradle测试配置默认会剥离环境变量,还需要在build.gradle中添加测试进程的环境变量传递配置:

test {
    environment "AZURE_STORAGE_CONNECTION_STRING", System.getenv("AZURE_STORAGE_CONNECTION_STRING")
    environment "AZURE_STORAGE_CONTAINER_NAME", System.getenv("AZURE_STORAGE_CONTAINER_NAME")
    // 其他测试配置保持不变
}

内容的提问来源于stack exchange,提问作者usr-local-ΕΨΗΕΛΩΝ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 09:57:03