单元测试从Zip添加到Zip / Extracting

时间:2022-09-25 11:59:39

I have the following code for adding to/extracting from Zip. I'm trying to refactor this to make it test-ready. Can someone provide pointers on how I can accomplish this? Aside: I'm using Moq as my mock framework and MSTest as my Unit Testing tool

我有以下代码用于添加/从Zip中提取。我正在尝试重构这个以使其准备好测试。有人可以提供我如何实现这一目标的指示吗?旁白:我使用Moq作为我的模拟框架,使用MSTest作为我的单元测试工具

private const long BufferSize = 4096;

public static void ExtractZip(string zipFilename, string folder) {
  using (var zip = System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate)) {
    foreach (var part in zip.GetParts()) {
      using (var reader = new StreamReader(part.GetStream(FileMode.Open, FileAccess.Read))) {
        using (var writer = new FileStream(folder + "\\" + Path.GetFileName(part.Uri.OriginalString),
                                           FileMode.Create, FileAccess.Write)) {
          var buffer = System.Text.Encoding.UTF8.GetBytes(reader.ReadToEnd());
          writer.Write(buffer, 0, buffer.Length);
        }
      }
    }
  }
}

public static void AddFileToZip(string zipFilename, string fileToAdd) {
  using (var zip = System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate)) {
    var destFilename = ".\\" + Path.GetFileName(fileToAdd);
    var uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
    if (zip.PartExists(uri)) {
      zip.DeletePart(uri);
    }
    var part = zip.CreatePart(uri, "", CompressionOption.Normal);
    using (var fileStream = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read)) {
      using (var dest = part.GetStream()) {
        CopyStream(fileStream, dest);
      }
    }
  }
}

Thanks in advance.

提前致谢。

3 个解决方案

#1


I would make these two static methods (ExtractZip and AddFileToZip) instance methods, and put it into an interface:

我将这两个静态方法(ExtractZip和AddFileToZip)实例方法,并将其放入一个接口:

public interface IZipper
{
  void ExtractZip(string zipFilename, string folder);
  void AddFileToZip(string zipFilename, string fileToAdd);
}

public class Zipper : IZipper
{
  public void ExtractZip(string zipFilename, string folder)
  { 
    //...
  }

  void AddFileToZip(string zipFilename, string fileToAdd)
  {
    //...
  }
}


// client code
class Foo
{
  private IZipper myZipper;
  // gets an instance of the zipper (injection), but implements only 
  // against the interface. Allows mocks on the IZipper interface.
  public Foo(IZipper zipper)
  {
    myZipper = zipper;
  }

}

Client code is now easy to test.

客户端代码现在很容易测试。

What about the Zipper class?

拉链类怎么样?

  • Consider if it is worth to test anyway.
  • 考虑是否值得测试。

  • In our project, we distinguish between unit tests (isolated) and integration tests, where it is possible to use the database or the file system. You could declare it as an "file system integration test". Of course, only the test target folder is allowed to be used. This shouldn't make any problems.
  • 在我们的项目中,我们区分单元测试(隔离)和集成测试,可以使用数据库或文件系统。您可以将其声明为“文件系统集成测试”。当然,只允许使用测试目标文件夹。这不应该有任何问题。

  • Consider to move the file operations out, and only work with streams. Then you can easily test the zipping on memory streams. The file operations still need to be somewhere and aren't tested.
  • 考虑将文件操作移出,并仅使用流。然后,您可以轻松地测试内存流上的压缩。文件操作仍然需要在某处并且不进行测试。

#2


Abstract away the creation of FileStreams behind IStreamProvider and pass it to AddFileToZip and ExtractZip. You'll have to abstract filesystem operations unless you're willing to do disk IO while doing unit tests.

摘要在IStreamProvider后面创建FileStreams并将其传递给AddFileToZip和ExtractZip。除非您愿意在进行单元测试时执行磁盘IO,否则您将不得不抽象文件系统操作。

#3


