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

Azure Blob文件上传功能单元/集成测试编写方法咨询

Azure Blob上传方法测试实现方案

你现有的代码直接耦合了Azure Storage SDK的具体实现,没有做依赖隔离,纯单元测试没法直接写,分单元测试、集成测试两种场景的实现方式如下:

单元测试实现

单元测试的核心是隔离外部Blob存储依赖,只验证接口本身的参数校验、返回逻辑是否正确,首先要做最小程度的代码重构,不然没法Mock外部依赖:

  • 把Blob上传的核心逻辑抽成接口服务,通过依赖注入注入到控制器,不要在接口方法里直接写连接串解析、Blob客户端初始化的逻辑
  • 把参数校验逻辑前置,提前拦截无效请求

重构后的控制器代码参考:

// 先定义Blob操作抽象接口
public interface IBlobStorageService
{
    Task<string> UploadAsync(Stream fileStream, string fileName);
}

// 你的原有Blob上传逻辑移到这个接口的实现类里,注册到DI容器
public class AzureBlobStorageService : IBlobStorageService
{
    private readonly string _connectionString;
    private readonly string _containerName;
    public AzureBlobStorageService(IConfiguration config)
    {
        _connectionString = config.GetValue<string>("BlobConnectionString");
        _containerName = config.GetValue<string>("BlobContainerName");
    }

    public async Task<string> UploadAsync(Stream fileStream, string fileName)
    {
        CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(_connectionString);
        CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference(_containerName);
        // 可以加容器不存在时自动创建的逻辑,避免运行时报错
        await container.CreateIfNotExistsAsync();
        CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
        await blockBlob.UploadFromStreamAsync(fileStream);
        return blockBlob.Uri.OriginalString;
    }
}

// 控制器改造后
[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase
{
    private readonly IBlobStorageService _blobStorageService;
    public FileController(IBlobStorageService blobStorageService)
    {
        _blobStorageService = blobStorageService;
    }

    [HttpPost("upload")]
    public async Task<IActionResult> UploadFile(IFormFile files)
    {
        if (files == null || files.Length == 0)
            return BadRequest("请上传有效文件");
        await using var stream = files.OpenReadStream();
        var fileUri = await _blobStorageService.UploadAsync(stream, files.FileName);
        return Ok(fileUri);
    }
}

重构完成后,就可以用Moq之类的Mock框架模拟Blob服务,写不依赖真实Azure资源的单元测试,参考用例(xUnit框架):

public class FileControllerTests
{
    [Fact]
    public async Task Upload_ValidFile_ReturnsCorrectUri()
    {
        // 构造Mock的Blob服务
        var mockBlobService = new Mock<IBlobStorageService>();
        var testExpectedUri = "https://test-storage.blob.core.windows.net/container/test.jpg";
        mockBlobService.Setup(s => s.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>()))
            .ReturnsAsync(testExpectedUri);
        
        var controller = new FileController(mockBlobService.Object);
        // 构造模拟上传文件
        using var testStream = new MemoryStream(Encoding.UTF8.GetBytes("test content"));
        var testFormFile = new FormFile(testStream, 0, testStream.Length, "files", "test.jpg")
        {
            Headers = new HeaderDictionary(),
            ContentType = "image/jpeg"
        };

        // 执行上传方法
        var result = await controller.UploadFile(testFormFile);

        // 校验结果
        var okResult = Assert.IsType<OkObjectResult>(result);
        Assert.Equal(testExpectedUri, okResult.Value);
        // 确认上传方法确实被调用
        mockBlobService.Verify(s => s.UploadAsync(It.IsAny<Stream>(), "test.jpg"), Times.Once);
    }

    [Fact]
    public async Task Upload_EmptyFile_ReturnsBadRequest()
    {
        var mockBlobService = new Mock<IBlobStorageService>();
        var controller = new FileController(mockBlobService.Object);

        var result = await controller.UploadFile(null);
        
        Assert.IsType<BadRequestObjectResult>(result);
        // 空文件不应该触发上传逻辑
        mockBlobService.Verify(s => s.UploadAsync(It.IsAny<Stream>(), It.IsAny<string>()), Times.Never);
    }
}

注:如果你坚持不重构代码,旧版WindowsAzure.Storage包里的CloudBlobClient等类没有对应抽象接口,没法直接Mock,只能写集成测试,单元测试无法覆盖核心逻辑。

集成测试实现

集成测试用来验证从接口请求到Blob真实上传的全链路逻辑,不需要隔离外部依赖,推荐优先用本地Azurite存储模拟器做测试,不需要消耗Azure云资源,也不会影响生产数据。
测试实现参考(用ASP.NET Core自带的WebApplicationFactory搭建测试宿主):

public class BlobUploadIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _testFactory;
    public BlobUploadIntegrationTests(WebApplicationFactory<Program> factory)
    {
        // 替换测试环境配置,指向本地Azurite模拟器
        _testFactory = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureAppConfiguration((ctx, cfg) =>
            {
                cfg.AddInMemoryCollection(new Dictionary<string, string>
                {
                    ["BlobConnectionString"] = "UseDevelopmentStorage=true", // Azurite默认连接串
                    ["BlobContainerName"] = "test-upload-container"
                });
            });
        });
    }

    [Fact]
    public async Task Upload_FullPipeline_ReturnsAccessibleBlobUri()
    {
        var httpClient = _testFactory.CreateClient();
        // 构造文件上传请求
        using var formContent = new MultipartFormDataContent();
        var testFileBytes = new byte[] { 0x01, 0x02, 0x03 }; // 模拟测试图片二进制内容
        var fileContent = new ByteArrayContent(testFileBytes);
        fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/png");
        formContent.Add(fileContent, "files", "test-upload.png");

        // 调用上传接口
        var response = await httpClient.PostAsync("/api/file/upload", formContent);
        response.EnsureSuccessStatusCode();
        var returnedUri = await response.Content.ReadAsStringAsync();

        // 校验返回结果
        Assert.True(Uri.IsWellFormedUriString(returnedUri, UriKind.Absolute));
        Assert.Contains("test-upload.png", returnedUri);

        // 可选:访问返回的URI,校验文件内容和上传的一致
        var blobClient = new HttpClient();
        var uploadedBytes = await blobClient.GetByteArrayAsync(returnedUri);
        Assert.Equal(testFileBytes, uploadedBytes);
    }
}

注意事项:

  • 本地跑集成测试需要先启动Azurite模拟器,VS安装Azure开发负载后会自带这个工具
  • 如果用真实Azure存储账号做测试,必须用和生产隔离的专用测试账号,不要在测试代码里写生产环境连接串
  • 测试跑完建议加清理逻辑,删除测试过程中上传的临时文件,避免冗余存储占用

内容的提问来源于stack exchange,提问作者EdwinThePower

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:30:52