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

如何测试带有I/O与密封类依赖的ImageDrawingCombiner类?

如何测试依赖I/O和密封Canvas的ImageDrawingCombiner类

咱们先来拆解下你遇到的核心问题:直接依赖WPF的密封Canvas类(没法轻松Mock),加上直接做文件I/O操作,这俩确实会让单元测试变得棘手。下面是一套实用的重构+测试方案,帮你把这个类变得可测试:

1. 先解耦I/O操作:提取抽象接口

首先把文件保存的逻辑从ImageDrawingCombiner里抽离出来,用接口定义行为,这样测试时就能用模拟实现替代真实的磁盘操作。

步骤1:定义I/O操作的抽象接口

public interface IImageFileWriter
{
    void SaveImage(Uri path, ICanvasSurface surface);
}

步骤2:重构原类,通过构造函数注入依赖

修改ImageDrawingCombiner,让它依赖上面的接口,而不是自己直接处理文件:

public class ImageDrawingCombiner
{
    private readonly IImageFileWriter _imageFileWriter;

    // 注入依赖,测试时可以传Mock对象
    public ImageDrawingCombiner(IImageFileWriter imageFileWriter)
    {
        _imageFileWriter = imageFileWriter ?? throw new ArgumentNullException(nameof(imageFileWriter));
    }

    public void CombineDrawingsIntoImage(Uri path, ICanvasSurface surface)
    {
        Size size = new Size(surface.ActualWidth, surface.ActualHeight);
        // 这里保留你的绘制逻辑(如果有的话)
        
        // 把保存操作委托给接口
        _imageFileWriter.SaveImage(path, surface);
    }
}

步骤3:实现生产环境用的真实I/O类

public class DefaultImageFileWriter : IImageFileWriter
{
    public void SaveImage(Uri path, ICanvasSurface surface)
    {
        // 转换为真实Canvas对象(需要适配器支持,见下文)
        if (surface is CanvasSurfaceAdapter canvasAdapter)
        {
            Canvas canvas = canvasAdapter.WrappedCanvas;
            Size size = new Size(canvas.ActualWidth, canvas.ActualHeight);
            
            // 这里放你原来的图像渲染+保存逻辑
            RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
                (int)size.Width, (int)size.Height, 96d, 96d, PixelFormats.Pbgra32);
            renderBitmap.Render(canvas);

            using (FileStream outStream = new FileStream(path.LocalPath, FileMode.Create))
            {
                PngBitmapEncoder encoder = new PngBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
                encoder.Save(outStream);
            }
        }
        else
        {
            throw new InvalidOperationException("不支持的画布类型");
        }
    }
}

2. 处理密封Canvas的依赖:用适配器模式抽象

WPF的Canvas是密封类,没法继承Mock,所以我们用适配器模式把它包装成一个可抽象的接口。

步骤1:定义Canvas的抽象接口

只暴露你的代码实际用到的属性/方法,比如这里的尺寸:

public interface ICanvasSurface
{
    double ActualWidth { get; }
    double ActualHeight { get; }
    // 如果你的代码用到Canvas的其他方法,也加到这里
}

步骤2:创建适配器包装真实Canvas

public class CanvasSurfaceAdapter : ICanvasSurface
{
    public Canvas WrappedCanvas { get; }

    public CanvasSurfaceAdapter(Canvas canvas)
    {
        WrappedCanvas = canvas ?? throw new ArgumentNullException(nameof(canvas));
    }

    public double ActualWidth => WrappedCanvas.ActualWidth;
    public double ActualHeight => WrappedCanvas.ActualHeight;
}

3. 编写单元测试示例(用Moq框架)

现在你可以轻松Mock依赖,验证逻辑流程,不用碰真实的I/O和Canvas:

[TestClass]
public class ImageDrawingCombinerTests
{
    [TestMethod]
    public void CombineDrawingsIntoImage_ShouldTriggerSaveWithCorrectParams()
    {
        // 准备测试依赖
        var mockFileWriter = new Mock<IImageFileWriter>();
        var mockCanvas = new Mock<ICanvasSurface>();
        mockCanvas.Setup(c => c.ActualWidth).Returns(800);
        mockCanvas.Setup(c => c.ActualHeight).Returns(600);
        
        var combiner = new ImageDrawingCombiner(mockFileWriter.Object);
        var testPath = new Uri("C:\\test_output.png");

        // 执行测试方法
        combiner.CombineDrawingsIntoImage(testPath, mockCanvas.Object);

        // 验证逻辑:SaveImage是否被正确调用
        mockFileWriter.Verify(w => w.SaveImage(testPath, mockCanvas.Object), Times.Once);
    }
}

4. 额外建议:补充集成测试

单元测试覆盖了逻辑流程,但你可能需要少量集成测试,用真实的CanvasSurfaceAdapter和DefaultImageFileWriter,验证图像确实能正确保存到磁盘。这类测试可以放在单独的集成测试项目里,避免每次单元测试都做I/O操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:06:35