如何测试带有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
相关产品推荐
相关产品推荐

