【问题标题】:Unit Testing Methods With File IO使用文件 IO 的单元测试方法
【发布时间】:2013-05-14 11:06:11
【问题描述】:

我正在努力养成编写单元测试的习惯,我以前写过一些,但它们通常都很基础......我想开始转向 TDD,因为我想提高我的代码质量(设计和结构) - 减少耦合,同时希望减少滑入可测试构建的回归数量。

我开始着手一个相对简单的项目。生成的程序监视一个文件夹,然后对该文件夹中的文件进行操作。

下面是从项目中提取的一些代码的典型示例:

private string RestoreExtension(String file)
    {
        var unknownFile = Path.GetFileName(file);
        var ignoreDir = Path.GetDirectoryName(file) + "\\Unknown";
        string newFile;

        //We need this library for determining mime
        if (CheckLibrary("urlmon.dll"))
        {
            AddLogLine("Attempting to restore file extension");
            var mime = GetMimeType(BufferFile(file, 256));
            var extension = FileExtensions.FindExtension(mime);
            if (!String.IsNullOrEmpty(extension))
            {
                AddLogLine("Found extension: " + extension);
                newFile = file + "." + extension;
                File.Move(file, newFile);
                return newFile;
            }
        }
        else
        {
            AddLogLine("Unable to load urlmon.dll");
        }

        AddLogLine("Unable to restore file extension");

        if (!Directory.Exists(ignoreDir))
        {
            Directory.CreateDirectory(ignoreDir);
        }
        var time = DateTime.Now;
        const string format = "dd-MM-yyyy HH-mm-ss";

        newFile = ignoreDir + "\\" + unknownFile + "-" + time.ToString(format);
        File.Move(file, newFile);

        return String.Empty;
    }

问题:

如何使用 IO 测试方法? 我真的不想使用真正的文件,因为这会很慢(而且更多的是集成测试?),但我真的看不到另一种方式。我可以添加一个可切换的 IO 抽象层,但这听起来可能会使代码不必要地复杂化......

这样的项目值得进行单元测试吗? 我的意思是,是不是太简单了。一旦你去掉 .Net 和 3rd 方库调用,剩下的就不多了……那么,使它可测试的工作量意味着它不是一个好的测试候选者吗?

这个项目中的很多方法都是私有的,因为这个项目恰好是一个 Windows 服务。我读过你应该只测试外部可见的方法(公共或通过接口公开),在这种情况下不太可能但是我想公开我自己的任何方法。那么这个项目是否值得测试(因为 id 可能需要将方法访问修饰符更改为 public 或添加一些属性以便 NUnit 可以看到它们)?

【问题讨论】:

  • 我知道这是旧的,但使用 Microsoft Fakes 更容易

标签: c# unit-testing mocking tdd nunit


【解决方案1】:

关于如何测试文件 I/O: 执行此操作的常用方法是将文件 I/O 封装在包装器类中。例如:

public class FileHandler : IFileHandler
{
     public string GetFilename(string name)
     {
         return System.IO.GetFileName(name);
     }

    public string GetDirectoryName(string directory)
    {
         return System.IO.GetDirectoryName(directory);
    }
}

public interface IFileHandler
{
      string GetFilename(string name);
      string GetDirectoryName(string directory);
}

在您的测试方法中,您仅针对接口进行编程。当您运行单元测试时,您将使用一个返回预定义值的模拟对象。为此,您可以使用Moq

这需要做一些工作,但您也不需要测试文件 I/O。

最好的 一月

【讨论】:

    【解决方案2】:

    如何使用 IO 测试方法?

    通过在 .NET 的 IO 库上引入抽象,然后在应用程序中使用真实实现 (BCL),并在单元测试中使用假(模拟)实现。这种抽象很容易自己编写,但那些项目已经存在(例如System.IO.Abstraction),所以重新发明轮子毫无意义,尤其是在这样微不足道的事情上。

    我真的不想使用真实文件,因为这会很慢

    这是正确的,在测试中你想使用fake (mock) objects

    这样的项目值得进行单元测试吗?

    每个项目都值得测试,您将以一种或另一种方式对其进行测试(在最极端的情况下,通过手动执行应用程序并进行良好的、老式的点击等待测试)。

    请注意,手动测试复杂解析器方法的 10 个边缘情况几乎总是太耗时,每次都需要您加载大文件并等待结果。这就是隔离环境中的自动化测试有很大帮助的地方。您的代码也是如此——文件系统、Web 服务,它们都会在您进入实际业务逻辑之前引入大量开销。必须隔离。

    这个项目中的很多方法都是私有的(...)我读过你应该只测试外部可见的方法

    再次正确,您应该测试公共合约。然而,如果私有的东西变得足够大来测试它,大多数时候它是一个很好的指标,它也足够大到可以把它放在自己的类中。请注意,仅为单元测试使用更改成员访问修饰符并不是最好的主意。几乎总是提取类/方法重构更好。

    【讨论】:

      【解决方案3】:

      您可以使用Microsoft Fakes 做到这一点。首先 generate a fake assembly 用于 System.dll - 或任何其他包,然后模拟预期返回,如下所示:

      using Microsoft.QualityTools.Testing.Fakes;
      ...
      using (ShimsContext.Create())
      {
           System.IO.Fakes.ShimDirectory.ExistsString = (p) => true;
           System.IO.Fakes.ShimFile.MoveStringString = (p,n) => {};
           // .. and other methods you'd like to test
      
           var result = RestoreExtension("C:\myfile.ext");
           // then you can assert with this result 
      }
      

      【讨论】:

        【解决方案4】:

        我对 TDD 也很陌生,但我想我可以给你一些建议:

        1. 关于 IO 测试。您可以使用模拟对象。为您要测试的方法创建一个模拟,并将其与实际传入的值进行比较。在 Moq 框架中,它看起来像这样:

          var mock = new Mock<IRepository<PersonalCabinetAccount>>();
          mock.Setup(m => m.RemoveByID(expectedID))
              .Callback((Int32 a) => Assert.AreEqual(expectedID, a));
          
          var account = new PersonalCabinetAccount {ID = myID};
          var repository = mock.Object;
          repository.RemoveByID(account.myId);
          
        2. 当然,是的。如果你想养成一个习惯,你需要测试一切,我想。这样知识就来了;)

        3. 您可以使用InternalsVisibleTo 属性来避免可见性问题。

          外部可见的测试方法

        我认为这是关于企业编程的。只是用您自己的受信任代码的空闲时间。

        希望我的观点对你有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-09-11
          • 2016-11-12
          • 1970-01-01
          • 2017-09-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多