如何验证是否已在文件系统上复制流

时间:2013-10-04 07:32:17

标签: unit-testing file-io mocking moq

我想编写一个单元测试,检查流已经复制到磁盘上了:

问题是CopyTo方法不是虚拟的所以我不能使用

inputMemoryStreamMock.Verify(c => c.CopyTo(outputMemoryStreamMock.Object));

我不知道如何模拟流: - /

这是我的测试方法:

    [TestMethod]
    public void Save_Stream_On_DestinationPath()
    {
        // Arrange
        string fileName = "filename.pdf";
        DateTime date = new DateTime(2013, 9, 27);

        var serverConfigMock = new Mock<IServerConfigurationManager>();
        serverConfigMock.Setup(config => config.ReportingBasePath).Returns(@"c:\reportsFolder");

        var factoryReportFileResultMock = new Mock<IReportFileResultFactory>();

        var timeManagementMock = new Mock<ITimeManagement>();
        timeManagementMock.Setup(c => c.GetServerDate()).Returns(date);

        var fileSystemMock = new Mock<IFileSystem>();
        var fileInfoFactory = new Mock<IFileInfoFactory>();
        var directoryInfoBaseMock = new Mock<DirectoryInfoBase>();
        var inputMemoryStreamMock = new Mock<Stream>();
        var outputMemoryStreamMock = new Mock<Stream>();

        var reportFileHelper = new ReportFileHelper(serverConfigMock.Object, factoryReportFileResultMock.Object, fileSystemMock.Object);


        inputMemoryStreamMock.Setup(c => c.CanRead).Returns(true);
        outputMemoryStreamMock.Setup(c => c.CanWrite).Returns(true);
        outputMemoryStreamMock.Setup(c => c.CanWrite).Returns(true);

        fileSystemMock.Setup(c => c.FileInfo).Returns(fileInfoFactory.Object);
        fileSystemMock.Setup(c => c.File.Create(It.IsAny<string>())).Returns(outputMemoryStreamMock.Object);
        fileSystemMock.Setup(c => c.Directory.CreateDirectory(It.IsAny<string>())).Returns(directoryInfoBaseMock.Object);
        // Act  
        reportFileHelper.Save(inputMemoryStreamMock.Object, fileName, timeManagementMock.Object);

        // Assert
        inputMemoryStreamMock.Verify(c => c.CopyTo(outputMemoryStreamMock.Object));

    }

以下是测试方法:

    public void Save(Stream portfolioReportFileInfoBase,  string destinationName, ITimeManagement timeManagement)
    {
        string destinationPath = GetDestinationPath(timeManagement);
        string destinationFileUri = Path.Combine(destinationPath, destinationName);

        FileSystem.Directory.CreateDirectory(destinationPath);

        using (var fileStream = FileSystem.File.Create(destinationFileUri))
        {
            portfolioReportFileInfoBase.CopyTo(fileStream);
        }
    }

谢谢

2 个答案:

答案 0 :(得分:3)

我接受了几件事:

<强>可读性

我不确定你的测试读得非常好。为什么声明一个fileName变量,就好像它甚至没有断言那样重要?同样适用于date。这会使您的测试变得杂乱无章。内联值或Anonymous Variables可以提供更好的信噪比。

那你为什么要设置CreateDirectory()来回复一些东西?你永远不会使用那个回报值,对吗?我建议你摆脱它和directoryInfoBaseMock变量。与FileInfofileInfoFactory相同。您的测试需要包含最低限度来设置要验证的对象,仅此而已。如果你需要构建一个深度复杂的对象图来测试一个简单的东西,那么通常会出现问题。

<强>设计

引发警报的第二件事是Save()混合了多个语言级别。当你阅读它时,似乎是在同一时间:

  • 投资组合和报告

  • 时间管理

  • 很多低级文件系统的东西

这通常表明对象试图处理太多,违反了单一责任原则。

我要做的是将这些职责分配到不同的对象中。

为什么Save()需要了解时间管理?当然,我们使用时间管理来计算目标路径,但是如果我们直接将目标路径直接传递给Save()方法,它是否会更好地与语言级别和责任级别保持一致?

为什么Save()需要了解投资组合和报告?好吧,基本没有理由。您只需将portfolioReportFileInfoBase重命名为... stream

然后可以将

Save()GetPath()移动到单独的低级文件系统包装类(IFileSystem是一个完美的候选者),消除ReportFileHelper和{{1}}之间的紧密耦合文件系统。

请勿尝试使用模拟测试您的报告是否已写入磁盘。通过集成测试来完成它。 Don't mock types you don't own。仅使用模拟来测试您自己的类如何相互通信。将外部库/平台包装到包装器对象中,并在应用程序的边界编写集成测试,以验证包装器是否能够很好地与这些外部库/平台配合使用。

答案 1 :(得分:1)

如果您不愿意更改Save方法的代码,则必须使用隔离框架(如TypeMock Isolator或Microsoft Fakes)来测试此案例,因为CopyTo不能被Moq嘲笑。

您已经封装了对IFileSystem后面的文件系统的访问权限;为什么不添加像CopyStreamToPath这样的方法?

public void Save(Stream portfolioReportFileInfoBase,  string destinationName, ITimeManagement timeManagement)
{
    string destinationPath = GetDestinationPath(timeManagement);
    string destinationFileUri = Path.Combine(destinationPath, destinationName);

    FileSystem.Directory.CreateDirectory(destinationPath);
    FileSystem.CopyStreamToPath(portfolioReportFileInfoBase, destinationFileUri);
}

和测试:

fileSystemMock.Verify(c => c.CopyStreamToPath(inputMemoryStreamMock.object, It.IsAny<string>()));