【问题标题】:How to create testable code using .Net IO classes?如何使用 .Net IO 类创建可测试的代码?
【发布时间】:2012-02-04 19:56:55
【问题描述】:

我想创建可单元测试的代码来模拟对 .Net System.IO 类的调用,因此我可以真正进行单元测试,而不是依赖于文件系统。 我正在使用 SystemWrapper 类来包装 BCL 类。

我正在尝试获取一个简单的示例来查看文件是否存在。

我遇到的问题是在类中注入依赖项不起作用,因为实例化依赖项(通过StructureMap)需要知道要传递的构造函数参数,当时不可用,也有没有默认构造函数。

示例代码:

// don't want to create dependency here like so
//IFileInfoWrap fileInfoWrap = new FileInfoWrap(filename);

// using service locator (anti-pattern?!) since it can't be 
// injected in this class
var fileInfoWrap = ObjectFactory.GetInstance<IFileInfoWrap>(
    new ExplicitArguments(new Dictionary<string, object>
    {
        {"fileName", filename}
    }));

Console.WriteLine("File exists? {0}",  fileInfoWrap.Exists); 

我不喜欢的是没有注入依赖项,ObjectFactory 不应该在这里(但我看不到其他创建它的方法)。 ExplicitArguments 使它变得混乱,并且参数名称是一个魔术字符串。

为了让它工作,StructureMap 配置类需要知道我想使用哪个构造函数(我刚开始使用 StructureMap,所以这可能不是正确的设置方法):

ObjectFactory.Initialize(x =>
{
    x.Scan(scan =>
    {
        scan.AssembliesFromPath(".");
        scan.RegisterConcreteTypesAgainstTheFirstInterface();
        scan.WithDefaultConventions();
    });

    // use the correct constructor (string instead of FileInfo)
    x.SelectConstructor(() => new FileInfoWrap(null as string));

    // setting the value of the constructor
    x.For<IFileInfoWrap>()
        .Use<FileInfoWrap>()
        .Ctor<string>("fileName")
        .Is(@".");
});

有没有人找到更好的解决方案来针对 System.IO 类创建可测试的代码? 我知道部分问题在于 System.IO 类的设计。

【问题讨论】:

  • SystemWrapper 包含大部分非常容易泄漏的抽象。对 Streams、TextWriter、TextReader 等进行 IO 建模会更加简单和容易。这些类已经是抽象的,完全不需要 SystemWrapper。
  • 对流的另一票
  • 我对 SystemWrapper 的发现是,它似乎是一个很好的接口包装器,但由于原始类的工作方式,它仍然是一个死胡同。例如,无法正确模拟返回 FileInfo 对象数组。滚动我自己的更简化的包装器,它不必模仿现有的类,而更多的工作确实会导致一个可行的解决方案恕我直言。

标签: c# dependency-injection tdd inversion-of-control base-class-library


【解决方案1】:

我非常成功地使用的一种方法是为 System.IO 和 FCL 的其他部分中找到的类型滚动我自己的代理类型。例如。我想依赖System.IO.File。我创建了一个名为System.IO.Proxies 的库,并添加了一个具体类型File 和一个接口IFile。接口IFile 公开了与我从System.IO.File 需要的所有成员等效的成员,而具体类型通过将方法调用转发到System.IO.File 之外什么都不做来实现这些成员。 System.IO.Proxies 被排除在单元测试和代码覆盖范围之外。在我的消费程序集中,我只依赖System.IO.Proxies,具体来说,我只依赖IFile。这样我就可以轻松地模拟这种依赖关系,并为我的消费程序集实现 100% 的代码覆盖率。

(请注意,这是我的 more general answer 对上一个问题的定制版本。)

【讨论】:

  • 是的,我也是这样结束的。可以将呼叫委托给例如System.IO.FileInfo 到一个单独的类。滚动您自己的代理确实需要为每个类创建代理,这可能是相当多的工作,当然它可以与 BCL 类所需的功能一起增长。此外,当返回其他类型(FileInfo)时,应在代理中创建所有必需的属性(名称、全名、长度)。人们会认为这个问题已经“解决”了,避免了每个人的代理。
  • 非常成功地使用了相同的方法。通常你不需要像 File 这样的类的所有方法/属性/事件。对于你确实需要的少数人来说,编写这样的包装器真的很容易。
猜你喜欢
  • 2015-11-07
  • 1970-01-01
  • 2013-08-17
  • 2015-10-16
  • 2019-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多