2

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

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.

Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
Vyas Bharghava
  • 6,372
  • 9
  • 39
  • 59
  • That all looks pretty cool but have you thought about using SharpZipLib to do what you are doing? – Calanus Jul 08 '09 at 08:56
  • @Calanus: Yeah, Will consider that in future... Doing some prototyping... Grabbed this code from my toolbox @ random. Thanks for the suggestion, though :) – Vyas Bharghava Jul 08 '09 at 09:13

3 Answers3

2

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

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.
Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • @Stefan, thanks for reminding about the clients (of the zipping code). Yes, I'll mock the whole zipper for the clients. Thanks again for sharing your thoughts and helping out. – Vyas Bharghava Jul 08 '09 at 11:22
0

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.

Anton Gogolev
  • 113,561
  • 39
  • 200
  • 288
  • @Anton: Thanks for the reply. Yes, I realize that'd be a good start. But what about Opening the Zip file (requires me to have a physical archive :( )? – Vyas Bharghava Jul 08 '09 at 09:11
0

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.

/// <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;
    }
}

Vyas Bharghava
  • 6,372
  • 9
  • 39
  • 59
  • I have use this code to zip my XLS file. but while i zipping it creating a XML file inside the ZIP folder. how could i remove that XML file from the ZIP as i do not want that. please refer this link:http://stackoverflow.com/questions/27035144/unexpected-xml-file-in-zip-file-created-when-compressing-an-xls-file-how-do-i-p – HomeWork Nov 24 '14 at 09:15