【问题标题】:Unit Testing a Class That Uses the File System对使用文件系统的类进行单元测试
【发布时间】:2011-01-15 19:08:43
【问题描述】:

我有一个输出简单报告文件的类。它从 XML 文件中读取一些记录 ID 号:每个用于查找存储在数据库中的匹配记录。然后它将每条记录的详细信息写入 CSV 文件。

我想知道 - 组织它以便易于测试但遵循封装原则的最佳方式是什么?我认为最好避免与文件系统交互,除非绝对必要,所以我正在处理 Stream 对象。在进行单元测试时,我可以使用部分模拟对象来覆盖读取或写入文件的位。

我也不确定何时/何地处理流而不使单元测试变得棘手。看来我可能不得不将流公开给单元测试。

我的项目使用 NHibernate 进行数据访问,使用 Spring .NET 进行依赖注入,使用 Rhino.Mocks 进行单元测试。

目前我有类似的东西:

public class ReportBiz
{
    //Access to repository, populated by Spring
    internal ICardRequestDAO CardRequestData { get;set;} 

    //This normally returns a FileStream containing data from the XML file. When testing this is overridden by using a Rhino.Mocks partial mock and returns a MemoryStream
    internal virtual Stream GetSourceStream()
    {
        //Load file and return a Stream
        ...
    }

    //This normally returns a FileStream where CSV data is saved. When testing this is overridden by using a Rhino.Mocks partial mock and returns a MemoryStream
    internal virtual Stream GetOutputStream()
    {
        //Create file where CSV data gets saved and return a Stream
        ...
    }

    public void CreateReportFile()
    {
        Stream sourceStream = GetSourceStream();
        ...

        //Create an XmlDocument instance using the stream
        //For each XML element, get the corresponding record from the database
        //Add record data to CSV stream     
        ...
    }
    }

使用某种自定义工厂或其他东西并将流传递给构造函数会更好吗?但是如果涉及到一些业务逻辑呢,例如文件名是根据查询结果确定的?

还是说整个文件访问都不是问题?

抱歉,如果我遗漏了一些明显的东西。如有任何建议,我将不胜感激。

【问题讨论】:

  • 我需要问:你的 XML 是做什么的?它是否只包含指向数据库记录的键列表?
  • 除了数据库键,它还包含我需要的与记录相关的其他三个字段。这些也会在报告中使用。

标签: c# .net unit-testing stream class-design


【解决方案1】:

在保留对一次性资源生命周期的控制权的同时,使文件访问可模拟的最简单方法是将StreamFactory 注入您的类:

public class ReportBiz {

    private IStreamFactory streamFactory;

    public ReportBiz(IStreamFactory streamFactory) {
        this.streamFactory = streamFactory
    }

    public void CreateReportFile() {
        using(Stream stream = this.streamFactory.CreateStream()) {
            // perform the important work!
        }
    }
}

当涉及更多业务逻辑时,您的工厂方法可能会更复杂一些,但不会太多:

public void CreateReportFile() {
    string sourcePath   = this.otherComponent.GetReportPath();
    using(Stream stream = this.streamFactory.CreateStream(sourcePath)) {
        // perform the important work!
    }
}

【讨论】:

  • 感谢您的回复。但是我仍然对如何验证 CSV 报告文件流以确保我写出正确的数据感到有点困惑。如果输出流是 using 语句的一部分,那么我无法对其进行测试。我是否应该只在类级别保留对它的引用并避免在 ReportBiz 实例本身被释放之前释放它?
  • 我没有使用过 Rhino Mocks,但是在 NMock 中,我会注入一个模拟 IStreamFactory,存根调用它的 CreateStream 方法并让它返回一个模拟 Stream设置期望值(对Stream.Write 的调用)以验证我是否写入了正确的数据。
【解决方案2】:

您必须以某种方式模拟您的流以避免将它们暴露给测试,因为您的业务逻辑的核心是正确获取输入字符串和输出字符串。

在这种情况下,关键是您必须认识到数据流中有三个阶段:

  • 读取数据:解析数据是一个独特而独特的问题
  • 输出的内容:你必须验证给定正确的数据,你有正确的 CSV 字符串输出
  • 将该内容写入文件

老实说,整个文件写入问题并不是什么大问题——.NET Framework 提供了经过良好测试的文件写入功能,它超出了您的测试范围。您将遇到的大多数问题将是您向文件吐出的 CSV 的准确性。

作为预防措施,我建议不要滚动您自己的 CSV 编写器。您应该尝试查找已经存在的 CSV 库 -- there are a lot of them over the net

【讨论】:

    猜你喜欢
    • 2021-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-14
    • 2011-12-18
    • 2010-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多