I'm pasting the final code here as it might help someone and also allow me to get feedback. Thanks to Stefan for pointing me in the right direction.

我在这里粘贴最终代码,因为它可以帮助某人并允许我获得反馈。感谢Stefan指出我正确的方向。

/// <summary>
/// The commented methods are marked for future
/// </summary>
public interface IZipper
{
    //void Create();
    void ExtractAll();
    void ExtractAll(string folder);
    //void Extract(string fileName);
    void AddFile(string fileName);
    //void DeleteFile(string fileName);
}
public interface IZipStreamProvider
{
    Stream GetStream(string fileName);
}

public class ZipStreamProvider : IZipStreamProvider
{
    public Stream GetStream(string fileName)
    {
        //Create a read/writable file
        return new FileStream(fileName, FileMode.Create);
    }
}

public class Zipper : IZipper
{
    private const long BufferSize = 4096;
    public string ZipFileName { get; set;}

    //seam.. to use property injection
    private IZipStreamProvider ZipStreamProvider { get; set;}

    public Zipper(string zipFilename)
    {
        ZipFileName = zipFilename;
        //By default, write to file
        ZipStreamProvider = new ZipStreamProvider();
    }

    public void ExtractAll()
    {
        ExtractAll(Environment.CurrentDirectory);
    }

    public void ExtractAll(string folder)
    {
        using (var zip = System.IO.Packaging.Package.Open(ZipStreamProvider.GetStream(ZipFileName)))
        {
            foreach (var part in zip.GetParts())
            {
                using (var reader = new StreamReader(part.GetStream(FileMode.Open, FileAccess.Read)))
                {
                    using (var writer = ZipStreamProvider.GetStream(folder + "\\" + Path.GetFileName(part.Uri.OriginalString)))
                    {
                        var buffer = System.Text.Encoding.UTF8.GetBytes(reader.ReadToEnd());
                        writer.Write(buffer, 0, buffer.Length);
                    }
                }
            }
        }
    }

    public void AddFile(string fileToAdd)
    {
        using (var zip = System.IO.Packaging.Package.Open(ZipFileName, FileMode.OpenOrCreate))
        {
            var destFilename = ".\\" + Path.GetFileName(fileToAdd);
            var uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
            var part = zip.CreatePart(uri, "", CompressionOption.Normal);
            using (var fileStream = ZipStreamProvider.GetStream(fileToAdd))
            {
                using (var dest = part.GetStream())
                {
                    CopyStream(fileStream, dest);
                }
            }
        }
    }

    private long CopyStream(Stream inputStream, Stream outputStream)
    {
        var bufferSize = inputStream.Length < BufferSize ? inputStream.Length : BufferSize;
        var buffer = new byte[bufferSize];
        int bytesRead;
        var bytesWritten = 0L;
        while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            outputStream.Write(buffer, 0, bytesRead);
            bytesWritten += bufferSize;
        }

        return bytesWritten;
    }
}

#1


I would make these two static methods (ExtractZip and AddFileToZip) instance methods, and put it into an interface:

我将这两个静态方法(ExtractZip和AddFileToZip)实例方法,并将其放入一个接口:

public interface IZipper
{
  void ExtractZip(string zipFilename, string folder);
  void AddFileToZip(string zipFilename, string fileToAdd);
}

public class Zipper : IZipper
{
  public void ExtractZip(string zipFilename, string folder)
  { 
    //...
  }

  void AddFileToZip(string zipFilename, string fileToAdd)
  {
    //...
  }
}


// client code
class Foo
{
  private IZipper myZipper;
  // gets an instance of the zipper (injection), but implements only 
  // against the interface. Allows mocks on the IZipper interface.
  public Foo(IZipper zipper)
  {
    myZipper = zipper;
  }

}

Client code is now easy to test.

客户端代码现在很容易测试。

What about the Zipper class?

