使用 Shims 对 ZipFile 进行单元测试

Unit Testing using Shims for ZipFile

我正在尝试对一些使用 ZipFile.OpenRead 的代码进行单元测试,以从 ZIP 中提取一些 XML 文件(使用最小起订量编写单元测试)

有没有办法用我自己的结果替换对 ZipFile.OpenRead 的调用?我曾在类似情况下使用垫片,但我不知道在这种情况下该怎么做,而且关于垫片的文档非常稀少。

这里是(部分)需要单元测试的方法:

    public IEnumerable<ConfigurationViewModel> ExtractXmlFromZip(string fileName)
    {
        var configs = new List<ConfigurationViewModel>();
        using (var archive = ZipFile.OpenRead(fileName))
        {
            foreach (ZipArchiveEntry entry in archive.Entries)
            {
                if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
                {
                    LoadConfigfromZipArchiveEntry(entry, configs)
                }
            }
        }
        return configs;
   }

没有办法使用 mock 来模拟像 ZipFile 这样的静态 class,您可以使用 IZipFileWrapper 来包装它(比如)

public IZipFileWrapper
{
     ZipArchive OpenRead(string fileName)
}

public ZipFileWrapper : IZipFileWrapper
{
   public ZipArchive OpenRead(string fileName)
   {
     return ZipFile.OpenRead(fileName)
   }
}

那么代码就变成了:

public MyObj
{
   private IZipFileWrapper zipFileWrapper;

   public MyObj(IZipFileWrapper zipFileWrapper)
   {
      this.zipFileWrapper = zipFileWrapper;
   }

   public IEnumerable<ConfigurationViewModel> ExtractXmlFromZip(string fileName)
   {
       var configs = new List<ConfigurationViewModel>();
       // Call the wrapper
       using (var archive = this.zipFileWrapper.OpenRead(fileName))
       {
           foreach (ZipArchiveEntry entry in archive.Entries)
           {
              if (entry.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
              {
                  LoadConfigfromZipArchiveEntry(entry, configs)
              }
           }
       }
       return configs;
    }
}

并测试

[TestMethod]
public void ExtractXmlFromZip_Test()
{
    var myThing = new MyObj();
    var fileName = "my.zip";
    ZipArchive myZipArchive = CreateTestZipFile(); // Set up your return

    var mockWrapper = new Mock<IZipFileWrapper>();    
    mockWrapper.Setup(m => m.OpenRead(fileName)).Returns(myZipArchive);

        var configs = myThing.ExtractXmlFromZip(fileName);
        // Assert
    }
}

您可能需要包装更多才能通过,但希望这能说明概念。


(在我意识到你问的是最小起订量而不是 Microsoft Fakes 的垫片之前写了这篇文章)
有一种更简单的方法可以使用可以获取此代码的 Shims - 来自 Microsoft Fakes。 ZipFile class 是 System.IO.Compression.FileSystem 的一部分,它在同名的 dll 中。

为了让我们使用 ShimZipFile,我们需要添加一个 Fakes 程序集:

注意:我们需要伪造System.IO.Compression.FileSystem(即dll)not System.IO.Compression dlll(即ZipFile的命名空间) .

其中应该对项目进行如下修改:

然后我们可以在测试中使用它,例如:

[TestMethod]
public void ExtractXmlFromZip_Test()
{
    var myThing = new MyObj();
    var fileName = "my.zip";
    ZipArchive myZipArchive = CreateTestZipFile(); // Set up your return
    using (ShimsContext.Create())
    {
        System.IO.Compression.Fakes.ShimZipFile.OpenReadString = (filename) => myZipArchive;
        var configs = myThing.ExtractXmlFromZip(fileName);
        // Assert
    }
}

MSDN 上有关于 naming conventions 伪造填充程序的信息。

我最近采用的一种方法是在 class 上使用私有委托成员,其唯一目的是包装具体实现。

幸运的是,对我来说,R# 可以轻松创建具有单个私有成员的具体代理 class,公开包装类型上的 public 成员(通过委托成员代码生成器) ,然后抽取接口解耦,提供mock方式,测试时验证。