【发布时间】:2018-04-19 14:07:29
【问题描述】:
我想测试下面的类,但是 I/O 和密封的类依赖关系让它变得非常困难。
public class ImageDrawingCombiner
{
/// <summary>
/// Save image to a specified location in path
/// </summary>
/// <param name="path">Location to save the image</param>
/// <param name="surface">The image as canvas</param>
public void CombineDrawingsIntoImage(Uri path, Canvas surface)
{
Size size = new Size(surface.ActualWidth, surface.ActualHeight);
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)size.Width, (int)size.Height, 96d, 96d, PixelFormats.Pbgra32);
renderBitmap.Render(surface);
SaveBitmapAsPngImage(path, renderBitmap);
}
// SaveBitmapAsPngImage(path, renderBitmap);
private void SaveBitmapAsPngImage(Uri path, RenderTargetBitmap renderBitmap)
{
// Create a file stream for saving image
using (FileStream outStream = new FileStream(path.LocalPath, FileMode.OpenOrCreate))
{
// Use png encoder for our data
PngBitmapEncoder encoder = new PngBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
}
}
}
稍微重构了 SaveBitmapAsPngImage 方法:
// SaveBitmapAsPngImage(path, renderBitmap, new PngBitmapEncoder());
public void SaveBitmapAsPngImage(Uri path, BitmapSource renderBitmap, BitmapEncoder pngBitmapEncoder)
{
// Create a file stream for saving image
using (FileStream outStream = new FileStream(path.LocalPath, FileMode.OpenOrCreate))
{
// Use png encoder for our data
// push the rendered bitmap to it
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
pngBitmapEncoder.Save(outStream);
}
将其公开以供测试(代码异味?)。它仍在使用 FileStream。有些人会建议用 MemoryStream 和/或工厂模式替换它,但最终它必须保存到某个地方的图像文件中。
即使我用包装器或接口 (SystemInterface) 替换所有基于 I/O 的调用: - 应该在哪里初始化实例?在复合根?这是很多泡沫...... - 如何避免使用 DI 的“最多 3 个构造函数参数”规则? - 这个简单的功能听起来需要做很多工作
测试应确保生成图像文件。
编辑: 尝试运行@Nkosi Moq 测试,但需要修复。替换:
var renderBitmap = new Canvas();
与:
Size renderSize = new Size(100, 50);
var renderBitmap = new RenderTargetBitmap(
(int)renderSize.Width, (int)renderSize.Height, 96d, 96d, PixelFormats.Pbgra32);
测试结果:
BitmapServiceTest.BitmapService_Should_SaveBitmapAsPngImage 抛出 异常:System.IO.IOException:无法从流中读取。 ---> System.Runtime.InteropServices.COMException:来自 HRESULT 的异常: 0x88982F72 在 System.Windows.Media.Imaging.BitmapEncoder.Save(Stream 流)
似乎编码器对模拟的 Moq 流不满意。 PngBitmapEncoder 依赖项也应该通过方法注入(并在测试中模拟)吗?
【问题讨论】:
-
该类将图像保存到磁盘上的文件中。似乎这基本上就是它所做的一切。因此,如果您想验证此类的功能,您应该在调用 CombineDrawingsIntoImage 方法后验证磁盘上确实有一个文件。如果你模拟出这个功能,就没有太多要测试的东西了,是吗?
-
通过调用您的公共方法来测试您的私有方法。如果这变得笨拙,则很有可能您的班级做得太多并且 SaveBitmapAsPngImage 属于另一个班级(可以单独测试)。
-
@mm8 带有文件检查的集成测试是有意义的。在这种情况下,可单元测试的类帮助我验证 conserns 的分离并在设计时牢记 S.O.L.I.D 原则。
-
@adam-g 由于依赖关系,目前无法对公共方法进行单元测试。因此,我想了解什么是完美或最优的设计。
-
完美是一种非常强烈的赞美。这很棒。阅读他们第一段中括号中的内容,直到它沉入其中。您的班级不需要知道如何保存BitmapAsPngImage。它只需要访问它可以要求这样做的东西(示例中的 IBitmapService)。然后,您可以使用 Moq(或您最喜欢的模拟框架)注入一个假的进行测试。
标签: c# wpf nunit rhino-mocks