评论者更新:
如果您正在阅读结构化数据(例如书籍对象)(如果没有,请将字符串替换为IEnumerable)
interface BookRepository
{
IEnumerable<Books> LoadFrom(string filePath);
void SaveTo(string filePath, IEnumerable<Books> books);
}
现在,您可以使用构造函数注入来将模拟对象注入到客户类中。客户类的单元测试因此变得快速,不会触发文件系统。它们只验证依赖项上调用了正确的方法(例如,Load/Save)。
var testSubject = new Client(new Mock<BookRepository>.Object);
接下来,您需要创建一个真正的BookRepository实现,它可以使用文件(或明天如果您想要的话,可以使用Sql DB)。其他人不必知道。 为FileBasedBookRepository编写集成测试(实现上述角色),并测试使用引用文件调用Load是否提供正确的对象,以及使用已知列表调用Save是否将它们保存到磁盘。即使用真实文件 进行测试。这些测试会比较慢,因此请标记它们或将其移到单独的套件中。
[TestFixture]
[Category("Integration - Slow")]
public class FileBasedBookRepository
{
[Test]
public void CanLoadBooksFromFileOnDisk() {...}
[Test]
public void CanWriteBooksToFileOnDisk() {...}
}
最后应该有一个或多个验收测试来测试负载和保存功能。
有一个普遍的规则,要小心编写进行文件I/O的单元测试,因为它们往往太慢了。但是在单元测试中进行文件I/O并没有绝对的禁止。
在您的单元测试中设置和拆除一个临时目录,并在该临时目录中创建测试文件和目录。是的,您的测试速度将比纯CPU测试慢,但仍然很快。JUnit甚至提供了支持代码来帮助处理这种情况:在TemporaryFolder
上的@Rule
。
话虽如此,大多数文件写入代码采用以下形式:
只有第一部分真正涉及文件系统。其余部分只是使用输出流。
因此,您可以将中间和最后一部分提取到自己的方法(函数)中,该方法操作给定的输出流,而不是命名文件。然后,您可以模拟该输出流以对该方法进行单元测试。这些单元测试将非常快速。大多数编程语言已经提供了适当的输出流类。
只剩下第一部分需要进行单元测试。您只需要进行少量测试,因此您的测试套件仍然可以接受地快速运行。