拉链类怎么样?

  • Consider if it is worth to test anyway.
  • 考虑是否值得测试。

  • In our project, we distinguish between unit tests (isolated) and integration tests, where it is possible to use the database or the file system. You could declare it as an "file system integration test". Of course, only the test target folder is allowed to be used. This shouldn't make any problems.
  • 在我们的项目中,我们区分单元测试(隔离)和集成测试,可以使用数据库或文件系统。您可以将其声明为“文件系统集成测试”。当然,只允许使用测试目标文件夹。这不应该有任何问题。

  • Consider to move the file operations out, and only work with streams. Then you can easily test the zipping on memory streams. The file operations still need to be somewhere and aren't tested.
  • 考虑将文件操作移出,并仅使用流。然后,您可以轻松地测试内存流上的压缩。文件操作仍然需要在某处并且不进行测试。

#2


Abstract away the creation of FileStreams behind IStreamProvider and pass it to AddFileToZip and ExtractZip. You'll have to abstract filesystem operations unless you're willing to do disk IO while doing unit tests.

摘要在IStreamProvider后面创建FileStreams并将其传递给AddFileToZip和ExtractZip。除非您愿意在进行单元测试时执行磁盘IO,否则您将不得不抽象文件系统操作。

#3


I'm pasting the final code here as it might help someone and also allow me to get feedback. Thanks to Stefan for pointing me in the right direction.

我在这里粘贴最终代码,因为它可以帮助某人并允许我获得反馈。感谢Stefan指出我正确的方向。

/// <summary>
/// The commented methods are marked for future
/// </summary>
public interface IZipper
{
    //void Create();
    void ExtractAll();
    void ExtractAll(string folder);
    //void Extract(string fileName);
    void AddFile(string fileName);
    //void DeleteFile(string fileName);
}
public interface IZipStreamProvider
{
    Stream GetStream(string fileName);
}

public class ZipStreamProvider : IZipStreamProvider
{
    public Stream GetStream(string fileName)
    {
        //Create a read/writable file
        return new FileStream(fileName, FileMode.Create);
    }
}

public class Zipper : IZipper
{
    private const long BufferSize = 4096;
    public string ZipFileName { get; set;}

    //seam.. to use property injection
    private IZipStreamProvider ZipStreamProvider { get; set;}

    public Zipper(string zipFilename)
    {
        ZipFileName = zipFilename;
        //By default, write to file
        ZipStreamProvider = new ZipStreamProvider();
    }

    public void ExtractAll()
    {
        ExtractAll(Environment.CurrentDirectory);
    }

    public void ExtractAll(string folder)
    {
        using (var zip = System.IO.Packaging.Package.Open(ZipStreamProvider.GetStream(ZipFileName)))
        {
            foreach (var part in zip.GetParts())
            {
                using (var reader = new StreamReader(part.GetStream(FileMode.Open, FileAccess.Read)))
                {
                    using (var writer = ZipStreamProvider.GetStream(folder + "\\" + Path.GetFileName(part.Uri.OriginalString)))
                    {
                        var buffer = System.Text.Encoding.UTF8.GetBytes(reader.ReadToEnd());
                        writer.Write(buffer, 0, buffer.Length);
                    }
                }
            }
        }
    }

    public void AddFile(string fileToAdd)
    {
        using (var zip = System.IO.Packaging.Package.Open(ZipFileName, FileMode.OpenOrCreate))
        {
            var destFilename = ".\\" + Path.GetFileName(fileToAdd);
            var uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
            var part = zip.CreatePart(uri, "", CompressionOption.Normal);
            using (var fileStream = ZipStreamProvider.GetStream(fileToAdd))
            {
                using (var dest = part.GetStream())
                {
                    CopyStream(fileStream, dest);
                }
            }
        }
    }

    private long CopyStream(Stream inputStream, Stream outputStream)
    {
        var bufferSize = inputStream.Length < BufferSize ? inputStream.Length : BufferSize;
        var buffer = new byte[bufferSize];
        int bytesRead;
        var bytesWritten = 0L;
        while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            outputStream.Write(buffer, 0, bytesRead);
            bytesWritten += bufferSize;
        }

        return bytesWritten;
    }
}