如何将文件保存到磁盘?

时间:2010-08-01 11:16:11

标签: unit-testing tdd

我知道强烈建议在与文件系统分离时运行单元测试 ,因为如果你在测试中触摸文件系统,你也可以测试文件系统本身。好的,这是合理的 我的问题是,如果我想测试文件保存到磁盘,我该怎么办?与数据库一样,我将一个负责数据库访问的接口分开,然后为我的测试创建另一个实现?或者可能还有其他方式吗?

3 个答案:

答案 0 :(得分:40)

我对此的态度严重偏向我刚刚阅读的Growing Object-Oriented Software Guided by Tests(GOOS)书,但这是我今天所知道的最好的。具体做法是:

  • 创建一个接口,从代码中抽象出文件系统。模拟它作为协作者/依赖者需要这个类的地方。这可以使您的单元测试快速并快速反馈。
  • 创建测试接口实际实现的集成测试。即验证调用Save()实际上是将文件持久存储到磁盘并具有写入内容(使用引用文件或解析它应该包含的一些内容)
  • 创建一个测试整个系统的验收测试 - 端到端。在这里,您可以验证是否已创建文件 - 此测试的目的是确认实际实现是否正确连接/插入。

评论者更新:

如果您正在阅读结构化数据(例如Book对象)(如果没有替换IEnumerable的字符串)

interface BookRepository
{
  IEnumerable<Books> LoadFrom(string filePath);
  void SaveTo(string filePath, IEnumerable<Books> books);
}

现在,您可以使用构造函数注入将mock注入客户端类。因此客户类单元测试很快; 不要点击文件系统。他们只是验证在依赖项上调用了正确的方法(例如加载/保存)

var testSubject = new Client(new Mock<BookRepository>.Object);

接下来,您需要创建BookRepository的实际实现,该实现可以在File(或明天的Sql DB,如果您需要)的情况下运行。没有人知道。 为FileBasedBookRepository(实现上述角色)编写集成测试,并测试使用引用文件调用Load给出正确的对象并使用已知列表调用Save,将它们保存到磁盘。即使用真实文件这些测试会很慢,所以用标签标记它们或将它移到一个单独的套件中。

[TestFixture]
[Category("Integration - Slow")]
public class FileBasedBookRepository 
{
  [Test]
  public void CanLoadBooksFromFileOnDisk() {...}
  [Test]
  public void CanWriteBooksToFileOnDisk() {...}
}

最后应该有一个/多个验收测试,可以进行加载和保存。

答案 1 :(得分:6)

您可以代替将文件名传递给保存功能,传递Stream,TextWriter或类似功能。然后,在测试时,您可以传递基于内存的实现,并验证是否写入了正确的字节,而不实际将任何内容写入磁盘。

要测试问题和异常,您可以查看模拟框架。这可以帮助您在保存过程中的特定点人为地生成特定异常,并测试您的代码是否正确处理它。

答案 2 :(得分:3)

一般规则是谨慎编写执行文件I / O的单元测试,因为它们往往太慢。但是在单元测试中没有绝对禁止文件I / O.

在您的单元测试中,设置并拆除临时目录,并在该临时目录中创建测试文件和目录。是的,您的测试将比纯CPU测试慢,但它们仍然会很快。 JUnit甚至还有支持代码来帮助解决这个问题:@Rule上的TemporaryFolder

也就是说,大多数文件编写代码采用以下形式:

  1. 打开文件的输出流。此代码必须处理丢失的文件或文件权限问题。您将需要测试它是否打开文件并应对这些失败情况。
  2. 写入输出流。这必须以正确的格式写入,这是需要最多测试的最复杂的部分。它必须在写入输出流时处理I / O错误,尽管这些错误在实践中很少见。
  3. 关闭输出流。这必须在关闭流时处理I / O错误,尽管这些错误在实践中很少见。
  4. 只有第一个真正处理文件系统。其余的只使用输出流。

    因此,您可以将中间和最后一部分提取到自己的方法(函数),该方法操作给定的输出流,而不是命名文件。然后,您可以模拟该输出流以对该方法进行单元测试。那些单元测试将非常快。大多数编程语言已经提供了合适的输出流类。

    只留下第一部分进行单元测试。您只需要进行一些测试,因此您的测试套件应该仍然可以接受。