如何使用.Net IO类创建可测试代码?

时间:2012-01-07 09:56:53

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

我想创建单元可测试的代码来模拟对.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配置类需要知道explict我想要使用哪个构造函数(我刚开始使用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类的设计。

1 个答案:

答案 0 :(得分:5)

我非常成功地使用的方法是为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的定制版本。)